| Index: ui/gfx/color_transform.cc
|
| diff --git a/ui/gfx/color_transform.cc b/ui/gfx/color_transform.cc
|
| index ecc8aaf2297789a328609a0347b3757a2bbe0307..49a3026c895e13c33926b613c6724dc4e45d723c 100644
|
| --- a/ui/gfx/color_transform.cc
|
| +++ b/ui/gfx/color_transform.cc
|
| @@ -5,14 +5,17 @@
|
| #include "ui/gfx/color_transform.h"
|
|
|
| #include <algorithm>
|
| -#include <vector>
|
| +#include <cmath>
|
| +#include <list>
|
| +#include <memory>
|
|
|
| #include "base/logging.h"
|
| #include "base/memory/ptr_util.h"
|
| +#include "third_party/qcms/src/qcms.h"
|
| #include "ui/gfx/color_space.h"
|
| #include "ui/gfx/icc_profile.h"
|
| +#include "ui/gfx/skia_color_space_util.h"
|
| #include "ui/gfx/transform.h"
|
| -#include "third_party/qcms/src/qcms.h"
|
|
|
| #ifndef THIS_MUST_BE_INCLUDED_AFTER_QCMS_H
|
| extern "C" {
|
| @@ -20,15 +23,26 @@ extern "C" {
|
| };
|
| #endif
|
|
|
| +using std::exp;
|
| +using std::log;
|
| +using std::max;
|
| +using std::min;
|
| +using std::pow;
|
| +using std::sqrt;
|
| +
|
| namespace gfx {
|
|
|
| -float EvalSkTransferFn(const SkColorSpaceTransferFn& fn, float x) {
|
| - if (x < 0)
|
| - return 0;
|
| - if (x < fn.fD)
|
| - return fn.fC * x + fn.fF;
|
| - return powf(fn.fA * x + fn.fB, fn.fG) + fn.fE;
|
| -}
|
| +namespace {
|
| +
|
| +// Helper for scoped QCMS profiles.
|
| +struct QcmsProfileDeleter {
|
| + void operator()(qcms_profile* p) {
|
| + if (p) {
|
| + qcms_profile_release(p);
|
| + }
|
| + }
|
| +};
|
| +using ScopedQcmsProfile = std::unique_ptr<qcms_profile, QcmsProfileDeleter>;
|
|
|
| Transform Invert(const Transform& t) {
|
| Transform ret = t;
|
| @@ -58,11 +72,11 @@ float FromLinear(ColorSpace::TransferID id, float v) {
|
| float a = 1.099296826809442f;
|
| float b = 0.018053968510807f;
|
| if (v < -b) {
|
| - return -a * powf(-v, 0.45f) + (a - 1.0f);
|
| + return -a * pow(-v, 0.45f) + (a - 1.0f);
|
| } else if (v <= b) {
|
| return 4.5f * v;
|
| } else {
|
| - return a * powf(v, 0.45f) - (a - 1.0f);
|
| + return a * pow(v, 0.45f) - (a - 1.0f);
|
| }
|
| }
|
|
|
| @@ -71,24 +85,24 @@ float FromLinear(ColorSpace::TransferID id, float v) {
|
| float b = 0.018f;
|
| float l = 0.0045f;
|
| if (v < -l) {
|
| - return -(a * powf(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f;
|
| + return -(a * pow(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f;
|
| } else if (v <= b) {
|
| return 4.5f * v;
|
| } else {
|
| - return a * powf(v, 0.45f) - (a - 1.0f);
|
| + return a * pow(v, 0.45f) - (a - 1.0f);
|
| }
|
| }
|
|
|
| case ColorSpace::TransferID::SMPTEST2084: {
|
| // Go from scRGB levels to 0-1.
|
| v *= 80.0f / 10000.0f;
|
| - v = fmax(0.0f, v);
|
| + v = max(0.0f, v);
|
| float m1 = (2610.0f / 4096.0f) / 4.0f;
|
| float m2 = (2523.0f / 4096.0f) * 128.0f;
|
| float c1 = 3424.0f / 4096.0f;
|
| float c2 = (2413.0f / 4096.0f) * 32.0f;
|
| float c3 = (2392.0f / 4096.0f) * 32.0f;
|
| - return powf((c1 + c2 * powf(v, m1)) / (1.0f + c3 * powf(v, m1)), m2);
|
| + return pow((c1 + c2 * pow(v, m1)) / (1.0f + c3 * pow(v, m1)), m2);
|
| }
|
|
|
| // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf
|
| @@ -96,9 +110,9 @@ float FromLinear(ColorSpace::TransferID id, float v) {
|
| const float a = 0.17883277f;
|
| const float b = 0.28466892f;
|
| const float c = 0.55991073f;
|
| - v = fmax(0.0f, v);
|
| + v = max(0.0f, v);
|
| if (v <= 1)
|
| - return 0.5f * sqrtf(v);
|
| + return 0.5f * sqrt(v);
|
| else
|
| return a * log(v - b) + c;
|
| }
|
| @@ -116,22 +130,22 @@ float ToLinear(ColorSpace::TransferID id, float v) {
|
| case ColorSpace::TransferID::LOG:
|
| if (v < 0.0f)
|
| return 0.0f;
|
| - return powf(10.0f, (v - 1.0f) * 2.0f);
|
| + return pow(10.0f, (v - 1.0f) * 2.0f);
|
|
|
| case ColorSpace::TransferID::LOG_SQRT:
|
| if (v < 0.0f)
|
| return 0.0f;
|
| - return powf(10.0f, (v - 1.0f) * 2.5f);
|
| + return pow(10.0f, (v - 1.0f) * 2.5f);
|
|
|
| case ColorSpace::TransferID::IEC61966_2_4: {
|
| float a = 1.099296826809442f;
|
| float b = 0.018053968510807f;
|
| if (v < FromLinear(ColorSpace::TransferID::IEC61966_2_4, -a)) {
|
| - return -powf((a - 1.0f - v) / a, 1.0f / 0.45f);
|
| + return -pow((a - 1.0f - v) / a, 1.0f / 0.45f);
|
| } else if (v <= FromLinear(ColorSpace::TransferID::IEC61966_2_4, b)) {
|
| return v / 4.5f;
|
| } else {
|
| - return powf((v + a - 1.0f) / a, 1.0f / 0.45f);
|
| + return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
|
| }
|
| }
|
|
|
| @@ -140,24 +154,23 @@ float ToLinear(ColorSpace::TransferID id, float v) {
|
| float b = 0.018f;
|
| float l = 0.0045f;
|
| if (v < FromLinear(ColorSpace::TransferID::BT1361_ECG, -l)) {
|
| - return -powf((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f;
|
| + return -pow((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f;
|
| } else if (v <= FromLinear(ColorSpace::TransferID::BT1361_ECG, b)) {
|
| return v / 4.5f;
|
| } else {
|
| - return powf((v + a - 1.0f) / a, 1.0f / 0.45f);
|
| + return pow((v + a - 1.0f) / a, 1.0f / 0.45f);
|
| }
|
| }
|
|
|
| case ColorSpace::TransferID::SMPTEST2084: {
|
| - v = fmax(0.0f, v);
|
| + v = max(0.0f, v);
|
| float m1 = (2610.0f / 4096.0f) / 4.0f;
|
| float m2 = (2523.0f / 4096.0f) * 128.0f;
|
| float c1 = 3424.0f / 4096.0f;
|
| float c2 = (2413.0f / 4096.0f) * 32.0f;
|
| float c3 = (2392.0f / 4096.0f) * 32.0f;
|
| - v = powf(
|
| - fmax(powf(v, 1.0f / m2) - c1, 0) / (c2 - c3 * powf(v, 1.0f / m2)),
|
| - 1.0f / m1);
|
| + v = pow(max(pow(v, 1.0f / m2) - c1, 0.0f) / (c2 - c3 * pow(v, 1.0f / m2)),
|
| + 1.0f / m1);
|
| // This matches the scRGB definition that 1.0 means 80 nits.
|
| // TODO(hubbe): It would be *nice* if 1.0 meant more than that, but
|
| // that might be difficult to do right now.
|
| @@ -166,12 +179,12 @@ float ToLinear(ColorSpace::TransferID id, float v) {
|
| }
|
|
|
| case ColorSpace::TransferID::SMPTEST2084_NON_HDR:
|
| - v = fmax(0.0f, v);
|
| - return fmin(2.3f * pow(v, 2.8f), v / 5.0f + 0.8f);
|
| + v = max(0.0f, v);
|
| + return min(2.3f * pow(v, 2.8f), v / 5.0f + 0.8f);
|
|
|
| // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf
|
| case ColorSpace::TransferID::ARIB_STD_B67: {
|
| - v = fmax(0.0f, v);
|
| + v = max(0.0f, v);
|
| const float a = 0.17883277f;
|
| const float b = 0.28466892f;
|
| const float c = 0.55991073f;
|
| @@ -204,79 +217,105 @@ Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space) {
|
| return Transform(range_adjust_matrix);
|
| }
|
|
|
| +Transform GetPrimaryTransform(const gfx::ColorSpace& color_space) {
|
| + SkMatrix44 primary_matrix;
|
| + color_space.GetPrimaryMatrix(&primary_matrix);
|
| + return Transform(primary_matrix);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| class ColorTransformMatrix;
|
| -class ColorTransformToLinear;
|
| class ColorTransformFromLinear;
|
| class ColorTransformToBT2020CL;
|
| class ColorTransformFromBT2020CL;
|
| class ColorTransformNull;
|
| +class QCMSColorTransform;
|
|
|
| -class ColorTransformInternal : public ColorTransform {
|
| +class ColorTransformStep {
|
| public:
|
| - // Visitor pattern, Prepend() calls return prev->Join(this).
|
| - virtual bool Prepend(ColorTransformInternal* prev) = 0;
|
| + ColorTransformStep() {}
|
| + virtual ~ColorTransformStep() {}
|
| + virtual ColorTransformFromLinear* GetFromLinear() { return nullptr; }
|
| + virtual ColorTransformToBT2020CL* GetToBT2020CL() { return nullptr; }
|
| + virtual ColorTransformFromBT2020CL* GetFromBT2020CL() { return nullptr; }
|
| + virtual ColorTransformMatrix* GetMatrix() { return nullptr; }
|
| + virtual ColorTransformNull* GetNull() { return nullptr; }
|
| + virtual QCMSColorTransform* GetQCMS() { return nullptr; }
|
|
|
| // Join methods, returns true if the |next| transform was successfully
|
| // assimilated into |this|.
|
| // If Join() returns true, |next| is no longer needed and can be deleted.
|
| - virtual bool Join(const ColorTransformToLinear& next) { return false; }
|
| - virtual bool Join(const ColorTransformFromLinear& next) { return false; }
|
| - virtual bool Join(const ColorTransformToBT2020CL& next) { return false; }
|
| - virtual bool Join(const ColorTransformFromBT2020CL& next) { return false; }
|
| - virtual bool Join(const ColorTransformMatrix& next) { return false; }
|
| - virtual bool Join(const ColorTransformNull& next) { return true; }
|
| + virtual bool Join(ColorTransformStep* next) { return false; }
|
|
|
| // Return true if this is a null transform.
|
| virtual bool IsNull() { return false; }
|
| +
|
| + virtual void Transform(ColorTransform::TriStim* color, size_t num) = 0;
|
| +
|
| + private:
|
| + DISALLOW_COPY_AND_ASSIGN(ColorTransformStep);
|
| };
|
|
|
| -class ColorTransformNull : public ColorTransformInternal {
|
| +class ColorTransformInternal : public ColorTransform {
|
| public:
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| + ColorTransformInternal(const ColorSpace& from,
|
| + const ColorSpace& to,
|
| + Intent intent);
|
| + ~ColorTransformInternal() override;
|
| +
|
| + // Perform transformation of colors, |colors| is both input and output.
|
| + void Transform(TriStim* colors, size_t num) override {
|
| + for (const auto& step : steps_)
|
| + step->Transform(colors, num);
|
| }
|
| - bool IsNull() override { return true; }
|
| - void transform(ColorTransform::TriStim* color, size_t num) override {}
|
| + size_t NumberOfStepsForTesting() const override { return steps_.size(); }
|
| +
|
| + private:
|
| + void AppendColorSpaceToColorSpaceTransform(ColorSpace from,
|
| + const ColorSpace& to,
|
| + ColorTransform::Intent intent);
|
| + void Simplify();
|
| +
|
| + std::list<std::unique_ptr<ColorTransformStep>> steps_;
|
| };
|
|
|
| -class ColorTransformMatrix : public ColorTransformInternal {
|
| +class ColorTransformNull : public ColorTransformStep {
|
| public:
|
| - explicit ColorTransformMatrix(const Transform& matrix) : matrix_(matrix) {}
|
| -
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| - }
|
| + ColorTransformNull* GetNull() override { return this; }
|
| + bool IsNull() override { return true; }
|
| + void Transform(ColorTransform::TriStim* color, size_t num) override {}
|
| +};
|
|
|
| - bool Join(const ColorTransformMatrix& next) override {
|
| - Transform tmp = next.matrix_;
|
| +class ColorTransformMatrix : public ColorTransformStep {
|
| + public:
|
| + explicit ColorTransformMatrix(const class Transform& matrix)
|
| + : matrix_(matrix) {}
|
| + ColorTransformMatrix* GetMatrix() override { return this; }
|
| + bool Join(ColorTransformStep* next_untyped) override {
|
| + ColorTransformMatrix* next = next_untyped->GetMatrix();
|
| + if (!next)
|
| + return false;
|
| + class Transform tmp = next->matrix_;
|
| tmp *= matrix_;
|
| matrix_ = tmp;
|
| return true;
|
| }
|
|
|
| bool IsNull() override {
|
| - // Returns true if we're very close to an identity matrix.
|
| - for (int i = 0; i < 4; i++) {
|
| - for (int j = 0; j < 4; j++) {
|
| - float expected = i == j ? 1.0f : 0.0f;
|
| - if (fabs(matrix_.matrix().get(i, j) - expected) > 0.00001f) {
|
| - return false;
|
| - }
|
| - }
|
| - }
|
| - return true;
|
| + return SkMatrixIsApproximatelyIdentity(matrix_.matrix());
|
| }
|
|
|
| - void transform(ColorTransform::TriStim* colors, size_t num) override {
|
| + void Transform(ColorTransform::TriStim* colors, size_t num) override {
|
| for (size_t i = 0; i < num; i++)
|
| matrix_.TransformPoint(colors + i);
|
| }
|
|
|
| private:
|
| - Transform matrix_;
|
| + class Transform matrix_;
|
| };
|
|
|
| -class ColorTransformFromLinear : public ColorTransformInternal {
|
| +class ColorTransformFromLinear : public ColorTransformStep {
|
| public:
|
| explicit ColorTransformFromLinear(ColorSpace::TransferID transfer,
|
| const SkColorSpaceTransferFn& fn,
|
| @@ -285,13 +324,9 @@ class ColorTransformFromLinear : public ColorTransformInternal {
|
| if (transfer_ == ColorSpace::TransferID::LINEAR_HDR)
|
| transfer_ = ColorSpace::TransferID::LINEAR;
|
| }
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| - }
|
| -
|
| + ColorTransformFromLinear* GetFromLinear() override { return this; }
|
| bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; }
|
| -
|
| - void transform(ColorTransform::TriStim* colors, size_t num) override {
|
| + void Transform(ColorTransform::TriStim* colors, size_t num) override {
|
| if (fn_valid_) {
|
| for (size_t i = 0; i < num; i++) {
|
| colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x()));
|
| @@ -314,7 +349,7 @@ class ColorTransformFromLinear : public ColorTransformInternal {
|
| bool fn_valid_ = false;
|
| };
|
|
|
| -class ColorTransformToLinear : public ColorTransformInternal {
|
| +class ColorTransformToLinear : public ColorTransformStep {
|
| public:
|
| explicit ColorTransformToLinear(ColorSpace::TransferID transfer,
|
| const SkColorSpaceTransferFn& fn,
|
| @@ -324,10 +359,6 @@ class ColorTransformToLinear : public ColorTransformInternal {
|
| transfer_ = ColorSpace::TransferID::LINEAR;
|
| }
|
|
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| - }
|
| -
|
| static bool IsGamma22(ColorSpace::TransferID transfer) {
|
| switch (transfer) {
|
| // We don't need to check BT709 here because it's been translated into
|
| @@ -341,9 +372,15 @@ class ColorTransformToLinear : public ColorTransformInternal {
|
| }
|
| }
|
|
|
| - bool Join(const ColorTransformFromLinear& next) override {
|
| - if (transfer_ == next.transfer_ ||
|
| - (IsGamma22(transfer_) && IsGamma22(next.transfer_))) {
|
| + bool Join(ColorTransformStep* next_untyped) override {
|
| + ColorTransformFromLinear* next = next_untyped->GetFromLinear();
|
| + if (!next)
|
| + return false;
|
| + // TODO(ccameron): Use SkTransferFnsApproximatelyCancel and
|
| + // SkTransferFnIsApproximatelyIdentity to merge parametric transfer
|
| + // functions.
|
| + if (transfer_ == next->transfer_ ||
|
| + (IsGamma22(transfer_) && IsGamma22(next->transfer_))) {
|
| transfer_ = ColorSpace::TransferID::LINEAR;
|
| return true;
|
| }
|
| @@ -358,7 +395,7 @@ class ColorTransformToLinear : public ColorTransformInternal {
|
| }
|
|
|
| ColorTransform::TriStim ClipToWhite(ColorTransform::TriStim& c) {
|
| - float maximum = std::max(std::max(c.x(), c.y()), c.z());
|
| + float maximum = max(max(c.x(), c.y()), c.z());
|
| if (maximum > 1.0f) {
|
| float l = Luma(c);
|
| c.Scale(1.0f / maximum);
|
| @@ -370,7 +407,7 @@ class ColorTransformToLinear : public ColorTransformInternal {
|
| return c;
|
| }
|
|
|
| - void transform(ColorTransform::TriStim* colors, size_t num) override {
|
| + void Transform(ColorTransform::TriStim* colors, size_t num) override {
|
| if (fn_valid_) {
|
| for (size_t i = 0; i < num; i++) {
|
| colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x()));
|
| @@ -420,13 +457,12 @@ class ColorTransformToLinear : public ColorTransformInternal {
|
| // Then we run the transfer function like normal, and finally
|
| // this class is inserted as an extra step which takes calculates
|
| // the U and V values.
|
| -class ColorTransformToBT2020CL : public ColorTransformInternal {
|
| +class ColorTransformToBT2020CL : public ColorTransformStep {
|
| public:
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| - }
|
| -
|
| - bool Join(const ColorTransformFromBT2020CL& next) override {
|
| + bool Join(ColorTransformStep* next_untyped) override {
|
| + ColorTransformFromBT2020CL* next = next_untyped->GetFromBT2020CL();
|
| + if (!next)
|
| + return false;
|
| if (null_)
|
| return false;
|
| null_ = true;
|
| @@ -435,7 +471,7 @@ class ColorTransformToBT2020CL : public ColorTransformInternal {
|
|
|
| bool IsNull() override { return null_; }
|
|
|
| - void transform(ColorTransform::TriStim* RYB, size_t num) override {
|
| + void Transform(ColorTransform::TriStim* RYB, size_t num) override {
|
| for (size_t i = 0; i < num; i++) {
|
| float U, V;
|
| float B_Y = RYB[i].z() - RYB[i].y();
|
| @@ -459,13 +495,12 @@ class ColorTransformToBT2020CL : public ColorTransformInternal {
|
| };
|
|
|
| // Inverse of ColorTransformToBT2020CL, see comment above for more info.
|
| -class ColorTransformFromBT2020CL : public ColorTransformInternal {
|
| +class ColorTransformFromBT2020CL : public ColorTransformStep {
|
| public:
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - return prev->Join(*this);
|
| - }
|
| -
|
| - bool Join(const ColorTransformToBT2020CL& next) override {
|
| + bool Join(ColorTransformStep* next_untyped) override {
|
| + ColorTransformToBT2020CL* next = next_untyped->GetToBT2020CL();
|
| + if (!next)
|
| + return false;
|
| if (null_)
|
| return false;
|
| null_ = true;
|
| @@ -474,7 +509,7 @@ class ColorTransformFromBT2020CL : public ColorTransformInternal {
|
|
|
| bool IsNull() override { return null_; }
|
|
|
| - void transform(ColorTransform::TriStim* YUV, size_t num) override {
|
| + void Transform(ColorTransform::TriStim* YUV, size_t num) override {
|
| if (null_)
|
| return;
|
| for (size_t i = 0; i < num; i++) {
|
| @@ -501,188 +536,133 @@ class ColorTransformFromBT2020CL : public ColorTransformInternal {
|
| bool null_ = false;
|
| };
|
|
|
| -class ChainColorTransform : public ColorTransform {
|
| - public:
|
| - ChainColorTransform(std::unique_ptr<ColorTransform> a,
|
| - std::unique_ptr<ColorTransform> b)
|
| - : a_(std::move(a)), b_(std::move(b)) {}
|
| -
|
| - private:
|
| - void transform(TriStim* colors, size_t num) override {
|
| - a_->transform(colors, num);
|
| - b_->transform(colors, num);
|
| - }
|
| - std::unique_ptr<ColorTransform> a_;
|
| - std::unique_ptr<ColorTransform> b_;
|
| -};
|
| +void ColorTransformInternal::AppendColorSpaceToColorSpaceTransform(
|
| + ColorSpace from,
|
| + const ColorSpace& to,
|
| + ColorTransform::Intent intent) {
|
| + if (intent == ColorTransform::Intent::INTENT_PERCEPTUAL) {
|
| + switch (from.transfer_) {
|
| + case ColorSpace::TransferID::UNSPECIFIED:
|
| + case ColorSpace::TransferID::BT709:
|
| + case ColorSpace::TransferID::SMPTE170M:
|
| + // SMPTE 1886 suggests that we should be using gamma 2.4 for BT709
|
| + // content. However, most displays actually use a gamma of 2.2, and
|
| + // user studies shows that users don't really care. Using the same
|
| + // gamma as the display will let us optimize a lot more, so lets stick
|
| + // with using the SRGB transfer function.
|
| + from.transfer_ = ColorSpace::TransferID::IEC61966_2_1;
|
| + break;
|
|
|
| -class TransformBuilder {
|
| - public:
|
| - void Append(std::unique_ptr<ColorTransformInternal> transform) {
|
| - if (!disable_optimizations_ && transform->IsNull())
|
| - return; // Null transform
|
| - transforms_.push_back(std::move(transform));
|
| - if (disable_optimizations_)
|
| - return;
|
| - while (transforms_.size() >= 2 &&
|
| - transforms_.back()->Prepend(
|
| - transforms_[transforms_.size() - 2].get())) {
|
| - transforms_.pop_back();
|
| - if (transforms_.back()->IsNull()) {
|
| - transforms_.pop_back();
|
| + case ColorSpace::TransferID::SMPTEST2084:
|
| + if (!to.IsHDR()) {
|
| + // We don't have an HDR display, so replace SMPTE 2084 with
|
| + // something that returns ranges more or less suitable for a normal
|
| + // display.
|
| + from.transfer_ = ColorSpace::TransferID::SMPTEST2084_NON_HDR;
|
| + }
|
| break;
|
| - }
|
| - }
|
| - }
|
|
|
| - std::unique_ptr<ColorTransform> GetTransform() {
|
| - if (transforms_.empty())
|
| - return base::MakeUnique<ColorTransformNull>();
|
| - std::unique_ptr<ColorTransform> ret(std::move(transforms_.back()));
|
| - transforms_.pop_back();
|
| + case ColorSpace::TransferID::ARIB_STD_B67:
|
| + if (!to.IsHDR()) {
|
| + // Interpreting HLG using a gamma 2.4 works reasonably well for SDR
|
| + // displays.
|
| + from.transfer_ = ColorSpace::TransferID::GAMMA24;
|
| + }
|
| + break;
|
|
|
| - while (!transforms_.empty()) {
|
| - ret = std::unique_ptr<ColorTransform>(new ChainColorTransform(
|
| - std::move(transforms_.back()), std::move(ret)));
|
| - transforms_.pop_back();
|
| + default: // Do nothing
|
| + break;
|
| }
|
|
|
| - return ret;
|
| - }
|
| -
|
| - void disable_optimizations() { disable_optimizations_ = true; }
|
| -
|
| - private:
|
| - bool disable_optimizations_ = false;
|
| - std::vector<std::unique_ptr<ColorTransformInternal>> transforms_;
|
| -};
|
| -
|
| -class ColorSpaceToColorSpaceTransform {
|
| - public:
|
| - static Transform GetPrimaryTransform(const ColorSpace& c) {
|
| - SkMatrix44 sk_matrix;
|
| - c.GetPrimaryMatrix(&sk_matrix);
|
| - return Transform(sk_matrix);
|
| + // TODO(hubbe): shrink gamuts here (never stretch gamuts)
|
| }
|
|
|
| - static void ColorSpaceToColorSpace(ColorSpace from,
|
| - ColorSpace to,
|
| - ColorTransform::Intent intent,
|
| - TransformBuilder* builder) {
|
| - if (intent == ColorTransform::Intent::INTENT_PERCEPTUAL) {
|
| - switch (from.transfer_) {
|
| - case ColorSpace::TransferID::UNSPECIFIED:
|
| - case ColorSpace::TransferID::BT709:
|
| - case ColorSpace::TransferID::SMPTE170M:
|
| - // SMPTE 1886 suggests that we should be using gamma 2.4 for BT709
|
| - // content. However, most displays actually use a gamma of 2.2, and
|
| - // user studies shows that users don't really care. Using the same
|
| - // gamma as the display will let us optimize a lot more, so lets stick
|
| - // with using the SRGB transfer function.
|
| - from.transfer_ = ColorSpace::TransferID::IEC61966_2_1;
|
| - break;
|
| -
|
| - case ColorSpace::TransferID::SMPTEST2084:
|
| - if (!to.IsHDR()) {
|
| - // We don't have an HDR display, so replace SMPTE 2084 with
|
| - // something that returns ranges more or less suitable for a normal
|
| - // display.
|
| - from.transfer_ = ColorSpace::TransferID::SMPTEST2084_NON_HDR;
|
| - }
|
| - break;
|
| -
|
| - case ColorSpace::TransferID::ARIB_STD_B67:
|
| - if (!to.IsHDR()) {
|
| - // Interpreting HLG using a gamma 2.4 works reasonably well for SDR
|
| - // displays.
|
| - from.transfer_ = ColorSpace::TransferID::GAMMA24;
|
| - }
|
| - break;
|
| -
|
| - default: // Do nothing
|
| - break;
|
| - }
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(GetRangeAdjustMatrix(from)));
|
|
|
| - // TODO(hubbe): shrink gamuts here (never stretch gamuts)
|
| - }
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(Invert(GetTransferMatrix(from))));
|
|
|
| - builder->Append(base::MakeUnique<ColorTransformMatrix>(
|
| - GetRangeAdjustMatrix(from)));
|
| -
|
| - builder->Append(base::MakeUnique<ColorTransformMatrix>(
|
| - Invert(GetTransferMatrix(from))));
|
| + SkColorSpaceTransferFn to_linear_fn;
|
| + bool to_linear_fn_valid = from.GetTransferFunction(&to_linear_fn);
|
| + steps_.push_back(base::MakeUnique<ColorTransformToLinear>(
|
| + from.transfer_, to_linear_fn, to_linear_fn_valid));
|
|
|
| - SkColorSpaceTransferFn to_linear_fn;
|
| - bool to_linear_fn_valid = from.GetTransferFunction(&to_linear_fn);
|
| - builder->Append(base::MakeUnique<ColorTransformToLinear>(
|
| - from.transfer_, to_linear_fn, to_linear_fn_valid));
|
| + if (from.matrix_ == ColorSpace::MatrixID::BT2020_CL) {
|
| + // BT2020 CL is a special case.
|
| + steps_.push_back(base::MakeUnique<ColorTransformFromBT2020CL>());
|
| + }
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(GetPrimaryTransform(from)));
|
|
|
| - if (from.matrix_ == ColorSpace::MatrixID::BT2020_CL) {
|
| - // BT2020 CL is a special case.
|
| - builder->Append(base::MakeUnique<ColorTransformFromBT2020CL>());
|
| - }
|
| - builder->Append(
|
| - base::MakeUnique<ColorTransformMatrix>(GetPrimaryTransform(from)));
|
| -
|
| - builder->Append(base::MakeUnique<ColorTransformMatrix>(
|
| - Invert(GetPrimaryTransform(to))));
|
| - if (to.matrix_ == ColorSpace::MatrixID::BT2020_CL) {
|
| - // BT2020 CL is a special case.
|
| - builder->Append(base::MakeUnique<ColorTransformToBT2020CL>());
|
| - }
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(Invert(GetPrimaryTransform(to))));
|
| + if (to.matrix_ == ColorSpace::MatrixID::BT2020_CL) {
|
| + // BT2020 CL is a special case.
|
| + steps_.push_back(base::MakeUnique<ColorTransformToBT2020CL>());
|
| + }
|
|
|
| - SkColorSpaceTransferFn from_linear_fn;
|
| - bool from_linear_fn_valid = to.GetInverseTransferFunction(&from_linear_fn);
|
| - builder->Append(base::MakeUnique<ColorTransformFromLinear>(
|
| - to.transfer_, from_linear_fn, from_linear_fn_valid));
|
| + SkColorSpaceTransferFn from_linear_fn;
|
| + bool from_linear_fn_valid = to.GetInverseTransferFunction(&from_linear_fn);
|
| + steps_.push_back(base::MakeUnique<ColorTransformFromLinear>(
|
| + to.transfer_, from_linear_fn, from_linear_fn_valid));
|
|
|
| - builder->Append(
|
| - base::MakeUnique<ColorTransformMatrix>(GetTransferMatrix(to)));
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(GetTransferMatrix(to)));
|
|
|
| - builder->Append(base::MakeUnique<ColorTransformMatrix>(
|
| - Invert(GetRangeAdjustMatrix(to))));
|
| - }
|
| -};
|
| + steps_.push_back(
|
| + base::MakeUnique<ColorTransformMatrix>(Invert(GetRangeAdjustMatrix(to))));
|
| +}
|
|
|
| -class QCMSColorTransform : public ColorTransformInternal {
|
| +class QCMSColorTransform : public ColorTransformStep {
|
| public:
|
| // Takes ownership of the profiles
|
| - QCMSColorTransform(qcms_profile* from, qcms_profile* to)
|
| - : from_(from), to_(to) {}
|
| - ~QCMSColorTransform() override {
|
| - qcms_profile_release(from_);
|
| - qcms_profile_release(to_);
|
| + QCMSColorTransform(ScopedQcmsProfile from, ScopedQcmsProfile to)
|
| + : from_(std::move(from)), to_(std::move(to)) {}
|
| + ~QCMSColorTransform() override {}
|
| + QCMSColorTransform* GetQCMS() override { return this; }
|
| + bool Join(ColorTransformStep* next_untyped) override {
|
| + QCMSColorTransform* next = next_untyped->GetQCMS();
|
| + if (!next)
|
| + return false;
|
| + if (qcms_profile_match(to_.get(), next->from_.get())) {
|
| + to_ = std::move(next->to_);
|
| + return true;
|
| + }
|
| + return false;
|
| }
|
| - bool Prepend(ColorTransformInternal* prev) override {
|
| - // Not currently optimizable.
|
| + bool IsNull() override {
|
| + if (qcms_profile_match(from_.get(), to_.get()))
|
| + return true;
|
| return false;
|
| }
|
| - bool IsNull() override { return from_ == to_; }
|
| - void transform(TriStim* colors, size_t num) override {
|
| - CHECK(sizeof(TriStim) == sizeof(float[3]));
|
| + void Transform(ColorTransform::TriStim* colors, size_t num) override {
|
| + CHECK(sizeof(ColorTransform::TriStim) == sizeof(float[3]));
|
| // QCMS doesn't like numbers outside 0..1
|
| for (size_t i = 0; i < num; i++) {
|
| - colors[i].set_x(fmin(1.0f, fmax(0.0f, colors[i].x())));
|
| - colors[i].set_y(fmin(1.0f, fmax(0.0f, colors[i].y())));
|
| - colors[i].set_z(fmin(1.0f, fmax(0.0f, colors[i].z())));
|
| + colors[i].set_x(min(1.0f, max(0.0f, colors[i].x())));
|
| + colors[i].set_y(min(1.0f, max(0.0f, colors[i].y())));
|
| + colors[i].set_z(min(1.0f, max(0.0f, colors[i].z())));
|
| }
|
| - qcms_chain_transform(from_, to_, reinterpret_cast<float*>(colors),
|
| + qcms_chain_transform(from_.get(), to_.get(),
|
| + reinterpret_cast<float*>(colors),
|
| reinterpret_cast<float*>(colors), num * 3);
|
| }
|
|
|
| private:
|
| - qcms_profile *from_, *to_;
|
| + ScopedQcmsProfile from_;
|
| + ScopedQcmsProfile to_;
|
| };
|
|
|
| -qcms_profile* GetQCMSProfileIfAvailable(const ColorSpace& color_space) {
|
| +ScopedQcmsProfile GetQCMSProfileIfAvailable(const ColorSpace& color_space) {
|
| ICCProfile icc_profile = ICCProfile::FromColorSpace(color_space);
|
| if (icc_profile.GetData().empty())
|
| return nullptr;
|
| - return qcms_profile_from_memory(icc_profile.GetData().data(),
|
| - icc_profile.GetData().size());
|
| + return ScopedQcmsProfile(qcms_profile_from_memory(
|
| + icc_profile.GetData().data(), icc_profile.GetData().size()));
|
| }
|
|
|
| -qcms_profile* GetXYZD50Profile() {
|
| +ScopedQcmsProfile GetXYZD50Profile() {
|
| // QCMS is trixy, it has a datatype called qcms_CIE_xyY, but what it expects
|
| // is in fact not xyY color coordinates, it just wants the x/y values of the
|
| // primaries with Y equal to 1.0.
|
| @@ -700,67 +680,78 @@ qcms_profile* GetXYZD50Profile() {
|
| w.x = 0.34567f;
|
| w.y = 0.35850f;
|
| w.Y = 1.0f;
|
| - return qcms_profile_create_rgb_with_gamma(w, xyz, 1.0f);
|
| + return ScopedQcmsProfile(qcms_profile_create_rgb_with_gamma(w, xyz, 1.0f));
|
| }
|
|
|
| -std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform(
|
| - const ColorSpace& from,
|
| - const ColorSpace& to,
|
| - Intent intent) {
|
| - TransformBuilder builder;
|
| - if (intent == Intent::TEST_NO_OPT) {
|
| - builder.disable_optimizations();
|
| - }
|
| -
|
| - qcms_profile* from_profile = GetQCMSProfileIfAvailable(from);
|
| - qcms_profile* to_profile = GetQCMSProfileIfAvailable(to);
|
| +ColorTransformInternal::ColorTransformInternal(const ColorSpace& from,
|
| + const ColorSpace& to,
|
| + Intent intent) {
|
| + ScopedQcmsProfile from_profile = GetQCMSProfileIfAvailable(from);
|
| + ScopedQcmsProfile to_profile = GetQCMSProfileIfAvailable(to);
|
| + bool has_from_profile = !!from_profile;
|
| + bool has_to_profile = !!to_profile;
|
|
|
| - if (from_profile && to_profile) {
|
| - return std::unique_ptr<ColorTransform>(
|
| - new QCMSColorTransform(from_profile, to_profile));
|
| - }
|
| if (from_profile) {
|
| - builder.Append(std::unique_ptr<ColorTransformInternal>(
|
| - new QCMSColorTransform(from_profile, GetXYZD50Profile())));
|
| + steps_.push_back(base::MakeUnique<QCMSColorTransform>(
|
| + std::move(from_profile), GetXYZD50Profile()));
|
| }
|
| - ColorSpaceToColorSpaceTransform::ColorSpaceToColorSpace(
|
| - from_profile ? ColorSpace::CreateXYZD50() : from,
|
| - to_profile ? ColorSpace::CreateXYZD50() : to, intent, &builder);
|
| +
|
| + AppendColorSpaceToColorSpaceTransform(
|
| + has_from_profile ? ColorSpace::CreateXYZD50() : from,
|
| + has_to_profile ? ColorSpace::CreateXYZD50() : to, intent);
|
| +
|
| if (to_profile) {
|
| - builder.Append(std::unique_ptr<ColorTransformInternal>(
|
| - new QCMSColorTransform(GetXYZD50Profile(), to_profile)));
|
| + steps_.push_back(base::MakeUnique<QCMSColorTransform>(
|
| + GetXYZD50Profile(), std::move(to_profile)));
|
| }
|
|
|
| - return builder.GetTransform();
|
| + if (intent != Intent::TEST_NO_OPT)
|
| + Simplify();
|
| }
|
|
|
| -// static
|
| -float ColorTransform::ToLinearForTesting(ColorSpace::TransferID transfer,
|
| - float v) {
|
| - ColorSpace space(ColorSpace::PrimaryID::BT709, transfer,
|
| - ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL);
|
| - SkColorSpaceTransferFn to_linear_fn;
|
| - bool to_linear_fn_valid = space.GetTransferFunction(&to_linear_fn);
|
| - ColorTransformToLinear to_linear_transform(transfer, to_linear_fn,
|
| - to_linear_fn_valid);
|
| - TriStim color(v, v, v);
|
| - to_linear_transform.transform(&color, 1);
|
| - return color.x();
|
| +ColorTransformInternal::~ColorTransformInternal() {}
|
| +
|
| +void ColorTransformInternal::Simplify() {
|
| + for (auto iter = steps_.begin(); iter != steps_.end();) {
|
| + std::unique_ptr<ColorTransformStep>& this_step = *iter;
|
| +
|
| + // Try to Join |next_step| into |this_step|. If successful, re-visit the
|
| + // step before |this_step|.
|
| + auto iter_next = iter;
|
| + iter_next++;
|
| + if (iter_next != steps_.end()) {
|
| + std::unique_ptr<ColorTransformStep>& next_step = *iter_next;
|
| + if (this_step->Join(next_step.get())) {
|
| + steps_.erase(iter_next);
|
| + if (iter != steps_.begin())
|
| + --iter;
|
| + continue;
|
| + }
|
| + }
|
| +
|
| + // If |this_step| step is a no-op, remove it, and re-visit the step before
|
| + // |this_step|.
|
| + if (this_step->IsNull()) {
|
| + iter = steps_.erase(iter);
|
| + if (iter != steps_.begin())
|
| + --iter;
|
| + continue;
|
| + }
|
| +
|
| + ++iter;
|
| + }
|
| }
|
|
|
| // static
|
| -float ColorTransform::FromLinearForTesting(ColorSpace::TransferID transfer,
|
| - float v) {
|
| - ColorSpace space(ColorSpace::PrimaryID::BT709, transfer,
|
| - ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL);
|
| - SkColorSpaceTransferFn from_linear_fn;
|
| - bool from_linear_fn_valid = space.GetInverseTransferFunction(&from_linear_fn);
|
| -
|
| - ColorTransformFromLinear from_linear_transform(transfer, from_linear_fn,
|
| - from_linear_fn_valid);
|
| - TriStim color(v, v, v);
|
| - from_linear_transform.transform(&color, 1);
|
| - return color.x();
|
| +std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform(
|
| + const ColorSpace& from,
|
| + const ColorSpace& to,
|
| + Intent intent) {
|
| + return std::unique_ptr<ColorTransform>(
|
| + new ColorTransformInternal(from, to, intent));
|
| }
|
|
|
| +ColorTransform::ColorTransform() {}
|
| +ColorTransform::~ColorTransform() {}
|
| +
|
| } // namespace gfx
|
|
|