1 // Copyright (c) 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/gfx/color_transform.h"
6
7 #include <algorithm>
8 #include <cmath>
9 #include <list>
10 #include <memory>
11 #include <sstream>
12 #include <utility>
13
14 #include "base/logging.h"
15 #include "base/notreached.h"
16 #include "third_party/skia/include/core/SkColor.h"
17 #include "third_party/skia/include/third_party/skcms/skcms.h"
18 #include "ui/gfx/color_space.h"
19 #include "ui/gfx/icc_profile.h"
20 #include "ui/gfx/skia_color_space_util.h"
21 #include "ui/gfx/transform.h"
22
23 using std::abs;
24 using std::copysign;
25 using std::exp;
26 using std::log;
27 using std::max;
28 using std::min;
29 using std::pow;
30 using std::sqrt;
31 using std::endl;
32
33 namespace gfx {
34
35 namespace {
36
InitStringStream(std::stringstream * ss)37 void InitStringStream(std::stringstream* ss) {
38 ss->imbue(std::locale::classic());
39 ss->precision(8);
40 *ss << std::scientific;
41 }
42
Str(float f)43 std::string Str(float f) {
44 std::stringstream ss;
45 InitStringStream(&ss);
46 ss << f;
47 return ss.str();
48 }
49
Invert(const Transform & t)50 Transform Invert(const Transform& t) {
51 Transform ret = t;
52 if (!t.GetInverse(&ret)) {
53 LOG(ERROR) << "Inverse should always be possible.";
54 }
55 return ret;
56 }
57
FromLinear(ColorSpace::TransferID id,float v)58 float FromLinear(ColorSpace::TransferID id, float v) {
59 switch (id) {
60 case ColorSpace::TransferID::LOG:
61 if (v < 0.01f)
62 return 0.0f;
63 return 1.0f + log(v) / log(10.0f) / 2.0f;
64
65 case ColorSpace::TransferID::LOG_SQRT:
66 if (v < sqrt(10.0f) / 1000.0f)
67 return 0.0f;
68 return 1.0f + log(v) / log(10.0f) / 2.5f;
69
70 case ColorSpace::TransferID::IEC61966_2_4: {
71 float a = 1.099296826809442f;
72 float b = 0.018053968510807f;
73 if (v < -b)
74 return -a * pow(-v, 0.45f) + (a - 1.0f);
75 else if (v <= b)
76 return 4.5f * v;
77 return a * pow(v, 0.45f) - (a - 1.0f);
78 }
79
80 case ColorSpace::TransferID::BT1361_ECG: {
81 float a = 1.099f;
82 float b = 0.018f;
83 float l = 0.0045f;
84 if (v < -l)
85 return -(a * pow(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f;
86 else if (v <= b)
87 return 4.5f * v;
88 else
89 return a * pow(v, 0.45f) - (a - 1.0f);
90 }
91
92 default:
93 // Handled by skcms_TransferFunction.
94 break;
95 }
96 NOTREACHED();
97 return 0;
98 }
99
ToLinear(ColorSpace::TransferID id,float v)100 float ToLinear(ColorSpace::TransferID id, float v) {
101 switch (id) {
102 case ColorSpace::TransferID::LOG:
103 if (v < 0.0f)
104 return 0.0f;
105 return pow(10.0f, (v - 1.0f) * 2.0f);
106
107 case ColorSpace::TransferID::LOG_SQRT:
108 if (v < 0.0f)
109 return 0.0f;
110 return pow(10.0f, (v - 1.0f) * 2.5f);
111
112 case ColorSpace::TransferID::IEC61966_2_4: {
113 float a = 1.099296826809442f;
114 // Equal to FromLinear(ColorSpace::TransferID::IEC61966_2_4, -a).
115 float from_linear_neg_a = -1.047844f;
116 // Equal to FromLinear(ColorSpace::TransferID::IEC61966_2_4, b).
117 float from_linear_b = 0.081243f;
118 if (v < from_linear_neg_a)
119 return -pow((a - 1.0f - v) / a, 1.0f / 0.45f);
120 else if (v <= from_linear_b)
121 return v / 4.5f;
122 return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
123 }
124
125 case ColorSpace::TransferID::BT1361_ECG: {
126 float a = 1.099f;
127 // Equal to FromLinear(ColorSpace::TransferID::BT1361_ECG, -l).
128 float from_linear_neg_l = -0.020250f;
129 // Equal to FromLinear(ColorSpace::TransferID::BT1361_ECG, b).
130 float from_linear_b = 0.081000f;
131 if (v < from_linear_neg_l)
132 return -pow((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f;
133 else if (v <= from_linear_b)
134 return v / 4.5f;
135 return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
136 }
137
138 default:
139 // Handled by skcms_TransferFunction.
140 break;
141 }
142 NOTREACHED();
143 return 0;
144 }
145
GetTransferMatrix(const gfx::ColorSpace & color_space)146 Transform GetTransferMatrix(const gfx::ColorSpace& color_space) {
147 SkMatrix44 transfer_matrix;
148 color_space.GetTransferMatrix(&transfer_matrix);
149 return Transform(transfer_matrix);
150 }
151
GetRangeAdjustMatrix(const gfx::ColorSpace & color_space,int bit_depth)152 Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space,
153 int bit_depth) {
154 SkMatrix44 range_adjust_matrix;
155 color_space.GetRangeAdjustMatrix(bit_depth, &range_adjust_matrix);
156 return Transform(range_adjust_matrix);
157 }
158
GetPrimaryTransform(const gfx::ColorSpace & color_space)159 Transform GetPrimaryTransform(const gfx::ColorSpace& color_space) {
160 SkMatrix44 primary_matrix;
161 color_space.GetPrimaryMatrix(&primary_matrix);
162 return Transform(primary_matrix);
163 }
164
165 } // namespace
166
167 class ColorTransformMatrix;
168 class ColorTransformSkTransferFn;
169 class ColorTransformFromLinear;
170 class ColorTransformFromBT2020CL;
171 class ColorTransformNull;
172
173 class ColorTransformStep {
174 public:
ColorTransformStep()175 ColorTransformStep() {}
~ColorTransformStep()176 virtual ~ColorTransformStep() {}
GetFromLinear()177 virtual ColorTransformFromLinear* GetFromLinear() { return nullptr; }
GetFromBT2020CL()178 virtual ColorTransformFromBT2020CL* GetFromBT2020CL() { return nullptr; }
GetSkTransferFn()179 virtual ColorTransformSkTransferFn* GetSkTransferFn() { return nullptr; }
GetMatrix()180 virtual ColorTransformMatrix* GetMatrix() { return nullptr; }
GetNull()181 virtual ColorTransformNull* GetNull() { return nullptr; }
182
183 // Join methods, returns true if the |next| transform was successfully
184 // assimilated into |this|.
185 // If Join() returns true, |next| is no longer needed and can be deleted.
Join(ColorTransformStep * next)186 virtual bool Join(ColorTransformStep* next) { return false; }
187
188 // Return true if this is a null transform.
IsNull()189 virtual bool IsNull() { return false; }
190 virtual void Transform(ColorTransform::TriStim* color, size_t num) const = 0;
191 // In the shader, |hdr| will appear before |src|, so any helper functions that
192 // are created should be put in |hdr|. Any helper functions should have
193 // |step_index| included in the function name, to ensure that there are no
194 // naming conflicts.
195 virtual void AppendShaderSource(std::stringstream* hdr,
196 std::stringstream* src,
197 size_t step_index) const = 0;
198 virtual void AppendSkShaderSource(std::stringstream* src) const = 0;
199
200 private:
201 DISALLOW_COPY_AND_ASSIGN(ColorTransformStep);
202 };
203
204 class ColorTransformInternal : public ColorTransform {
205 public:
206 ColorTransformInternal(const ColorSpace& src,
207 int src_bit_depth,
208 const ColorSpace& dst,
209 int dst_bit_depth,
210 Intent intent);
211 ~ColorTransformInternal() override;
212
GetSrcColorSpace() const213 gfx::ColorSpace GetSrcColorSpace() const override { return src_; }
GetDstColorSpace() const214 gfx::ColorSpace GetDstColorSpace() const override { return dst_; }
215
Transform(TriStim * colors,size_t num) const216 void Transform(TriStim* colors, size_t num) const override {
217 for (const auto& step : steps_) {
218 step->Transform(colors, num);
219 }
220 }
221 std::string GetShaderSource() const override;
222 std::string GetSkShaderSource() const override;
IsIdentity() const223 bool IsIdentity() const override { return steps_.empty(); }
NumberOfStepsForTesting() const224 size_t NumberOfStepsForTesting() const override { return steps_.size(); }
225
226 private:
227 void AppendColorSpaceToColorSpaceTransform(const ColorSpace& src,
228 int src_bit_depth,
229 const ColorSpace& dst,
230 int dst_bit_depth);
231 void Simplify();
232
233 std::list<std::unique_ptr<ColorTransformStep>> steps_;
234 gfx::ColorSpace src_;
235 gfx::ColorSpace dst_;
236 };
237
238 class ColorTransformNull : public ColorTransformStep {
239 public:
GetNull()240 ColorTransformNull* GetNull() override { return this; }
IsNull()241 bool IsNull() override { return true; }
Transform(ColorTransform::TriStim * color,size_t num) const242 void Transform(ColorTransform::TriStim* color, size_t num) const override {}
AppendShaderSource(std::stringstream * hdr,std::stringstream * src,size_t step_index) const243 void AppendShaderSource(std::stringstream* hdr,
244 std::stringstream* src,
245 size_t step_index) const override {}
AppendSkShaderSource(std::stringstream * src) const246 void AppendSkShaderSource(std::stringstream* src) const override {}
247 };
248
249 class ColorTransformMatrix : public ColorTransformStep {
250 public:
ColorTransformMatrix(const class Transform & matrix)251 explicit ColorTransformMatrix(const class Transform& matrix)
252 : matrix_(matrix) {}
GetMatrix()253 ColorTransformMatrix* GetMatrix() override { return this; }
Join(ColorTransformStep * next_untyped)254 bool Join(ColorTransformStep* next_untyped) override {
255 ColorTransformMatrix* next = next_untyped->GetMatrix();
256 if (!next)
257 return false;
258 class Transform tmp = next->matrix_;
259 tmp *= matrix_;
260 matrix_ = tmp;
261 return true;
262 }
263
IsNull()264 bool IsNull() override {
265 return SkMatrixIsApproximatelyIdentity(matrix_.matrix());
266 }
267
Transform(ColorTransform::TriStim * colors,size_t num) const268 void Transform(ColorTransform::TriStim* colors, size_t num) const override {
269 for (size_t i = 0; i < num; i++)
270 matrix_.TransformPoint(colors + i);
271 }
272
273
AppendShaderSource(std::stringstream * hdr,std::stringstream * src,size_t step_index) const274 void AppendShaderSource(std::stringstream* hdr,
275 std::stringstream* src,
276 size_t step_index) const override {
277 const SkMatrix44& m = matrix_.matrix();
278 *src << " color = mat3(";
279 *src << m.get(0, 0) << ", " << m.get(1, 0) << ", " << m.get(2, 0) << ",";
280 *src << endl;
281 *src << " ";
282 *src << m.get(0, 1) << ", " << m.get(1, 1) << ", " << m.get(2, 1) << ",";
283 *src << endl;
284 *src << " ";
285 *src << m.get(0, 2) << ", " << m.get(1, 2) << ", " << m.get(2, 2) << ")";
286 *src << " * color;" << endl;
287
288 // Only print the translational component if it isn't the identity.
289 if (m.get(0, 3) != 0.f || m.get(1, 3) != 0.f || m.get(2, 3) != 0.f) {
290 *src << " color += vec3(";
291 *src << m.get(0, 3) << ", " << m.get(1, 3) << ", " << m.get(2, 3);
292 *src << ");" << endl;
293 }
294 }
295
AppendSkShaderSource(std::stringstream * src) const296 void AppendSkShaderSource(std::stringstream* src) const override {
297 const SkMatrix44& m = matrix_.matrix();
298 *src << " color = half4x4(";
299 *src << m.get(0, 0) << ", " << m.get(1, 0) << ", " << m.get(2, 0) << ", 0,";
300 *src << endl;
301 *src << " ";
302 *src << m.get(0, 1) << ", " << m.get(1, 1) << ", " << m.get(2, 1) << ", 0,";
303 *src << endl;
304 *src << " ";
305 *src << m.get(0, 2) << ", " << m.get(1, 2) << ", " << m.get(2, 2) << ", 0,";
306 *src << endl;
307 *src << "0, 0, 0, 1)";
308 *src << " * color;" << endl;
309
310 // Only print the translational component if it isn't the identity.
311 if (m.get(0, 3) != 0.f || m.get(1, 3) != 0.f || m.get(2, 3) != 0.f) {
312 *src << " color += half4(";
313 *src << m.get(0, 3) << ", " << m.get(1, 3) << ", " << m.get(2, 3);
314 *src << ", 0);" << endl;
315 }
316 }
317
318 private:
319 class Transform matrix_;
320 };
321
322 class ColorTransformPerChannelTransferFn : public ColorTransformStep {
323 public:
ColorTransformPerChannelTransferFn(bool extended)324 explicit ColorTransformPerChannelTransferFn(bool extended)
325 : extended_(extended) {}
326
Transform(ColorTransform::TriStim * colors,size_t num) const327 void Transform(ColorTransform::TriStim* colors, size_t num) const override {
328 for (size_t i = 0; i < num; i++) {
329 ColorTransform::TriStim& c = colors[i];
330 if (extended_) {
331 c.set_x(copysign(Evaluate(abs(c.x())), c.x()));
332 c.set_y(copysign(Evaluate(abs(c.y())), c.y()));
333 c.set_z(copysign(Evaluate(abs(c.z())), c.z()));
334 } else {
335 c.set_x(Evaluate(c.x()));
336 c.set_y(Evaluate(c.y()));
337 c.set_z(Evaluate(c.z()));
338 }
339 }
340 }
341
AppendShaderSource(std::stringstream * hdr,std::stringstream * src,size_t step_index) const342 void AppendShaderSource(std::stringstream* hdr,
343 std::stringstream* src,
344 size_t step_index) const override {
345 *hdr << "float TransferFn" << step_index << "(float v) {" << endl;
346 AppendTransferShaderSource(hdr, true /* is_glsl */);
347 *hdr << " return v;" << endl;
348 *hdr << "}" << endl;
349 if (extended_) {
350 *src << " color.r = sign(color.r) * TransferFn" << step_index
351 << "(abs(color.r));" << endl;
352 *src << " color.g = sign(color.g) * TransferFn" << step_index
353 << "(abs(color.g));" << endl;
354 *src << " color.b = sign(color.b) * TransferFn" << step_index
355 << "(abs(color.b));" << endl;
356 } else {
357 *src << " color.r = TransferFn" << step_index << "(color.r);" << endl;
358 *src << " color.g = TransferFn" << step_index << "(color.g);" << endl;
359 *src << " color.b = TransferFn" << step_index << "(color.b);" << endl;
360 }
361 }
362
AppendSkShaderSource(std::stringstream * src) const363 void AppendSkShaderSource(std::stringstream* src) const override {
364 if (extended_) {
365 *src << "{ half v = abs(color.r);" << endl;
366 AppendTransferShaderSource(src, false /* is_glsl */);
367 *src << " color.r = sign(color.r) * v; }" << endl;
368 *src << "{ half v = abs(color.g);" << endl;
369 AppendTransferShaderSource(src, false /* is_glsl */);
370 *src << " color.g = sign(color.g) * v; }" << endl;
371 *src << "{ half v = abs(color.b);" << endl;
372 AppendTransferShaderSource(src, false /* is_glsl */);
373 *src << " color.b = sign(color.b) * v; }" << endl;
374 } else {
375 *src << "{ half v = color.r;" << endl;
376 AppendTransferShaderSource(src, false /* is_glsl */);
377 *src << " color.r = v; }" << endl;
378 *src << "{ half v = color.g;" << endl;
379 AppendTransferShaderSource(src, false /* is_glsl */);
380 *src << " color.g = v; }" << endl;
381 *src << "{ half v = color.b;" << endl;
382 AppendTransferShaderSource(src, false /* is_glsl */);
383 *src << " color.b = v; }" << endl;
384 }
385 }
386
387 virtual float Evaluate(float x) const = 0;
388 virtual void AppendTransferShaderSource(std::stringstream* src,
389 bool is_glsl) const = 0;
390
391 protected:
392 // True if the transfer function is extended to be defined for all real
393 // values by point symmetry.
394 bool extended_ = false;
395 };
396
397 // This class represents the piecewise-HDR function using three new parameters,
398 // P, Q, and R. The function is defined as:
399 // 0 : x < 0
400 // T(x) = sRGB(x/P) : x < P
401 // Q*x+R : x >= P
402 // This then expands to
403 // 0 : x < 0
404 // T(x) = C*x/P+F : x < P*D
405 // (A*x/P+B)**G + E : x < P
406 // Q*x+R : else
407 class ColorTransformPiecewiseHDR : public ColorTransformPerChannelTransferFn {
408 public:
GetParams(const gfx::ColorSpace color_space,skcms_TransferFunction * fn,float * p,float * q,float * r)409 static void GetParams(const gfx::ColorSpace color_space,
410 skcms_TransferFunction* fn,
411 float* p,
412 float* q,
413 float* r) {
414 float sdr_joint = 1;
415 float hdr_level = 1;
416 color_space.GetPiecewiseHDRParams(&sdr_joint, &hdr_level);
417
418 // P is exactly |sdr_joint|.
419 *p = sdr_joint;
420
421 if (sdr_joint < 1.f) {
422 // Q and R are computed such that |sdr_joint| maps to 1 and 1) maps to
423 // |hdr_level|.
424 *q = (hdr_level - 1.f) / (1.f - sdr_joint);
425 *r = (1.f - hdr_level * sdr_joint) / (1.f - sdr_joint);
426 } else {
427 // If |sdr_joint| is exactly 1, then just saturate at 1 (there is no HDR).
428 *q = 0;
429 *r = 1;
430 }
431
432 // Compute |fn| so that, at x, it evaluates to sRGB(x*P).
433 ColorSpace::CreateSRGB().GetTransferFunction(fn);
434 fn->d *= sdr_joint;
435 if (sdr_joint != 0) {
436 // If |sdr_joint| is 0, then we will never evaluate |fn| anyway.
437 fn->a /= sdr_joint;
438 fn->c /= sdr_joint;
439 }
440 }
InvertParams(skcms_TransferFunction * fn,float * p,float * q,float * r)441 static void InvertParams(skcms_TransferFunction* fn,
442 float* p,
443 float* q,
444 float* r) {
445 *fn = SkTransferFnInverse(*fn);
446 float old_p = *p;
447 float old_q = *q;
448 float old_r = *r;
449 *p = old_q * old_p + old_r;
450 if (old_q != 0.f) {
451 *q = 1.f / old_q;
452 *r = -old_r / old_q;
453 } else {
454 *q = 0.f;
455 *r = 1.f;
456 }
457 }
458
ColorTransformPiecewiseHDR(const skcms_TransferFunction fn,float p,float q,float r)459 ColorTransformPiecewiseHDR(const skcms_TransferFunction fn,
460 float p,
461 float q,
462 float r)
463 : ColorTransformPerChannelTransferFn(false),
464 fn_(fn),
465 p_(p),
466 q_(q),
467 r_(r) {}
468
469 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const470 float Evaluate(float v) const override {
471 if (v < 0)
472 return 0;
473 else if (v < fn_.d)
474 return fn_.c * v + fn_.f;
475 else if (v < p_)
476 return std::pow(fn_.a * v + fn_.b, fn_.g) + fn_.e;
477 else
478 return q_ * v + r_;
479 }
AppendTransferShaderSource(std::stringstream * result,bool is_glsl) const480 void AppendTransferShaderSource(std::stringstream* result,
481 bool is_glsl) const override {
482 *result << " if (v < 0.0) {\n";
483 *result << " v = 0.0;\n";
484 *result << " } else if (v < " << Str(fn_.d) << ") {\n";
485 *result << " v = " << Str(fn_.c) << " * v + " << Str(fn_.f) << ";"
486 << endl;
487 *result << " } else if (v < " << Str(p_) << ") {\n";
488 *result << " v = pow(" << Str(fn_.a) << " * v + " << Str(fn_.b) << ", "
489 << Str(fn_.g) << ") + " << Str(fn_.e) << ";\n";
490 *result << " } else {\n";
491 *result << " v = " << Str(q_) << " * v + " << Str(r_) << ";\n";
492 *result << " }\n";
493 }
494
495 private:
496 // Parameters of the SDR part.
497 const skcms_TransferFunction fn_;
498 // The SDR joint. Below this value in the domain, the function is defined by
499 // |fn_|.
500 const float p_;
501 // The slope of the linear HDR part.
502 const float q_;
503 // The intercept of the linear HDR part.
504 const float r_;
505 };
506
507 class ColorTransformSkTransferFn : public ColorTransformPerChannelTransferFn {
508 public:
ColorTransformSkTransferFn(const skcms_TransferFunction & fn,bool extended)509 explicit ColorTransformSkTransferFn(const skcms_TransferFunction& fn,
510 bool extended)
511 : ColorTransformPerChannelTransferFn(extended), fn_(fn) {}
512 // ColorTransformStep implementation.
GetSkTransferFn()513 ColorTransformSkTransferFn* GetSkTransferFn() override { return this; }
Join(ColorTransformStep * next_untyped)514 bool Join(ColorTransformStep* next_untyped) override {
515 ColorTransformSkTransferFn* next = next_untyped->GetSkTransferFn();
516 if (!next)
517 return false;
518 if (!extended_ && !next->extended_ &&
519 SkTransferFnsApproximatelyCancel(fn_, next->fn_)) {
520 // Set to be the identity.
521 fn_.a = 1;
522 fn_.b = 0;
523 fn_.c = 1;
524 fn_.d = 0;
525 fn_.e = 0;
526 fn_.f = 0;
527 fn_.g = 1;
528 return true;
529 }
530 return false;
531 }
IsNull()532 bool IsNull() override { return SkTransferFnIsApproximatelyIdentity(fn_); }
533
534 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const535 float Evaluate(float v) const override {
536 // Note that the sign-extension is performed by the caller.
537 return SkTransferFnEvalUnclamped(fn_, v);
538 }
AppendTransferShaderSource(std::stringstream * result,bool is_glsl) const539 void AppendTransferShaderSource(std::stringstream* result,
540 bool is_glsl) const override {
541 const float kEpsilon = 1.f / 1024.f;
542
543 // Construct the linear segment
544 // linear = C * x + F
545 // Elide operations that will be close to the identity.
546 std::string linear = "v";
547 if (std::abs(fn_.c - 1.f) > kEpsilon)
548 linear = Str(fn_.c) + " * " + linear;
549 if (std::abs(fn_.f) > kEpsilon)
550 linear = linear + " + " + Str(fn_.f);
551
552 // Construct the nonlinear segment.
553 // nonlinear = pow(A * x + B, G) + E
554 // Elide operations (especially the pow) that will be close to the
555 // identity.
556 std::string nonlinear = "v";
557 if (std::abs(fn_.a - 1.f) > kEpsilon)
558 nonlinear = Str(fn_.a) + " * " + nonlinear;
559 if (std::abs(fn_.b) > kEpsilon)
560 nonlinear = nonlinear + " + " + Str(fn_.b);
561 if (std::abs(fn_.g - 1.f) > kEpsilon)
562 nonlinear = "pow(" + nonlinear + ", " + Str(fn_.g) + ")";
563 if (std::abs(fn_.e) > kEpsilon)
564 nonlinear = nonlinear + " + " + Str(fn_.e);
565
566 *result << " if (v < " << Str(fn_.d) << ")" << endl;
567 *result << " v = " << linear << ";" << endl;
568 *result << " else" << endl;
569 *result << " v = " << nonlinear << ";" << endl;
570 }
571
572 private:
573 skcms_TransferFunction fn_;
574 };
575
576 class ColorTransformHLGFromLinear : public ColorTransformPerChannelTransferFn {
577 public:
ColorTransformHLGFromLinear(float sdr_white_level)578 explicit ColorTransformHLGFromLinear(float sdr_white_level)
579 : ColorTransformPerChannelTransferFn(false),
580 sdr_scale_factor_(sdr_white_level /
581 gfx::ColorSpace::kDefaultSDRWhiteLevel) {}
582
583 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const584 float Evaluate(float v) const override {
585 v *= sdr_scale_factor_;
586
587 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf
588 constexpr float a = 0.17883277f;
589 constexpr float b = 0.28466892f;
590 constexpr float c = 0.55991073f;
591 v = max(0.0f, v);
592 if (v <= 1)
593 return 0.5f * sqrt(v);
594 return a * log(v - b) + c;
595 }
596
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const597 void AppendTransferShaderSource(std::stringstream* src,
598 bool is_glsl) const override {
599 std::string scalar_type = is_glsl ? "float" : "half";
600 *src << " v = v * " << sdr_scale_factor_ << ";\n"
601 << " v = max(0.0, v);\n"
602 << " " << scalar_type << " a = 0.17883277;\n"
603 << " " << scalar_type << " b = 0.28466892;\n"
604 << " " << scalar_type << " c = 0.55991073;\n"
605 << " if (v <= 1.0)\n"
606 " v = 0.5 * sqrt(v);\n"
607 " else\n"
608 " v = a * log(v - b) + c;\n";
609 }
610
611 private:
612 const float sdr_scale_factor_;
613 };
614
615 class ColorTransformPQFromLinear : public ColorTransformPerChannelTransferFn {
616 public:
ColorTransformPQFromLinear(float sdr_white_level)617 explicit ColorTransformPQFromLinear(float sdr_white_level)
618 : ColorTransformPerChannelTransferFn(false),
619 sdr_white_level_(sdr_white_level) {}
620
621 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const622 float Evaluate(float v) const override {
623 v *= sdr_white_level_ / 10000.0f;
624 v = max(0.0f, v);
625 float m1 = (2610.0f / 4096.0f) / 4.0f;
626 float m2 = (2523.0f / 4096.0f) * 128.0f;
627 float c1 = 3424.0f / 4096.0f;
628 float c2 = (2413.0f / 4096.0f) * 32.0f;
629 float c3 = (2392.0f / 4096.0f) * 32.0f;
630 float p = powf(v, m1);
631 return powf((c1 + c2 * p) / (1.0f + c3 * p), m2);
632 }
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const633 void AppendTransferShaderSource(std::stringstream* src,
634 bool is_glsl) const override {
635 std::string scalar_type = is_glsl ? "float" : "half";
636 *src << " v *= " << sdr_white_level_
637 << " / 10000.0;\n"
638 " v = max(0.0, v);\n"
639 << " " << scalar_type << " m1 = (2610.0 / 4096.0) / 4.0;\n"
640 << " " << scalar_type << " m2 = (2523.0 / 4096.0) * 128.0;\n"
641 << " " << scalar_type << " c1 = 3424.0 / 4096.0;\n"
642 << " " << scalar_type << " c2 = (2413.0 / 4096.0) * 32.0;\n"
643 << " " << scalar_type
644 << " c3 = (2392.0 / 4096.0) * 32.0;\n"
645 " v = pow((c1 + c2 * pow(v, m1)) / \n"
646 " (1.0 + c3 * pow(v, m1)), m2);\n";
647 }
648
649 private:
650 const float sdr_white_level_;
651 };
652
653 class ColorTransformHLGToLinear : public ColorTransformPerChannelTransferFn {
654 public:
ColorTransformHLGToLinear(float sdr_white_level)655 explicit ColorTransformHLGToLinear(float sdr_white_level)
656 : ColorTransformPerChannelTransferFn(false),
657 sdr_scale_factor_(gfx::ColorSpace::kDefaultSDRWhiteLevel /
658 sdr_white_level) {}
659
660 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const661 float Evaluate(float v) const override {
662 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf
663 v = max(0.0f, v);
664 constexpr float a = 0.17883277f;
665 constexpr float b = 0.28466892f;
666 constexpr float c = 0.55991073f;
667 if (v <= 0.5f)
668 v = v * v * 4.0f;
669 else
670 v = exp((v - c) / a) + b;
671 return v * sdr_scale_factor_;
672 }
673
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const674 void AppendTransferShaderSource(std::stringstream* src,
675 bool is_glsl) const override {
676 std::string scalar_type = is_glsl ? "float" : "half";
677
678 *src << " v = max(0.0, v);\n"
679 << " " << scalar_type << " a = 0.17883277;\n"
680 << " " << scalar_type << " b = 0.28466892;\n"
681 << " " << scalar_type << " c = 0.55991073;\n"
682 << " if (v <= 0.5)\n"
683 " v = v * v * 4.0;\n"
684 " else\n"
685 " v = exp((v - c) / a) + b;\n"
686 " v = v * "
687 << sdr_scale_factor_ << ";\n";
688 }
689
690 private:
691 const float sdr_scale_factor_;
692 };
693
694 class ColorTransformPQToLinear : public ColorTransformPerChannelTransferFn {
695 public:
ColorTransformPQToLinear(float sdr_white_level)696 explicit ColorTransformPQToLinear(float sdr_white_level)
697 : ColorTransformPerChannelTransferFn(false),
698 sdr_white_level_(sdr_white_level) {}
699
700 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const701 float Evaluate(float v) const override {
702 v = max(0.0f, v);
703 float m1 = (2610.0f / 4096.0f) / 4.0f;
704 float m2 = (2523.0f / 4096.0f) * 128.0f;
705 float c1 = 3424.0f / 4096.0f;
706 float c2 = (2413.0f / 4096.0f) * 32.0f;
707 float c3 = (2392.0f / 4096.0f) * 32.0f;
708 float p = pow(v, 1.0f / m2);
709 v = powf(max(p - c1, 0.0f) / (c2 - c3 * p), 1.0f / m1);
710 v *= 10000.0f / sdr_white_level_;
711 return v;
712 }
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const713 void AppendTransferShaderSource(std::stringstream* src,
714 bool is_glsl) const override {
715 std::string scalar_type = is_glsl ? "float" : "half";
716 *src << " v = max(0.0, v);\n"
717 << " " << scalar_type << " m1 = (2610.0 / 4096.0) / 4.0;\n"
718 << " " << scalar_type << " m2 = (2523.0 / 4096.0) * 128.0;\n"
719 << " " << scalar_type << " c1 = 3424.0 / 4096.0;\n"
720 << " " << scalar_type << " c2 = (2413.0 / 4096.0) * 32.0;\n"
721 << " " << scalar_type << " c3 = (2392.0 / 4096.0) * 32.0;\n";
722 if (is_glsl) {
723 *src << " #ifdef GL_FRAGMENT_PRECISION_HIGH\n"
724 " highp float v2 = v;\n"
725 " #else\n"
726 " float v2 = v;\n"
727 " #endif\n";
728 } else {
729 *src << " " << scalar_type << " v2 = v;\n";
730 }
731 *src << " v2 = pow(max(pow(v2, 1.0 / m2) - c1, 0.0) /\n"
732 " (c2 - c3 * pow(v2, 1.0 / m2)), 1.0 / m1);\n"
733 " v = v2 * 10000.0 / "
734 << sdr_white_level_ << ";\n";
735 }
736
737 private:
738 const float sdr_white_level_;
739 };
740
741 class ColorTransformFromLinear : public ColorTransformPerChannelTransferFn {
742 public:
743 // ColorTransformStep implementation.
ColorTransformFromLinear(ColorSpace::TransferID transfer)744 explicit ColorTransformFromLinear(ColorSpace::TransferID transfer)
745 : ColorTransformPerChannelTransferFn(false), transfer_(transfer) {}
GetFromLinear()746 ColorTransformFromLinear* GetFromLinear() override { return this; }
IsNull()747 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; }
748
749 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const750 float Evaluate(float v) const override { return FromLinear(transfer_, v); }
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const751 void AppendTransferShaderSource(std::stringstream* src,
752 bool is_glsl) const override {
753 std::string scalar_type = is_glsl ? "float" : "half";
754 // This is a string-ized copy-paste from FromLinear.
755 switch (transfer_) {
756 case ColorSpace::TransferID::LOG:
757 *src << " if (v < 0.01)\n"
758 " v = 0.0;\n"
759 " else\n"
760 " v = 1.0 + log(v) / log(10.0) / 2.0;\n";
761 return;
762 case ColorSpace::TransferID::LOG_SQRT:
763 *src << " if (v < sqrt(10.0) / 1000.0)\n"
764 " v = 0.0;\n"
765 " else\n"
766 " v = 1.0 + log(v) / log(10.0) / 2.5;\n";
767 return;
768 case ColorSpace::TransferID::IEC61966_2_4:
769 *src << " " << scalar_type << " a = 1.099296826809442;\n"
770 << " " << scalar_type << " b = 0.018053968510807;\n"
771 << " if (v < -b)\n"
772 " v = -a * pow(-v, 0.45) + (a - 1.0);\n"
773 " else if (v <= b)\n"
774 " v = 4.5 * v;\n"
775 " else\n"
776 " v = a * pow(v, 0.45) - (a - 1.0);\n";
777 return;
778 case ColorSpace::TransferID::BT1361_ECG:
779 *src << " " << scalar_type << " a = 1.099;\n"
780 << " " << scalar_type << " b = 0.018;\n"
781 << " " << scalar_type << " l = 0.0045;\n"
782 << " if (v < -l)\n"
783 " v = -(a * pow(-4.0 * v, 0.45) + (a - 1.0)) / 4.0;\n"
784 " else if (v <= b)\n"
785 " v = 4.5 * v;\n"
786 " else\n"
787 " v = a * pow(v, 0.45) - (a - 1.0);\n";
788 return;
789 default:
790 break;
791 }
792 NOTREACHED();
793 }
794
795 private:
796 friend class ColorTransformToLinear;
797 ColorSpace::TransferID transfer_;
798 };
799
800 class ColorTransformToLinear : public ColorTransformPerChannelTransferFn {
801 public:
ColorTransformToLinear(ColorSpace::TransferID transfer)802 explicit ColorTransformToLinear(ColorSpace::TransferID transfer)
803 : ColorTransformPerChannelTransferFn(false), transfer_(transfer) {}
804 // ColorTransformStep implementation:
Join(ColorTransformStep * next_untyped)805 bool Join(ColorTransformStep* next_untyped) override {
806 ColorTransformFromLinear* next = next_untyped->GetFromLinear();
807 if (!next)
808 return false;
809 if (transfer_ == next->transfer_) {
810 transfer_ = ColorSpace::TransferID::LINEAR;
811 return true;
812 }
813 return false;
814 }
IsNull()815 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; }
816
817 // ColorTransformPerChannelTransferFn implementation:
Evaluate(float v) const818 float Evaluate(float v) const override { return ToLinear(transfer_, v); }
819
820 // This is a string-ized copy-paste from ToLinear.
AppendTransferShaderSource(std::stringstream * src,bool is_glsl) const821 void AppendTransferShaderSource(std::stringstream* src,
822 bool is_glsl) const override {
823 std::string scalar_type = is_glsl ? "float" : "half";
824 switch (transfer_) {
825 case ColorSpace::TransferID::LOG:
826 *src << " if (v < 0.0)\n"
827 " v = 0.0;\n"
828 " else\n"
829 " v = pow(10.0, (v - 1.0) * 2.0);\n";
830 return;
831 case ColorSpace::TransferID::LOG_SQRT:
832 *src << " if (v < 0.0)\n"
833 " v = 0.0;\n"
834 " else\n"
835 " v = pow(10.0, (v - 1.0) * 2.5);\n";
836 return;
837 case ColorSpace::TransferID::IEC61966_2_4:
838 *src << " " << scalar_type << " a = 1.099296826809442;\n"
839 << " " << scalar_type << " from_linear_neg_a = -1.047844;\n"
840 << " " << scalar_type << " from_linear_b = 0.081243;\n"
841 << " if (v < from_linear_neg_a)\n"
842 " v = -pow((a - 1.0 - v) / a, 1.0 / 0.45);\n"
843 " else if (v <= from_linear_b)\n"
844 " v = v / 4.5;\n"
845 " else\n"
846 " v = pow((v + a - 1.0) / a, 1.0 / 0.45);\n";
847 return;
848 case ColorSpace::TransferID::BT1361_ECG:
849 *src << " " << scalar_type << " a = 1.099;\n"
850 << " " << scalar_type << " from_linear_neg_l = -0.020250;\n"
851 << " " << scalar_type << " from_linear_b = 0.081000;\n"
852 << " if (v < from_linear_neg_l)\n"
853 " v = -pow((1.0 - a - v * 4.0) / a, 1.0 / 0.45) / 4.0;\n"
854 " else if (v <= from_linear_b)\n"
855 " v = v / 4.5;\n"
856 " else\n"
857 " v = pow((v + a - 1.0) / a, 1.0 / 0.45);\n";
858 return;
859 default:
860 break;
861 }
862 NOTREACHED();
863 }
864
865 private:
866 ColorSpace::TransferID transfer_;
867 };
868
869 // BT2020 Constant Luminance is different than most other
870 // ways to encode RGB values as YUV. The basic idea is that
871 // transfer functions are applied on the Y value instead of
872 // on the RGB values. However, running the transfer function
873 // on the U and V values doesn't make any sense since they
874 // are centered at 0.5. To work around this, the transfer function
875 // is applied to the Y, R and B values, and then the U and V
876 // values are calculated from that.
877 // In our implementation, the YUV->RGB matrix is used to
878 // convert YUV to RYB (the G value is replaced with an Y value.)
879 // Then we run the transfer function like normal, and finally
880 // this class is inserted as an extra step which takes calculates
881 // the U and V values.
882 class ColorTransformFromBT2020CL : public ColorTransformStep {
883 public:
Transform(ColorTransform::TriStim * YUV,size_t num) const884 void Transform(ColorTransform::TriStim* YUV, size_t num) const override {
885 for (size_t i = 0; i < num; i++) {
886 float Y = YUV[i].x();
887 float U = YUV[i].y() - 0.5;
888 float V = YUV[i].z() - 0.5;
889 float B_Y, R_Y;
890 if (U <= 0) {
891 B_Y = U * (-2.0 * -0.9702);
892 } else {
893 B_Y = U * (2.0 * 0.7910);
894 }
895 if (V <= 0) {
896 R_Y = V * (-2.0 * -0.8591);
897 } else {
898 R_Y = V * (2.0 * 0.4969);
899 }
900 // Return an RYB value, later steps will fix it.
901 YUV[i] = ColorTransform::TriStim(R_Y + Y, Y, B_Y + Y);
902 }
903 }
AppendShaderSource(std::stringstream * hdr,std::stringstream * src,size_t step_index) const904 void AppendShaderSource(std::stringstream* hdr,
905 std::stringstream* src,
906 size_t step_index) const override {
907 *hdr << "vec3 BT2020_YUV_to_RYB_Step" << step_index << "(vec3 color) {"
908 << endl;
909 *hdr << " float Y = color.x;" << endl;
910 *hdr << " float U = color.y - 0.5;" << endl;
911 *hdr << " float V = color.z - 0.5;" << endl;
912 *hdr << " float B_Y = 0.0;" << endl;
913 *hdr << " float R_Y = 0.0;" << endl;
914 *hdr << " if (U <= 0.0) {" << endl;
915 *hdr << " B_Y = U * (-2.0 * -0.9702);" << endl;
916 *hdr << " } else {" << endl;
917 *hdr << " B_Y = U * (2.0 * 0.7910);" << endl;
918 *hdr << " }" << endl;
919 *hdr << " if (V <= 0.0) {" << endl;
920 *hdr << " R_Y = V * (-2.0 * -0.8591);" << endl;
921 *hdr << " } else {" << endl;
922 *hdr << " R_Y = V * (2.0 * 0.4969);" << endl;
923 *hdr << " }" << endl;
924 *hdr << " return vec3(R_Y + Y, Y, B_Y + Y);" << endl;
925 *hdr << "}" << endl;
926
927 *src << " color.rgb = BT2020_YUV_to_RYB_Step" << step_index
928 << "(color.rgb);" << endl;
929 }
930
AppendSkShaderSource(std::stringstream * src) const931 void AppendSkShaderSource(std::stringstream* src) const override {
932 NOTREACHED();
933 }
934 };
935
AppendColorSpaceToColorSpaceTransform(const ColorSpace & src,int src_bit_depth,const ColorSpace & dst,int dst_bit_depth)936 void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform(
937 const ColorSpace& src,
938 int src_bit_depth,
939 const ColorSpace& dst,
940 int dst_bit_depth) {
941 steps_.push_back(std::make_unique<ColorTransformMatrix>(
942 GetRangeAdjustMatrix(src, src_bit_depth)));
943
944 if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
945 // BT2020 CL is a special case.
946 steps_.push_back(std::make_unique<ColorTransformFromBT2020CL>());
947 } else {
948 steps_.push_back(
949 std::make_unique<ColorTransformMatrix>(Invert(GetTransferMatrix(src))));
950 }
951
952 // If the target color space is not defined, just apply the adjust and
953 // tranfer matrices. This path is used by YUV to RGB color conversion
954 // when full color conversion is not enabled.
955 if (!dst.IsValid())
956 return;
957
958 skcms_TransferFunction src_to_linear_fn;
959 if (src.GetTransferFunction(&src_to_linear_fn)) {
960 steps_.push_back(std::make_unique<ColorTransformSkTransferFn>(
961 src_to_linear_fn, src.HasExtendedSkTransferFn()));
962 } else if (src.GetTransferID() == ColorSpace::TransferID::ARIB_STD_B67) {
963 float sdr_white_level = 0.f;
964 src.GetSDRWhiteLevel(&sdr_white_level);
965 steps_.push_back(
966 std::make_unique<ColorTransformHLGToLinear>(sdr_white_level));
967 } else if (src.GetTransferID() == ColorSpace::TransferID::SMPTEST2084) {
968 float sdr_white_level = 0.f;
969 src.GetSDRWhiteLevel(&sdr_white_level);
970 steps_.push_back(
971 std::make_unique<ColorTransformPQToLinear>(sdr_white_level));
972 } else if (src.GetTransferID() == ColorSpace::TransferID::PIECEWISE_HDR) {
973 skcms_TransferFunction fn;
974 float p, q, r;
975 ColorTransformPiecewiseHDR::GetParams(src, &fn, &p, &q, &r);
976 steps_.push_back(std::make_unique<ColorTransformPiecewiseHDR>(fn, p, q, r));
977 } else {
978 steps_.push_back(
979 std::make_unique<ColorTransformToLinear>(src.GetTransferID()));
980 }
981
982 if (src.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
983 // BT2020 CL is a special case.
984 steps_.push_back(
985 std::make_unique<ColorTransformMatrix>(Invert(GetTransferMatrix(src))));
986 }
987 steps_.push_back(
988 std::make_unique<ColorTransformMatrix>(GetPrimaryTransform(src)));
989
990 steps_.push_back(
991 std::make_unique<ColorTransformMatrix>(Invert(GetPrimaryTransform(dst))));
992 if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
993 // BT2020 CL is a special case.
994 steps_.push_back(
995 std::make_unique<ColorTransformMatrix>(GetTransferMatrix(dst)));
996 }
997
998 skcms_TransferFunction dst_from_linear_fn;
999 if (dst.GetInverseTransferFunction(&dst_from_linear_fn)) {
1000 steps_.push_back(std::make_unique<ColorTransformSkTransferFn>(
1001 dst_from_linear_fn, dst.HasExtendedSkTransferFn()));
1002 } else if (dst.GetTransferID() == ColorSpace::TransferID::ARIB_STD_B67) {
1003 float sdr_white_level = 0.f;
1004 dst.GetSDRWhiteLevel(&sdr_white_level);
1005 steps_.push_back(
1006 std::make_unique<ColorTransformHLGFromLinear>(sdr_white_level));
1007 } else if (dst.GetTransferID() == ColorSpace::TransferID::SMPTEST2084) {
1008 float sdr_white_level = 0.f;
1009 dst.GetSDRWhiteLevel(&sdr_white_level);
1010 steps_.push_back(
1011 std::make_unique<ColorTransformPQFromLinear>(sdr_white_level));
1012 } else if (dst.GetTransferID() == ColorSpace::TransferID::PIECEWISE_HDR) {
1013 skcms_TransferFunction fn;
1014 float p, q, r;
1015 ColorTransformPiecewiseHDR::GetParams(dst, &fn, &p, &q, &r);
1016 ColorTransformPiecewiseHDR::InvertParams(&fn, &p, &q, &r);
1017 steps_.push_back(std::make_unique<ColorTransformPiecewiseHDR>(fn, p, q, r));
1018 } else {
1019 steps_.push_back(
1020 std::make_unique<ColorTransformFromLinear>(dst.GetTransferID()));
1021 }
1022
1023 if (dst.GetMatrixID() == ColorSpace::MatrixID::BT2020_CL) {
1024 NOTREACHED();
1025 } else {
1026 steps_.push_back(
1027 std::make_unique<ColorTransformMatrix>(GetTransferMatrix(dst)));
1028 }
1029
1030 steps_.push_back(std::make_unique<ColorTransformMatrix>(
1031 Invert(GetRangeAdjustMatrix(dst, dst_bit_depth))));
1032 }
1033
ColorTransformInternal(const ColorSpace & src,int src_bit_depth,const ColorSpace & dst,int dst_bit_depth,Intent intent)1034 ColorTransformInternal::ColorTransformInternal(const ColorSpace& src,
1035 int src_bit_depth,
1036 const ColorSpace& dst,
1037 int dst_bit_depth,
1038 Intent intent)
1039 : src_(src), dst_(dst) {
1040 // If no source color space is specified, do no transformation.
1041 // TODO(ccameron): We may want dst assume sRGB at some point in the future.
1042 if (!src_.IsValid())
1043 return;
1044 AppendColorSpaceToColorSpaceTransform(src_, src_bit_depth, dst_,
1045 dst_bit_depth);
1046 if (intent != Intent::TEST_NO_OPT)
1047 Simplify();
1048 }
1049
GetShaderSource() const1050 std::string ColorTransformInternal::GetShaderSource() const {
1051 std::stringstream hdr;
1052 std::stringstream src;
1053 InitStringStream(&hdr);
1054 InitStringStream(&src);
1055 src << "vec3 DoColorConversion(vec3 color) {" << endl;
1056 size_t step_index = 0;
1057 for (const auto& step : steps_)
1058 step->AppendShaderSource(&hdr, &src, step_index++);
1059 src << " return color;" << endl;
1060 src << "}" << endl;
1061 return hdr.str() + src.str();
1062 }
1063
GetSkShaderSource() const1064 std::string ColorTransformInternal::GetSkShaderSource() const {
1065 std::stringstream src;
1066 InitStringStream(&src);
1067 for (const auto& step : steps_)
1068 step->AppendSkShaderSource(&src);
1069 return src.str();
1070 }
1071
~ColorTransformInternal()1072 ColorTransformInternal::~ColorTransformInternal() {}
1073
Simplify()1074 void ColorTransformInternal::Simplify() {
1075 for (auto iter = steps_.begin(); iter != steps_.end();) {
1076 std::unique_ptr<ColorTransformStep>& this_step = *iter;
1077
1078 // Try to Join |next_step| into |this_step|. If successful, re-visit the
1079 // step before |this_step|.
1080 auto iter_next = iter;
1081 iter_next++;
1082 if (iter_next != steps_.end()) {
1083 std::unique_ptr<ColorTransformStep>& next_step = *iter_next;
1084 if (this_step->Join(next_step.get())) {
1085 steps_.erase(iter_next);
1086 if (iter != steps_.begin())
1087 --iter;
1088 continue;
1089 }
1090 }
1091
1092 // If |this_step| step is a no-op, remove it, and re-visit the step before
1093 // |this_step|.
1094 if (this_step->IsNull()) {
1095 iter = steps_.erase(iter);
1096 if (iter != steps_.begin())
1097 --iter;
1098 continue;
1099 }
1100
1101 ++iter;
1102 }
1103 }
1104
1105 // static
NewColorTransform(const ColorSpace & src,int src_bit_depth,const ColorSpace & dst,int dst_bit_depth,Intent intent)1106 std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform(
1107 const ColorSpace& src,
1108 int src_bit_depth,
1109 const ColorSpace& dst,
1110 int dst_bit_depth,
1111 Intent intent) {
1112 return std::make_unique<ColorTransformInternal>(src, src_bit_depth, dst,
1113 dst_bit_depth, intent);
1114 }
1115
ColorTransform()1116 ColorTransform::ColorTransform() {}
~ColorTransform()1117 ColorTransform::~ColorTransform() {}
1118
1119 } // namespace gfx
1120