Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2016 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "ui/gfx/color_transform.h" | 5 #include "ui/gfx/color_transform.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <vector> | 8 #include <cmath> |
| 9 #include <list> | |
| 10 #include <memory> | |
| 9 | 11 |
| 10 #include "base/logging.h" | 12 #include "base/logging.h" |
| 11 #include "base/memory/ptr_util.h" | 13 #include "base/memory/ptr_util.h" |
| 14 #include "third_party/qcms/src/qcms.h" | |
| 12 #include "ui/gfx/color_space.h" | 15 #include "ui/gfx/color_space.h" |
| 13 #include "ui/gfx/icc_profile.h" | 16 #include "ui/gfx/icc_profile.h" |
| 17 #include "ui/gfx/skia_color_space_util.h" | |
| 14 #include "ui/gfx/transform.h" | 18 #include "ui/gfx/transform.h" |
| 15 #include "third_party/qcms/src/qcms.h" | |
| 16 | 19 |
| 17 #ifndef THIS_MUST_BE_INCLUDED_AFTER_QCMS_H | 20 #ifndef THIS_MUST_BE_INCLUDED_AFTER_QCMS_H |
| 18 extern "C" { | 21 extern "C" { |
| 19 #include "third_party/qcms/src/chain.h" | 22 #include "third_party/qcms/src/chain.h" |
| 20 }; | 23 }; |
| 21 #endif | 24 #endif |
| 22 | 25 |
| 23 namespace gfx { | 26 namespace gfx { |
| 24 | 27 |
| 25 float EvalSkTransferFn(const SkColorSpaceTransferFn& fn, float x) { | 28 namespace { |
| 26 if (x < 0) | 29 |
| 27 return 0; | 30 // Helper for scoped QCMS profiles. |
| 28 if (x < fn.fD) | 31 struct QcmsProfileDeleter { |
| 29 return fn.fC * x + fn.fF; | 32 void operator()(qcms_profile* p) { |
| 30 return powf(fn.fA * x + fn.fB, fn.fG) + fn.fE; | 33 if (p) { |
| 31 } | 34 qcms_profile_release(p); |
| 35 } | |
| 36 } | |
| 37 }; | |
| 38 using ScopedQcmsProfile = std::unique_ptr<qcms_profile, QcmsProfileDeleter>; | |
| 32 | 39 |
| 33 Transform Invert(const Transform& t) { | 40 Transform Invert(const Transform& t) { |
| 34 Transform ret = t; | 41 Transform ret = t; |
| 35 if (!t.GetInverse(&ret)) { | 42 if (!t.GetInverse(&ret)) { |
| 36 LOG(ERROR) << "Inverse should alsways be possible."; | 43 LOG(ERROR) << "Inverse should alsways be possible."; |
| 37 } | 44 } |
| 38 return ret; | 45 return ret; |
| 39 } | 46 } |
| 40 | 47 |
| 41 float FromLinear(ColorSpace::TransferID id, float v) { | 48 float FromLinear(ColorSpace::TransferID id, float v) { |
| 42 switch (id) { | 49 switch (id) { |
| 43 case ColorSpace::TransferID::SMPTEST2084_NON_HDR: | 50 case ColorSpace::TransferID::SMPTEST2084_NON_HDR: |
| 44 // Should already be handled. | 51 // Should already be handled. |
| 45 break; | 52 break; |
| 46 | 53 |
| 47 case ColorSpace::TransferID::LOG: | 54 case ColorSpace::TransferID::LOG: |
| 48 if (v < 0.01f) | 55 if (v < 0.01f) |
| 49 return 0.0f; | 56 return 0.0f; |
| 50 return 1.0f + log(v) / log(10.0f) / 2.0f; | 57 return 1.0f + std::log(v) / std::log(10.0f) / 2.0f; |
| 51 | 58 |
| 52 case ColorSpace::TransferID::LOG_SQRT: | 59 case ColorSpace::TransferID::LOG_SQRT: |
| 53 if (v < sqrt(10.0f) / 1000.0f) | 60 if (v < std::sqrt(10.0f) / 1000.0f) |
| 54 return 0.0f; | 61 return 0.0f; |
| 55 return 1.0f + log(v) / log(10.0f) / 2.5f; | 62 return 1.0f + std::log(v) / std::log(10.0f) / 2.5f; |
| 56 | 63 |
| 57 case ColorSpace::TransferID::IEC61966_2_4: { | 64 case ColorSpace::TransferID::IEC61966_2_4: { |
| 58 float a = 1.099296826809442f; | 65 float a = 1.099296826809442f; |
| 59 float b = 0.018053968510807f; | 66 float b = 0.018053968510807f; |
| 60 if (v < -b) { | 67 if (v < -b) { |
| 61 return -a * powf(-v, 0.45f) + (a - 1.0f); | 68 return -a * std::pow(-v, 0.45f) + (a - 1.0f); |
| 62 } else if (v <= b) { | 69 } else if (v <= b) { |
| 63 return 4.5f * v; | 70 return 4.5f * v; |
| 64 } else { | 71 } else { |
| 65 return a * powf(v, 0.45f) - (a - 1.0f); | 72 return a * std::pow(v, 0.45f) - (a - 1.0f); |
| 66 } | 73 } |
| 67 } | 74 } |
| 68 | 75 |
| 69 case ColorSpace::TransferID::BT1361_ECG: { | 76 case ColorSpace::TransferID::BT1361_ECG: { |
| 70 float a = 1.099f; | 77 float a = 1.099f; |
| 71 float b = 0.018f; | 78 float b = 0.018f; |
| 72 float l = 0.0045f; | 79 float l = 0.0045f; |
| 73 if (v < -l) { | 80 if (v < -l) { |
| 74 return -(a * powf(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f; | 81 return -(a * std::pow(-4.0f * v, 0.45f) + (a - 1.0f)) / 4.0f; |
| 75 } else if (v <= b) { | 82 } else if (v <= b) { |
| 76 return 4.5f * v; | 83 return 4.5f * v; |
| 77 } else { | 84 } else { |
| 78 return a * powf(v, 0.45f) - (a - 1.0f); | 85 return a * std::pow(v, 0.45f) - (a - 1.0f); |
| 79 } | 86 } |
| 80 } | 87 } |
| 81 | 88 |
| 82 case ColorSpace::TransferID::SMPTEST2084: { | 89 case ColorSpace::TransferID::SMPTEST2084: { |
| 83 // Go from scRGB levels to 0-1. | 90 // Go from scRGB levels to 0-1. |
| 84 v *= 80.0f / 10000.0f; | 91 v *= 80.0f / 10000.0f; |
| 85 v = fmax(0.0f, v); | 92 v = std::max(0.0f, v); |
| 86 float m1 = (2610.0f / 4096.0f) / 4.0f; | 93 float m1 = (2610.0f / 4096.0f) / 4.0f; |
| 87 float m2 = (2523.0f / 4096.0f) * 128.0f; | 94 float m2 = (2523.0f / 4096.0f) * 128.0f; |
| 88 float c1 = 3424.0f / 4096.0f; | 95 float c1 = 3424.0f / 4096.0f; |
| 89 float c2 = (2413.0f / 4096.0f) * 32.0f; | 96 float c2 = (2413.0f / 4096.0f) * 32.0f; |
| 90 float c3 = (2392.0f / 4096.0f) * 32.0f; | 97 float c3 = (2392.0f / 4096.0f) * 32.0f; |
| 91 return powf((c1 + c2 * powf(v, m1)) / (1.0f + c3 * powf(v, m1)), m2); | 98 return std::pow( |
| 99 (c1 + c2 * std::pow(v, m1)) / (1.0f + c3 * std::pow(v, m1)), m2); | |
| 92 } | 100 } |
| 93 | 101 |
| 94 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf | 102 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf |
| 95 case ColorSpace::TransferID::ARIB_STD_B67: { | 103 case ColorSpace::TransferID::ARIB_STD_B67: { |
| 96 const float a = 0.17883277f; | 104 const float a = 0.17883277f; |
| 97 const float b = 0.28466892f; | 105 const float b = 0.28466892f; |
| 98 const float c = 0.55991073f; | 106 const float c = 0.55991073f; |
| 99 v = fmax(0.0f, v); | 107 v = std::max(0.0f, v); |
| 100 if (v <= 1) | 108 if (v <= 1) |
| 101 return 0.5f * sqrtf(v); | 109 return 0.5f * sqrtf(v); |
| 102 else | 110 else |
| 103 return a * log(v - b) + c; | 111 return a * std::log(v - b) + c; |
| 104 } | 112 } |
| 105 | 113 |
| 106 default: | 114 default: |
| 107 // Handled by SkColorSpaceTransferFn. | 115 // Handled by SkColorSpaceTransferFn. |
| 108 break; | 116 break; |
| 109 } | 117 } |
| 110 NOTREACHED(); | 118 NOTREACHED(); |
| 111 return 0; | 119 return 0; |
| 112 } | 120 } |
| 113 | 121 |
| 114 float ToLinear(ColorSpace::TransferID id, float v) { | 122 float ToLinear(ColorSpace::TransferID id, float v) { |
| 115 switch (id) { | 123 switch (id) { |
| 116 case ColorSpace::TransferID::LOG: | 124 case ColorSpace::TransferID::LOG: |
| 117 if (v < 0.0f) | 125 if (v < 0.0f) |
| 118 return 0.0f; | 126 return 0.0f; |
| 119 return powf(10.0f, (v - 1.0f) * 2.0f); | 127 return std::pow(10.0f, (v - 1.0f) * 2.0f); |
| 120 | 128 |
| 121 case ColorSpace::TransferID::LOG_SQRT: | 129 case ColorSpace::TransferID::LOG_SQRT: |
| 122 if (v < 0.0f) | 130 if (v < 0.0f) |
| 123 return 0.0f; | 131 return 0.0f; |
| 124 return powf(10.0f, (v - 1.0f) * 2.5f); | 132 return std::pow(10.0f, (v - 1.0f) * 2.5f); |
| 125 | 133 |
| 126 case ColorSpace::TransferID::IEC61966_2_4: { | 134 case ColorSpace::TransferID::IEC61966_2_4: { |
| 127 float a = 1.099296826809442f; | 135 float a = 1.099296826809442f; |
| 128 float b = 0.018053968510807f; | 136 float b = 0.018053968510807f; |
| 129 if (v < FromLinear(ColorSpace::TransferID::IEC61966_2_4, -a)) { | 137 if (v < FromLinear(ColorSpace::TransferID::IEC61966_2_4, -a)) { |
| 130 return -powf((a - 1.0f - v) / a, 1.0f / 0.45f); | 138 return -std::pow((a - 1.0f - v) / a, 1.0f / 0.45f); |
| 131 } else if (v <= FromLinear(ColorSpace::TransferID::IEC61966_2_4, b)) { | 139 } else if (v <= FromLinear(ColorSpace::TransferID::IEC61966_2_4, b)) { |
| 132 return v / 4.5f; | 140 return v / 4.5f; |
| 133 } else { | 141 } else { |
| 134 return powf((v + a - 1.0f) / a, 1.0f / 0.45f); | 142 return std::pow((v + a - 1.0f) / a, 1.0f / 0.45f); |
| 135 } | 143 } |
| 136 } | 144 } |
| 137 | 145 |
| 138 case ColorSpace::TransferID::BT1361_ECG: { | 146 case ColorSpace::TransferID::BT1361_ECG: { |
| 139 float a = 1.099f; | 147 float a = 1.099f; |
| 140 float b = 0.018f; | 148 float b = 0.018f; |
| 141 float l = 0.0045f; | 149 float l = 0.0045f; |
| 142 if (v < FromLinear(ColorSpace::TransferID::BT1361_ECG, -l)) { | 150 if (v < FromLinear(ColorSpace::TransferID::BT1361_ECG, -l)) { |
| 143 return -powf((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f; | 151 return -std::pow((1.0f - a - v * 4.0f) / a, 1.0f / 0.45f) / 4.0f; |
| 144 } else if (v <= FromLinear(ColorSpace::TransferID::BT1361_ECG, b)) { | 152 } else if (v <= FromLinear(ColorSpace::TransferID::BT1361_ECG, b)) { |
| 145 return v / 4.5f; | 153 return v / 4.5f; |
| 146 } else { | 154 } else { |
| 147 return powf((v + a - 1.0f) / a, 1.0f / 0.45f); | 155 return std::pow((v + a - 1.0f) / a, 1.0f / 0.45f); |
| 148 } | 156 } |
| 149 } | 157 } |
| 150 | 158 |
| 151 case ColorSpace::TransferID::SMPTEST2084: { | 159 case ColorSpace::TransferID::SMPTEST2084: { |
| 152 v = fmax(0.0f, v); | 160 v = std::max(0.0f, v); |
| 153 float m1 = (2610.0f / 4096.0f) / 4.0f; | 161 float m1 = (2610.0f / 4096.0f) / 4.0f; |
| 154 float m2 = (2523.0f / 4096.0f) * 128.0f; | 162 float m2 = (2523.0f / 4096.0f) * 128.0f; |
| 155 float c1 = 3424.0f / 4096.0f; | 163 float c1 = 3424.0f / 4096.0f; |
| 156 float c2 = (2413.0f / 4096.0f) * 32.0f; | 164 float c2 = (2413.0f / 4096.0f) * 32.0f; |
| 157 float c3 = (2392.0f / 4096.0f) * 32.0f; | 165 float c3 = (2392.0f / 4096.0f) * 32.0f; |
| 158 v = powf( | 166 v = std::pow(std::max(std::pow(v, 1.0f / m2) - c1, 0.0f) / |
| 159 fmax(powf(v, 1.0f / m2) - c1, 0) / (c2 - c3 * powf(v, 1.0f / m2)), | 167 (c2 - c3 * std::pow(v, 1.0f / m2)), |
| 160 1.0f / m1); | 168 1.0f / m1); |
| 161 // This matches the scRGB definition that 1.0 means 80 nits. | 169 // This matches the scRGB definition that 1.0 means 80 nits. |
| 162 // TODO(hubbe): It would be *nice* if 1.0 meant more than that, but | 170 // TODO(hubbe): It would be *nice* if 1.0 meant more than that, but |
| 163 // that might be difficult to do right now. | 171 // that might be difficult to do right now. |
| 164 v *= 10000.0f / 80.0f; | 172 v *= 10000.0f / 80.0f; |
| 165 return v; | 173 return v; |
| 166 } | 174 } |
| 167 | 175 |
| 168 case ColorSpace::TransferID::SMPTEST2084_NON_HDR: | 176 case ColorSpace::TransferID::SMPTEST2084_NON_HDR: |
| 169 v = fmax(0.0f, v); | 177 v = std::max(0.0f, v); |
| 170 return fmin(2.3f * pow(v, 2.8f), v / 5.0f + 0.8f); | 178 return std::min(2.3f * pow(v, 2.8f), v / 5.0f + 0.8f); |
| 171 | 179 |
| 172 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf | 180 // Spec: http://www.arib.or.jp/english/html/overview/doc/2-STD-B67v1_0.pdf |
| 173 case ColorSpace::TransferID::ARIB_STD_B67: { | 181 case ColorSpace::TransferID::ARIB_STD_B67: { |
| 174 v = fmax(0.0f, v); | 182 v = std::max(0.0f, v); |
| 175 const float a = 0.17883277f; | 183 const float a = 0.17883277f; |
| 176 const float b = 0.28466892f; | 184 const float b = 0.28466892f; |
| 177 const float c = 0.55991073f; | 185 const float c = 0.55991073f; |
| 178 float v_ = 0.0f; | 186 float v_ = 0.0f; |
| 179 if (v <= 0.5f) { | 187 if (v <= 0.5f) { |
| 180 v_ = (v * 2.0f) * (v * 2.0f); | 188 v_ = (v * 2.0f) * (v * 2.0f); |
| 181 } else { | 189 } else { |
| 182 v_ = exp((v - c) / a) + b; | 190 v_ = std::exp((v - c) / a) + b; |
| 183 } | 191 } |
| 184 return v_; | 192 return v_; |
| 185 } | 193 } |
| 186 | 194 |
| 187 default: | 195 default: |
| 188 // Handled by SkColorSpaceTransferFn. | 196 // Handled by SkColorSpaceTransferFn. |
| 189 break; | 197 break; |
| 190 } | 198 } |
| 191 NOTREACHED(); | 199 NOTREACHED(); |
| 192 return 0; | 200 return 0; |
| 193 } | 201 } |
| 194 | 202 |
| 195 Transform GetTransferMatrix(const gfx::ColorSpace& color_space) { | 203 Transform GetTransferMatrix(const gfx::ColorSpace& color_space) { |
| 196 SkMatrix44 transfer_matrix; | 204 SkMatrix44 transfer_matrix; |
| 197 color_space.GetTransferMatrix(&transfer_matrix); | 205 color_space.GetTransferMatrix(&transfer_matrix); |
| 198 return Transform(transfer_matrix); | 206 return Transform(transfer_matrix); |
| 199 } | 207 } |
| 200 | 208 |
| 201 Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space) { | 209 Transform GetRangeAdjustMatrix(const gfx::ColorSpace& color_space) { |
| 202 SkMatrix44 range_adjust_matrix; | 210 SkMatrix44 range_adjust_matrix; |
| 203 color_space.GetRangeAdjustMatrix(&range_adjust_matrix); | 211 color_space.GetRangeAdjustMatrix(&range_adjust_matrix); |
| 204 return Transform(range_adjust_matrix); | 212 return Transform(range_adjust_matrix); |
| 205 } | 213 } |
| 206 | 214 |
| 215 Transform GetPrimaryTransform(const gfx::ColorSpace& color_space) { | |
| 216 SkMatrix44 primary_matrix; | |
| 217 color_space.GetPrimaryMatrix(&primary_matrix); | |
| 218 return Transform(primary_matrix); | |
| 219 } | |
| 220 | |
| 221 } // namespace | |
| 222 | |
| 207 class ColorTransformMatrix; | 223 class ColorTransformMatrix; |
| 208 class ColorTransformToLinear; | |
| 209 class ColorTransformFromLinear; | 224 class ColorTransformFromLinear; |
| 210 class ColorTransformToBT2020CL; | 225 class ColorTransformToBT2020CL; |
| 211 class ColorTransformFromBT2020CL; | 226 class ColorTransformFromBT2020CL; |
| 212 class ColorTransformNull; | 227 class ColorTransformNull; |
| 228 class QCMSColorTransform; | |
| 213 | 229 |
| 214 class ColorTransformInternal : public ColorTransform { | 230 class ColorTransformStep { |
| 215 public: | 231 public: |
| 216 // Visitor pattern, Prepend() calls return prev->Join(this). | 232 virtual ~ColorTransformStep() {} |
| 217 virtual bool Prepend(ColorTransformInternal* prev) = 0; | 233 virtual ColorTransformFromLinear* GetFromLinear() { return nullptr; } |
|
hubbe
2017/02/14 19:04:51
When I first saw this, I thought:
Why'd you go and
ccameron
2017/02/15 00:11:31
Sure -- updated the CL description (after skimming
| |
| 234 virtual ColorTransformToBT2020CL* GetToBT2020CL() { return nullptr; } | |
| 235 virtual ColorTransformFromBT2020CL* GetFromBT2020CL() { return nullptr; } | |
| 236 virtual ColorTransformMatrix* GetMatrix() { return nullptr; } | |
| 237 virtual ColorTransformNull* GetNull() { return nullptr; } | |
| 238 virtual QCMSColorTransform* GetQCMS() { return nullptr; } | |
| 218 | 239 |
| 219 // Join methods, returns true if the |next| transform was successfully | 240 // Join methods, returns true if the |next| transform was successfully |
| 220 // assimilated into |this|. | 241 // assimilated into |this|. |
| 221 // If Join() returns true, |next| is no longer needed and can be deleted. | 242 // If Join() returns true, |next| is no longer needed and can be deleted. |
| 222 virtual bool Join(const ColorTransformToLinear& next) { return false; } | 243 virtual bool Join(ColorTransformStep* next) { return false; } |
| 223 virtual bool Join(const ColorTransformFromLinear& next) { return false; } | |
| 224 virtual bool Join(const ColorTransformToBT2020CL& next) { return false; } | |
| 225 virtual bool Join(const ColorTransformFromBT2020CL& next) { return false; } | |
| 226 virtual bool Join(const ColorTransformMatrix& next) { return false; } | |
| 227 virtual bool Join(const ColorTransformNull& next) { return true; } | |
| 228 | 244 |
| 229 // Return true if this is a null transform. | 245 // Return true if this is a null transform. |
| 230 virtual bool IsNull() { return false; } | 246 virtual bool IsNull() { return false; } |
| 247 | |
| 248 virtual void Transform(ColorTransform::TriStim* color, size_t num) = 0; | |
| 231 }; | 249 }; |
| 232 | 250 |
| 233 class ColorTransformNull : public ColorTransformInternal { | 251 typedef std::list<std::unique_ptr<ColorTransformStep>> ColorTransformStepList; |
| 252 | |
| 253 class GFX_EXPORT ColorTransformInternal : public ColorTransform { | |
| 234 public: | 254 public: |
| 235 bool Prepend(ColorTransformInternal* prev) override { | 255 ColorTransformInternal(ColorTransformStepList steps) |
| 236 return prev->Join(*this); | 256 : steps_(std::move(steps)) {} |
| 257 ~ColorTransformInternal() override {} | |
| 258 | |
| 259 // Perform transformation of colors, |colors| is both input and output. | |
| 260 void Transform(TriStim* colors, size_t num) override { | |
| 261 for (const auto& step : steps_) | |
| 262 step->Transform(colors, num); | |
| 237 } | 263 } |
| 238 bool IsNull() override { return true; } | 264 |
| 239 void transform(ColorTransform::TriStim* color, size_t num) override {} | 265 size_t NumberOfStepsForTesting() const override { return steps_.size(); } |
| 266 | |
| 267 static void Append(ColorSpace from, | |
|
hubbe
2017/02/14 19:04:51
How about making this non-static and have it appen
ccameron
2017/02/15 00:11:31
That makes sense -- done.
| |
| 268 const ColorSpace& to, | |
| 269 ColorTransform::Intent intent, | |
| 270 ColorTransformStepList* builder); | |
| 271 | |
| 272 static void Simplify(ColorTransformStepList* steps); | |
|
hubbe
2017/02/14 19:04:51
Make non-static and operate on steps_ ?
ccameron
2017/02/15 00:11:31
(same deal).
| |
| 273 | |
| 274 private: | |
| 275 ColorTransformStepList steps_; | |
| 240 }; | 276 }; |
| 241 | 277 |
| 242 class ColorTransformMatrix : public ColorTransformInternal { | 278 class ColorTransformNull : public ColorTransformStep { |
| 243 public: | 279 public: |
| 244 explicit ColorTransformMatrix(const Transform& matrix) : matrix_(matrix) {} | 280 ColorTransformNull* GetNull() override { return this; } |
| 281 bool IsNull() override { return true; } | |
| 282 void Transform(ColorTransform::TriStim* color, size_t num) override {} | |
| 283 }; | |
| 245 | 284 |
| 246 bool Prepend(ColorTransformInternal* prev) override { | 285 class ColorTransformMatrix : public ColorTransformStep { |
| 247 return prev->Join(*this); | 286 public: |
| 248 } | 287 explicit ColorTransformMatrix(const class Transform& matrix) |
| 249 | 288 : matrix_(matrix) {} |
| 250 bool Join(const ColorTransformMatrix& next) override { | 289 ColorTransformMatrix* GetMatrix() override { return this; } |
| 251 Transform tmp = next.matrix_; | 290 bool Join(ColorTransformStep* next_untyped) override { |
| 291 ColorTransformMatrix* next = next_untyped->GetMatrix(); | |
| 292 if (!next) | |
| 293 return false; | |
| 294 class Transform tmp = next->matrix_; | |
| 252 tmp *= matrix_; | 295 tmp *= matrix_; |
| 253 matrix_ = tmp; | 296 matrix_ = tmp; |
| 254 return true; | 297 return true; |
| 255 } | 298 } |
| 256 | 299 |
| 257 bool IsNull() override { | 300 bool IsNull() override { |
| 258 // Returns true if we're very close to an identity matrix. | 301 return SkMatrixIsApproximatelyIdentity(matrix_.matrix()); |
| 259 for (int i = 0; i < 4; i++) { | |
| 260 for (int j = 0; j < 4; j++) { | |
| 261 float expected = i == j ? 1.0f : 0.0f; | |
| 262 if (fabs(matrix_.matrix().get(i, j) - expected) > 0.00001f) { | |
| 263 return false; | |
| 264 } | |
| 265 } | |
| 266 } | |
| 267 return true; | |
| 268 } | 302 } |
| 269 | 303 |
| 270 void transform(ColorTransform::TriStim* colors, size_t num) override { | 304 void Transform(ColorTransform::TriStim* colors, size_t num) override { |
| 271 for (size_t i = 0; i < num; i++) | 305 for (size_t i = 0; i < num; i++) |
| 272 matrix_.TransformPoint(colors + i); | 306 matrix_.TransformPoint(colors + i); |
| 273 } | 307 } |
| 274 | 308 |
| 275 private: | 309 private: |
| 276 Transform matrix_; | 310 class Transform matrix_; |
| 277 }; | 311 }; |
| 278 | 312 |
| 279 class ColorTransformFromLinear : public ColorTransformInternal { | 313 class ColorTransformFromLinear : public ColorTransformStep { |
| 280 public: | 314 public: |
| 281 explicit ColorTransformFromLinear(ColorSpace::TransferID transfer, | 315 explicit ColorTransformFromLinear(ColorSpace::TransferID transfer, |
| 282 const SkColorSpaceTransferFn& fn, | 316 const SkColorSpaceTransferFn& fn, |
| 283 bool fn_valid) | 317 bool fn_valid) |
| 284 : transfer_(transfer), fn_(fn), fn_valid_(fn_valid) { | 318 : transfer_(transfer), fn_(fn), fn_valid_(fn_valid) { |
| 285 if (transfer_ == ColorSpace::TransferID::LINEAR_HDR) | 319 if (transfer_ == ColorSpace::TransferID::LINEAR_HDR) |
| 286 transfer_ = ColorSpace::TransferID::LINEAR; | 320 transfer_ = ColorSpace::TransferID::LINEAR; |
| 287 } | 321 } |
| 288 bool Prepend(ColorTransformInternal* prev) override { | 322 ColorTransformFromLinear* GetFromLinear() override { return this; } |
| 289 return prev->Join(*this); | |
| 290 } | |
| 291 | |
| 292 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } | 323 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } |
| 293 | 324 void Transform(ColorTransform::TriStim* colors, size_t num) override { |
| 294 void transform(ColorTransform::TriStim* colors, size_t num) override { | |
| 295 if (fn_valid_) { | 325 if (fn_valid_) { |
| 296 for (size_t i = 0; i < num; i++) { | 326 for (size_t i = 0; i < num; i++) { |
| 297 colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x())); | 327 colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x())); |
| 298 colors[i].set_y(EvalSkTransferFn(fn_, colors[i].y())); | 328 colors[i].set_y(EvalSkTransferFn(fn_, colors[i].y())); |
| 299 colors[i].set_z(EvalSkTransferFn(fn_, colors[i].z())); | 329 colors[i].set_z(EvalSkTransferFn(fn_, colors[i].z())); |
| 300 } | 330 } |
| 301 } else { | 331 } else { |
| 302 for (size_t i = 0; i < num; i++) { | 332 for (size_t i = 0; i < num; i++) { |
| 303 colors[i].set_x(FromLinear(transfer_, colors[i].x())); | 333 colors[i].set_x(FromLinear(transfer_, colors[i].x())); |
| 304 colors[i].set_y(FromLinear(transfer_, colors[i].y())); | 334 colors[i].set_y(FromLinear(transfer_, colors[i].y())); |
| 305 colors[i].set_z(FromLinear(transfer_, colors[i].z())); | 335 colors[i].set_z(FromLinear(transfer_, colors[i].z())); |
| 306 } | 336 } |
| 307 } | 337 } |
| 308 } | 338 } |
| 309 | 339 |
| 310 private: | 340 private: |
| 311 friend class ColorTransformToLinear; | 341 friend class ColorTransformToLinear; |
| 312 ColorSpace::TransferID transfer_; | 342 ColorSpace::TransferID transfer_; |
| 313 SkColorSpaceTransferFn fn_; | 343 SkColorSpaceTransferFn fn_; |
| 314 bool fn_valid_ = false; | 344 bool fn_valid_ = false; |
| 315 }; | 345 }; |
| 316 | 346 |
| 317 class ColorTransformToLinear : public ColorTransformInternal { | 347 class ColorTransformToLinear : public ColorTransformStep { |
| 318 public: | 348 public: |
| 319 explicit ColorTransformToLinear(ColorSpace::TransferID transfer, | 349 explicit ColorTransformToLinear(ColorSpace::TransferID transfer, |
| 320 const SkColorSpaceTransferFn& fn, | 350 const SkColorSpaceTransferFn& fn, |
| 321 bool fn_valid) | 351 bool fn_valid) |
| 322 : transfer_(transfer), fn_(fn), fn_valid_(fn_valid) { | 352 : transfer_(transfer), fn_(fn), fn_valid_(fn_valid) { |
| 323 if (transfer_ == ColorSpace::TransferID::LINEAR_HDR) | 353 if (transfer_ == ColorSpace::TransferID::LINEAR_HDR) |
| 324 transfer_ = ColorSpace::TransferID::LINEAR; | 354 transfer_ = ColorSpace::TransferID::LINEAR; |
| 325 } | 355 } |
| 326 | 356 |
| 327 bool Prepend(ColorTransformInternal* prev) override { | |
| 328 return prev->Join(*this); | |
| 329 } | |
| 330 | |
| 331 static bool IsGamma22(ColorSpace::TransferID transfer) { | 357 static bool IsGamma22(ColorSpace::TransferID transfer) { |
| 332 switch (transfer) { | 358 switch (transfer) { |
| 333 // We don't need to check BT709 here because it's been translated into | 359 // We don't need to check BT709 here because it's been translated into |
| 334 // SRGB in ColorSpaceToColorSpaceTransform::ColorSpaceToLinear below. | 360 // SRGB in ColorSpaceToColorSpaceTransform::ColorSpaceToLinear below. |
| 335 case ColorSpace::TransferID::GAMMA22: | 361 case ColorSpace::TransferID::GAMMA22: |
| 336 case ColorSpace::TransferID::IEC61966_2_1: // SRGB | 362 case ColorSpace::TransferID::IEC61966_2_1: // SRGB |
| 337 return true; | 363 return true; |
| 338 | 364 |
| 339 default: | 365 default: |
| 340 return false; | 366 return false; |
| 341 } | 367 } |
| 342 } | 368 } |
| 343 | 369 |
| 344 bool Join(const ColorTransformFromLinear& next) override { | 370 bool Join(ColorTransformStep* next_untyped) override { |
| 345 if (transfer_ == next.transfer_ || | 371 ColorTransformFromLinear* next = next_untyped->GetFromLinear(); |
| 346 (IsGamma22(transfer_) && IsGamma22(next.transfer_))) { | 372 if (!next) |
| 373 return false; | |
| 374 // TODO(ccameron): Use SkTransferFnsApproximatelyCancel and | |
| 375 // SkTransferFnIsApproximatelyIdentity to merge parametric transfer | |
| 376 // functions. | |
| 377 if (transfer_ == next->transfer_ || | |
| 378 (IsGamma22(transfer_) && IsGamma22(next->transfer_))) { | |
| 347 transfer_ = ColorSpace::TransferID::LINEAR; | 379 transfer_ = ColorSpace::TransferID::LINEAR; |
| 348 return true; | 380 return true; |
| 349 } | 381 } |
| 350 return false; | 382 return false; |
| 351 } | 383 } |
| 352 | 384 |
| 353 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } | 385 bool IsNull() override { return transfer_ == ColorSpace::TransferID::LINEAR; } |
| 354 | 386 |
| 355 // Assumes BT2020 primaries. | 387 // Assumes BT2020 primaries. |
| 356 static float Luma(const ColorTransform::TriStim& c) { | 388 static float Luma(const ColorTransform::TriStim& c) { |
| 357 return c.x() * 0.2627f + c.y() * 0.6780f + c.z() * 0.0593f; | 389 return c.x() * 0.2627f + c.y() * 0.6780f + c.z() * 0.0593f; |
| 358 } | 390 } |
| 359 | 391 |
| 360 ColorTransform::TriStim ClipToWhite(ColorTransform::TriStim& c) { | 392 ColorTransform::TriStim ClipToWhite(ColorTransform::TriStim& c) { |
| 361 float maximum = std::max(std::max(c.x(), c.y()), c.z()); | 393 float maximum = std::max(std::max(c.x(), c.y()), c.z()); |
| 362 if (maximum > 1.0f) { | 394 if (maximum > 1.0f) { |
| 363 float l = Luma(c); | 395 float l = Luma(c); |
| 364 c.Scale(1.0f / maximum); | 396 c.Scale(1.0f / maximum); |
| 365 ColorTransform::TriStim white(1.0f, 1.0f, 1.0f); | 397 ColorTransform::TriStim white(1.0f, 1.0f, 1.0f); |
| 366 white.Scale((1.0f - 1.0f / maximum) * l / Luma(white)); | 398 white.Scale((1.0f - 1.0f / maximum) * l / Luma(white)); |
| 367 ColorTransform::TriStim black(0.0f, 0.0f, 0.0f); | 399 ColorTransform::TriStim black(0.0f, 0.0f, 0.0f); |
| 368 c += white - black; | 400 c += white - black; |
| 369 } | 401 } |
| 370 return c; | 402 return c; |
| 371 } | 403 } |
| 372 | 404 |
| 373 void transform(ColorTransform::TriStim* colors, size_t num) override { | 405 void Transform(ColorTransform::TriStim* colors, size_t num) override { |
| 374 if (fn_valid_) { | 406 if (fn_valid_) { |
| 375 for (size_t i = 0; i < num; i++) { | 407 for (size_t i = 0; i < num; i++) { |
| 376 colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x())); | 408 colors[i].set_x(EvalSkTransferFn(fn_, colors[i].x())); |
| 377 colors[i].set_y(EvalSkTransferFn(fn_, colors[i].y())); | 409 colors[i].set_y(EvalSkTransferFn(fn_, colors[i].y())); |
| 378 colors[i].set_z(EvalSkTransferFn(fn_, colors[i].z())); | 410 colors[i].set_z(EvalSkTransferFn(fn_, colors[i].z())); |
| 379 } | 411 } |
| 380 } else if (transfer_ == ColorSpace::TransferID::SMPTEST2084_NON_HDR) { | 412 } else if (transfer_ == ColorSpace::TransferID::SMPTEST2084_NON_HDR) { |
| 381 for (size_t i = 0; i < num; i++) { | 413 for (size_t i = 0; i < num; i++) { |
| 382 ColorTransform::TriStim ret(ToLinear(transfer_, colors[i].x()), | 414 ColorTransform::TriStim ret(ToLinear(transfer_, colors[i].x()), |
| 383 ToLinear(transfer_, colors[i].y()), | 415 ToLinear(transfer_, colors[i].y()), |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 413 // on the RGB values. However, running the transfer function | 445 // on the RGB values. However, running the transfer function |
| 414 // on the U and V values doesn't make any sense since they | 446 // on the U and V values doesn't make any sense since they |
| 415 // are centered at 0.5. To work around this, the transfer function | 447 // are centered at 0.5. To work around this, the transfer function |
| 416 // is applied to the Y, R and B values, and then the U and V | 448 // is applied to the Y, R and B values, and then the U and V |
| 417 // values are calculated from that. | 449 // values are calculated from that. |
| 418 // In our implementation, the YUV->RGB matrix is used to | 450 // In our implementation, the YUV->RGB matrix is used to |
| 419 // convert YUV to RYB (the G value is replaced with an Y value.) | 451 // convert YUV to RYB (the G value is replaced with an Y value.) |
| 420 // Then we run the transfer function like normal, and finally | 452 // Then we run the transfer function like normal, and finally |
| 421 // this class is inserted as an extra step which takes calculates | 453 // this class is inserted as an extra step which takes calculates |
| 422 // the U and V values. | 454 // the U and V values. |
| 423 class ColorTransformToBT2020CL : public ColorTransformInternal { | 455 class ColorTransformToBT2020CL : public ColorTransformStep { |
| 424 public: | 456 public: |
| 425 bool Prepend(ColorTransformInternal* prev) override { | 457 bool Join(ColorTransformStep* next_untyped) override { |
| 426 return prev->Join(*this); | 458 ColorTransformFromBT2020CL* next = next_untyped->GetFromBT2020CL(); |
| 427 } | 459 if (!next) |
| 428 | 460 return false; |
| 429 bool Join(const ColorTransformFromBT2020CL& next) override { | |
| 430 if (null_) | 461 if (null_) |
| 431 return false; | 462 return false; |
| 432 null_ = true; | 463 null_ = true; |
| 433 return true; | 464 return true; |
| 434 } | 465 } |
| 435 | 466 |
| 436 bool IsNull() override { return null_; } | 467 bool IsNull() override { return null_; } |
| 437 | 468 |
| 438 void transform(ColorTransform::TriStim* RYB, size_t num) override { | 469 void Transform(ColorTransform::TriStim* RYB, size_t num) override { |
| 439 for (size_t i = 0; i < num; i++) { | 470 for (size_t i = 0; i < num; i++) { |
| 440 float U, V; | 471 float U, V; |
| 441 float B_Y = RYB[i].z() - RYB[i].y(); | 472 float B_Y = RYB[i].z() - RYB[i].y(); |
| 442 if (B_Y <= 0) { | 473 if (B_Y <= 0) { |
| 443 U = B_Y / (-2.0 * -0.9702); | 474 U = B_Y / (-2.0 * -0.9702); |
| 444 } else { | 475 } else { |
| 445 U = B_Y / (2.0 * 0.7910); | 476 U = B_Y / (2.0 * 0.7910); |
| 446 } | 477 } |
| 447 float R_Y = RYB[i].x() - RYB[i].y(); | 478 float R_Y = RYB[i].x() - RYB[i].y(); |
| 448 if (R_Y <= 0) { | 479 if (R_Y <= 0) { |
| 449 V = R_Y / (-2.0 * -0.8591); | 480 V = R_Y / (-2.0 * -0.8591); |
| 450 } else { | 481 } else { |
| 451 V = R_Y / (2.0 * 0.4969); | 482 V = R_Y / (2.0 * 0.4969); |
| 452 } | 483 } |
| 453 RYB[i] = ColorTransform::TriStim(RYB[i].y(), U, V); | 484 RYB[i] = ColorTransform::TriStim(RYB[i].y(), U, V); |
| 454 } | 485 } |
| 455 } | 486 } |
| 456 | 487 |
| 457 private: | 488 private: |
| 458 bool null_ = false; | 489 bool null_ = false; |
| 459 }; | 490 }; |
| 460 | 491 |
| 461 // Inverse of ColorTransformToBT2020CL, see comment above for more info. | 492 // Inverse of ColorTransformToBT2020CL, see comment above for more info. |
| 462 class ColorTransformFromBT2020CL : public ColorTransformInternal { | 493 class ColorTransformFromBT2020CL : public ColorTransformStep { |
| 463 public: | 494 public: |
| 464 bool Prepend(ColorTransformInternal* prev) override { | 495 bool Join(ColorTransformStep* next_untyped) override { |
| 465 return prev->Join(*this); | 496 ColorTransformToBT2020CL* next = next_untyped->GetToBT2020CL(); |
| 466 } | 497 if (!next) |
| 467 | 498 return false; |
| 468 bool Join(const ColorTransformToBT2020CL& next) override { | |
| 469 if (null_) | 499 if (null_) |
| 470 return false; | 500 return false; |
| 471 null_ = true; | 501 null_ = true; |
| 472 return true; | 502 return true; |
| 473 } | 503 } |
| 474 | 504 |
| 475 bool IsNull() override { return null_; } | 505 bool IsNull() override { return null_; } |
| 476 | 506 |
| 477 void transform(ColorTransform::TriStim* YUV, size_t num) override { | 507 void Transform(ColorTransform::TriStim* YUV, size_t num) override { |
| 478 if (null_) | 508 if (null_) |
| 479 return; | 509 return; |
| 480 for (size_t i = 0; i < num; i++) { | 510 for (size_t i = 0; i < num; i++) { |
| 481 float Y = YUV[i].x(); | 511 float Y = YUV[i].x(); |
| 482 float U = YUV[i].y(); | 512 float U = YUV[i].y(); |
| 483 float V = YUV[i].z(); | 513 float V = YUV[i].z(); |
| 484 float B_Y, R_Y; | 514 float B_Y, R_Y; |
| 485 if (U <= 0) { | 515 if (U <= 0) { |
| 486 B_Y = Y * (-2.0 * -0.9702); | 516 B_Y = Y * (-2.0 * -0.9702); |
| 487 } else { | 517 } else { |
| 488 B_Y = U * (2.0 * 0.7910); | 518 B_Y = U * (2.0 * 0.7910); |
| 489 } | 519 } |
| 490 if (V <= 0) { | 520 if (V <= 0) { |
| 491 R_Y = V * (-2.0 * -0.8591); | 521 R_Y = V * (-2.0 * -0.8591); |
| 492 } else { | 522 } else { |
| 493 R_Y = V * (2.0 * 0.4969); | 523 R_Y = V * (2.0 * 0.4969); |
| 494 } | 524 } |
| 495 // Return an RYB value, later steps will fix it. | 525 // Return an RYB value, later steps will fix it. |
| 496 YUV[i] = ColorTransform::TriStim(R_Y + Y, YUV[i].x(), B_Y + Y); | 526 YUV[i] = ColorTransform::TriStim(R_Y + Y, YUV[i].x(), B_Y + Y); |
| 497 } | 527 } |
| 498 } | 528 } |
| 499 | 529 |
| 500 private: | 530 private: |
| 501 bool null_ = false; | 531 bool null_ = false; |
| 502 }; | 532 }; |
| 503 | 533 |
| 504 class ChainColorTransform : public ColorTransform { | 534 // static |
| 505 public: | 535 void ColorTransformInternal::Append(ColorSpace from, |
|
hubbe
2017/02/14 19:04:51
Append is kind of a strange name for this.
Maybe A
ccameron
2017/02/15 00:11:31
Done.
| |
| 506 ChainColorTransform(std::unique_ptr<ColorTransform> a, | 536 const ColorSpace& to, |
| 507 std::unique_ptr<ColorTransform> b) | 537 ColorTransform::Intent intent, |
| 508 : a_(std::move(a)), b_(std::move(b)) {} | 538 ColorTransformStepList* builder) { |
| 539 if (intent == ColorTransform::Intent::INTENT_PERCEPTUAL) { | |
| 540 switch (from.transfer_) { | |
| 541 case ColorSpace::TransferID::UNSPECIFIED: | |
| 542 case ColorSpace::TransferID::BT709: | |
| 543 case ColorSpace::TransferID::SMPTE170M: | |
| 544 // SMPTE 1886 suggests that we should be using gamma 2.4 for BT709 | |
| 545 // content. However, most displays actually use a gamma of 2.2, and | |
| 546 // user studies shows that users don't really care. Using the same | |
| 547 // gamma as the display will let us optimize a lot more, so lets stick | |
| 548 // with using the SRGB transfer function. | |
| 549 from.transfer_ = ColorSpace::TransferID::IEC61966_2_1; | |
| 550 break; | |
| 509 | 551 |
| 510 private: | 552 case ColorSpace::TransferID::SMPTEST2084: |
| 511 void transform(TriStim* colors, size_t num) override { | 553 if (!to.IsHDR()) { |
| 512 a_->transform(colors, num); | 554 // We don't have an HDR display, so replace SMPTE 2084 with |
| 513 b_->transform(colors, num); | 555 // something that returns ranges more or less suitable for a normal |
| 514 } | 556 // display. |
| 515 std::unique_ptr<ColorTransform> a_; | 557 from.transfer_ = ColorSpace::TransferID::SMPTEST2084_NON_HDR; |
| 516 std::unique_ptr<ColorTransform> b_; | 558 } |
| 517 }; | 559 break; |
| 518 | 560 |
| 519 class TransformBuilder { | 561 case ColorSpace::TransferID::ARIB_STD_B67: |
| 520 public: | 562 if (!to.IsHDR()) { |
| 521 void Append(std::unique_ptr<ColorTransformInternal> transform) { | 563 // Interpreting HLG using a gamma 2.4 works reasonably well for SDR |
| 522 if (!disable_optimizations_ && transform->IsNull()) | 564 // displays. |
| 523 return; // Null transform | 565 from.transfer_ = ColorSpace::TransferID::GAMMA24; |
| 524 transforms_.push_back(std::move(transform)); | 566 } |
| 525 if (disable_optimizations_) | |
| 526 return; | |
| 527 while (transforms_.size() >= 2 && | |
| 528 transforms_.back()->Prepend( | |
| 529 transforms_[transforms_.size() - 2].get())) { | |
| 530 transforms_.pop_back(); | |
| 531 if (transforms_.back()->IsNull()) { | |
| 532 transforms_.pop_back(); | |
| 533 break; | 567 break; |
| 534 } | 568 |
| 569 default: // Do nothing | |
| 570 break; | |
| 535 } | 571 } |
| 572 | |
| 573 // TODO(hubbe): shrink gamuts here (never stretch gamuts) | |
| 536 } | 574 } |
| 537 | 575 |
| 538 std::unique_ptr<ColorTransform> GetTransform() { | 576 builder->push_back( |
| 539 if (transforms_.empty()) | 577 base::MakeUnique<ColorTransformMatrix>(GetRangeAdjustMatrix(from))); |
| 540 return base::MakeUnique<ColorTransformNull>(); | |
| 541 std::unique_ptr<ColorTransform> ret(std::move(transforms_.back())); | |
| 542 transforms_.pop_back(); | |
| 543 | 578 |
| 544 while (!transforms_.empty()) { | 579 builder->push_back( |
| 545 ret = std::unique_ptr<ColorTransform>(new ChainColorTransform( | 580 base::MakeUnique<ColorTransformMatrix>(Invert(GetTransferMatrix(from)))); |
| 546 std::move(transforms_.back()), std::move(ret))); | |
| 547 transforms_.pop_back(); | |
| 548 } | |
| 549 | 581 |
| 550 return ret; | 582 SkColorSpaceTransferFn to_linear_fn; |
| 583 bool to_linear_fn_valid = from.GetTransferFunction(&to_linear_fn); | |
| 584 builder->push_back(base::MakeUnique<ColorTransformToLinear>( | |
| 585 from.transfer_, to_linear_fn, to_linear_fn_valid)); | |
| 586 | |
| 587 if (from.matrix_ == ColorSpace::MatrixID::BT2020_CL) { | |
| 588 // BT2020 CL is a special case. | |
| 589 builder->push_back(base::MakeUnique<ColorTransformFromBT2020CL>()); | |
| 590 } | |
| 591 builder->push_back( | |
| 592 base::MakeUnique<ColorTransformMatrix>(GetPrimaryTransform(from))); | |
| 593 | |
| 594 builder->push_back( | |
| 595 base::MakeUnique<ColorTransformMatrix>(Invert(GetPrimaryTransform(to)))); | |
| 596 if (to.matrix_ == ColorSpace::MatrixID::BT2020_CL) { | |
| 597 // BT2020 CL is a special case. | |
| 598 builder->push_back(base::MakeUnique<ColorTransformToBT2020CL>()); | |
| 551 } | 599 } |
| 552 | 600 |
| 553 void disable_optimizations() { disable_optimizations_ = true; } | 601 SkColorSpaceTransferFn from_linear_fn; |
| 602 bool from_linear_fn_valid = to.GetInverseTransferFunction(&from_linear_fn); | |
| 603 builder->push_back(base::MakeUnique<ColorTransformFromLinear>( | |
| 604 to.transfer_, from_linear_fn, from_linear_fn_valid)); | |
| 554 | 605 |
| 555 private: | 606 builder->push_back( |
| 556 bool disable_optimizations_ = false; | 607 base::MakeUnique<ColorTransformMatrix>(GetTransferMatrix(to))); |
| 557 std::vector<std::unique_ptr<ColorTransformInternal>> transforms_; | |
| 558 }; | |
| 559 | 608 |
| 560 class ColorSpaceToColorSpaceTransform { | 609 builder->push_back( |
| 561 public: | 610 base::MakeUnique<ColorTransformMatrix>(Invert(GetRangeAdjustMatrix(to)))); |
| 562 static Transform GetPrimaryTransform(const ColorSpace& c) { | 611 } |
| 563 SkMatrix44 sk_matrix; | |
| 564 c.GetPrimaryMatrix(&sk_matrix); | |
| 565 return Transform(sk_matrix); | |
| 566 } | |
| 567 | 612 |
| 568 static void ColorSpaceToColorSpace(ColorSpace from, | 613 class QCMSColorTransform : public ColorTransformStep { |
| 569 ColorSpace to, | |
| 570 ColorTransform::Intent intent, | |
| 571 TransformBuilder* builder) { | |
| 572 if (intent == ColorTransform::Intent::INTENT_PERCEPTUAL) { | |
| 573 switch (from.transfer_) { | |
| 574 case ColorSpace::TransferID::UNSPECIFIED: | |
| 575 case ColorSpace::TransferID::BT709: | |
| 576 case ColorSpace::TransferID::SMPTE170M: | |
| 577 // SMPTE 1886 suggests that we should be using gamma 2.4 for BT709 | |
| 578 // content. However, most displays actually use a gamma of 2.2, and | |
| 579 // user studies shows that users don't really care. Using the same | |
| 580 // gamma as the display will let us optimize a lot more, so lets stick | |
| 581 // with using the SRGB transfer function. | |
| 582 from.transfer_ = ColorSpace::TransferID::IEC61966_2_1; | |
| 583 break; | |
| 584 | |
| 585 case ColorSpace::TransferID::SMPTEST2084: | |
| 586 if (!to.IsHDR()) { | |
| 587 // We don't have an HDR display, so replace SMPTE 2084 with | |
| 588 // something that returns ranges more or less suitable for a normal | |
| 589 // display. | |
| 590 from.transfer_ = ColorSpace::TransferID::SMPTEST2084_NON_HDR; | |
| 591 } | |
| 592 break; | |
| 593 | |
| 594 case ColorSpace::TransferID::ARIB_STD_B67: | |
| 595 if (!to.IsHDR()) { | |
| 596 // Interpreting HLG using a gamma 2.4 works reasonably well for SDR | |
| 597 // displays. | |
| 598 from.transfer_ = ColorSpace::TransferID::GAMMA24; | |
| 599 } | |
| 600 break; | |
| 601 | |
| 602 default: // Do nothing | |
| 603 break; | |
| 604 } | |
| 605 | |
| 606 // TODO(hubbe): shrink gamuts here (never stretch gamuts) | |
| 607 } | |
| 608 | |
| 609 builder->Append(base::MakeUnique<ColorTransformMatrix>( | |
| 610 GetRangeAdjustMatrix(from))); | |
| 611 | |
| 612 builder->Append(base::MakeUnique<ColorTransformMatrix>( | |
| 613 Invert(GetTransferMatrix(from)))); | |
| 614 | |
| 615 SkColorSpaceTransferFn to_linear_fn; | |
| 616 bool to_linear_fn_valid = from.GetTransferFunction(&to_linear_fn); | |
| 617 builder->Append(base::MakeUnique<ColorTransformToLinear>( | |
| 618 from.transfer_, to_linear_fn, to_linear_fn_valid)); | |
| 619 | |
| 620 if (from.matrix_ == ColorSpace::MatrixID::BT2020_CL) { | |
| 621 // BT2020 CL is a special case. | |
| 622 builder->Append(base::MakeUnique<ColorTransformFromBT2020CL>()); | |
| 623 } | |
| 624 builder->Append( | |
| 625 base::MakeUnique<ColorTransformMatrix>(GetPrimaryTransform(from))); | |
| 626 | |
| 627 builder->Append(base::MakeUnique<ColorTransformMatrix>( | |
| 628 Invert(GetPrimaryTransform(to)))); | |
| 629 if (to.matrix_ == ColorSpace::MatrixID::BT2020_CL) { | |
| 630 // BT2020 CL is a special case. | |
| 631 builder->Append(base::MakeUnique<ColorTransformToBT2020CL>()); | |
| 632 } | |
| 633 | |
| 634 SkColorSpaceTransferFn from_linear_fn; | |
| 635 bool from_linear_fn_valid = to.GetInverseTransferFunction(&from_linear_fn); | |
| 636 builder->Append(base::MakeUnique<ColorTransformFromLinear>( | |
| 637 to.transfer_, from_linear_fn, from_linear_fn_valid)); | |
| 638 | |
| 639 builder->Append( | |
| 640 base::MakeUnique<ColorTransformMatrix>(GetTransferMatrix(to))); | |
| 641 | |
| 642 builder->Append(base::MakeUnique<ColorTransformMatrix>( | |
| 643 Invert(GetRangeAdjustMatrix(to)))); | |
| 644 } | |
| 645 }; | |
| 646 | |
| 647 class QCMSColorTransform : public ColorTransformInternal { | |
| 648 public: | 614 public: |
| 649 // Takes ownership of the profiles | 615 // Takes ownership of the profiles |
| 650 QCMSColorTransform(qcms_profile* from, qcms_profile* to) | 616 QCMSColorTransform(ScopedQcmsProfile from, ScopedQcmsProfile to) |
| 651 : from_(from), to_(to) {} | 617 : from_(std::move(from)), to_(std::move(to)) {} |
| 652 ~QCMSColorTransform() override { | 618 ~QCMSColorTransform() override {} |
| 653 qcms_profile_release(from_); | 619 QCMSColorTransform* GetQCMS() override { return this; } |
| 654 qcms_profile_release(to_); | 620 bool Join(ColorTransformStep* next_untyped) override { |
| 655 } | 621 QCMSColorTransform* next = next_untyped->GetQCMS(); |
| 656 bool Prepend(ColorTransformInternal* prev) override { | 622 if (!next) |
| 657 // Not currently optimizable. | 623 return false; |
| 624 if (qcms_profile_match(to_.get(), next->from_.get())) { | |
| 625 to_ = std::move(next->to_); | |
| 626 return true; | |
| 627 } | |
| 658 return false; | 628 return false; |
| 659 } | 629 } |
| 660 bool IsNull() override { return from_ == to_; } | 630 bool IsNull() override { |
| 661 void transform(TriStim* colors, size_t num) override { | 631 if (qcms_profile_match(from_.get(), to_.get())) |
| 662 CHECK(sizeof(TriStim) == sizeof(float[3])); | 632 return true; |
| 633 return false; | |
| 634 } | |
| 635 void Transform(ColorTransform::TriStim* colors, size_t num) override { | |
| 636 CHECK(sizeof(ColorTransform::TriStim) == sizeof(float[3])); | |
| 663 // QCMS doesn't like numbers outside 0..1 | 637 // QCMS doesn't like numbers outside 0..1 |
| 664 for (size_t i = 0; i < num; i++) { | 638 for (size_t i = 0; i < num; i++) { |
| 665 colors[i].set_x(fmin(1.0f, fmax(0.0f, colors[i].x()))); | 639 colors[i].set_x(std::min(1.0f, std::max(0.0f, colors[i].x()))); |
| 666 colors[i].set_y(fmin(1.0f, fmax(0.0f, colors[i].y()))); | 640 colors[i].set_y(std::min(1.0f, std::max(0.0f, colors[i].y()))); |
| 667 colors[i].set_z(fmin(1.0f, fmax(0.0f, colors[i].z()))); | 641 colors[i].set_z(std::min(1.0f, std::max(0.0f, colors[i].z()))); |
| 668 } | 642 } |
| 669 qcms_chain_transform(from_, to_, reinterpret_cast<float*>(colors), | 643 qcms_chain_transform(from_.get(), to_.get(), |
| 644 reinterpret_cast<float*>(colors), | |
| 670 reinterpret_cast<float*>(colors), num * 3); | 645 reinterpret_cast<float*>(colors), num * 3); |
| 671 } | 646 } |
| 672 | 647 |
| 673 private: | 648 private: |
| 674 qcms_profile *from_, *to_; | 649 ScopedQcmsProfile from_; |
| 650 ScopedQcmsProfile to_; | |
| 675 }; | 651 }; |
| 676 | 652 |
| 677 qcms_profile* GetQCMSProfileIfAvailable(const ColorSpace& color_space) { | 653 ScopedQcmsProfile GetQCMSProfileIfAvailable(const ColorSpace& color_space) { |
| 678 ICCProfile icc_profile = ICCProfile::FromColorSpace(color_space); | 654 ICCProfile icc_profile = ICCProfile::FromColorSpace(color_space); |
| 679 if (icc_profile.GetData().empty()) | 655 if (icc_profile.GetData().empty()) |
| 680 return nullptr; | 656 return nullptr; |
| 681 return qcms_profile_from_memory(icc_profile.GetData().data(), | 657 return ScopedQcmsProfile(qcms_profile_from_memory( |
| 682 icc_profile.GetData().size()); | 658 icc_profile.GetData().data(), icc_profile.GetData().size())); |
| 683 } | 659 } |
| 684 | 660 |
| 685 qcms_profile* GetXYZD50Profile() { | 661 ScopedQcmsProfile GetXYZD50Profile() { |
| 686 // QCMS is trixy, it has a datatype called qcms_CIE_xyY, but what it expects | 662 // QCMS is trixy, it has a datatype called qcms_CIE_xyY, but what it expects |
| 687 // is in fact not xyY color coordinates, it just wants the x/y values of the | 663 // is in fact not xyY color coordinates, it just wants the x/y values of the |
| 688 // primaries with Y equal to 1.0. | 664 // primaries with Y equal to 1.0. |
| 689 qcms_CIE_xyYTRIPLE xyz; | 665 qcms_CIE_xyYTRIPLE xyz; |
| 690 qcms_CIE_xyY w; | 666 qcms_CIE_xyY w; |
| 691 xyz.red.x = 1.0f; | 667 xyz.red.x = 1.0f; |
| 692 xyz.red.y = 0.0f; | 668 xyz.red.y = 0.0f; |
| 693 xyz.red.Y = 1.0f; | 669 xyz.red.Y = 1.0f; |
| 694 xyz.green.x = 0.0f; | 670 xyz.green.x = 0.0f; |
| 695 xyz.green.y = 1.0f; | 671 xyz.green.y = 1.0f; |
| 696 xyz.green.Y = 1.0f; | 672 xyz.green.Y = 1.0f; |
| 697 xyz.blue.x = 0.0f; | 673 xyz.blue.x = 0.0f; |
| 698 xyz.blue.y = 0.0f; | 674 xyz.blue.y = 0.0f; |
| 699 xyz.blue.Y = 1.0f; | 675 xyz.blue.Y = 1.0f; |
| 700 w.x = 0.34567f; | 676 w.x = 0.34567f; |
| 701 w.y = 0.35850f; | 677 w.y = 0.35850f; |
| 702 w.Y = 1.0f; | 678 w.Y = 1.0f; |
| 703 return qcms_profile_create_rgb_with_gamma(w, xyz, 1.0f); | 679 return ScopedQcmsProfile(qcms_profile_create_rgb_with_gamma(w, xyz, 1.0f)); |
| 704 } | 680 } |
| 705 | 681 |
| 682 // static | |
| 706 std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform( | 683 std::unique_ptr<ColorTransform> ColorTransform::NewColorTransform( |
|
hubbe
2017/02/14 19:04:51
Probably move all the code in this function into C
ccameron
2017/02/15 00:11:31
Good point -- done.
| |
| 707 const ColorSpace& from, | 684 const ColorSpace& from, |
| 708 const ColorSpace& to, | 685 const ColorSpace& to, |
| 709 Intent intent) { | 686 Intent intent) { |
| 710 TransformBuilder builder; | 687 ColorTransformStepList builder; |
| 711 if (intent == Intent::TEST_NO_OPT) { | 688 |
| 712 builder.disable_optimizations(); | 689 ScopedQcmsProfile from_profile = GetQCMSProfileIfAvailable(from); |
| 690 ScopedQcmsProfile to_profile = GetQCMSProfileIfAvailable(to); | |
| 691 bool has_from_profile = !!from_profile; | |
| 692 bool has_to_profile = !!to_profile; | |
| 693 | |
| 694 if (from_profile) { | |
| 695 builder.push_back(base::MakeUnique<QCMSColorTransform>( | |
| 696 std::move(from_profile), GetXYZD50Profile())); | |
| 713 } | 697 } |
| 714 | 698 |
| 715 qcms_profile* from_profile = GetQCMSProfileIfAvailable(from); | 699 ColorTransformInternal::Append( |
| 716 qcms_profile* to_profile = GetQCMSProfileIfAvailable(to); | 700 has_from_profile ? ColorSpace::CreateXYZD50() : from, |
| 701 has_to_profile ? ColorSpace::CreateXYZD50() : to, intent, &builder); | |
| 717 | 702 |
| 718 if (from_profile && to_profile) { | |
| 719 return std::unique_ptr<ColorTransform>( | |
| 720 new QCMSColorTransform(from_profile, to_profile)); | |
| 721 } | |
| 722 if (from_profile) { | |
| 723 builder.Append(std::unique_ptr<ColorTransformInternal>( | |
| 724 new QCMSColorTransform(from_profile, GetXYZD50Profile()))); | |
| 725 } | |
| 726 ColorSpaceToColorSpaceTransform::ColorSpaceToColorSpace( | |
| 727 from_profile ? ColorSpace::CreateXYZD50() : from, | |
| 728 to_profile ? ColorSpace::CreateXYZD50() : to, intent, &builder); | |
| 729 if (to_profile) { | 703 if (to_profile) { |
| 730 builder.Append(std::unique_ptr<ColorTransformInternal>( | 704 builder.push_back(base::MakeUnique<QCMSColorTransform>( |
| 731 new QCMSColorTransform(GetXYZD50Profile(), to_profile))); | 705 GetXYZD50Profile(), std::move(to_profile))); |
| 732 } | 706 } |
| 733 | 707 |
| 734 return builder.GetTransform(); | 708 if (intent != Intent::TEST_NO_OPT) |
| 709 ColorTransformInternal::Simplify(&builder); | |
| 710 | |
| 711 return std::unique_ptr<ColorTransform>( | |
| 712 new ColorTransformInternal(std::move(builder))); | |
| 735 } | 713 } |
| 736 | 714 |
| 737 // static | 715 // static |
| 738 float ColorTransform::ToLinearForTesting(ColorSpace::TransferID transfer, | 716 void ColorTransformInternal::Simplify(ColorTransformStepList* steps) { |
| 739 float v) { | 717 for (auto iter = steps->begin(); iter != steps->end();) { |
| 740 ColorSpace space(ColorSpace::PrimaryID::BT709, transfer, | 718 std::unique_ptr<ColorTransformStep>& this_step = *iter; |
| 741 ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); | |
| 742 SkColorSpaceTransferFn to_linear_fn; | |
| 743 bool to_linear_fn_valid = space.GetTransferFunction(&to_linear_fn); | |
| 744 ColorTransformToLinear to_linear_transform(transfer, to_linear_fn, | |
| 745 to_linear_fn_valid); | |
| 746 TriStim color(v, v, v); | |
| 747 to_linear_transform.transform(&color, 1); | |
| 748 return color.x(); | |
| 749 } | |
| 750 | 719 |
| 751 // static | 720 // Try to Join |next_step| into |this_step|. If successful, re-visit the |
| 752 float ColorTransform::FromLinearForTesting(ColorSpace::TransferID transfer, | 721 // step before |this_step|. |
| 753 float v) { | 722 auto iter_next = iter; |
| 754 ColorSpace space(ColorSpace::PrimaryID::BT709, transfer, | 723 iter_next++; |
| 755 ColorSpace::MatrixID::RGB, ColorSpace::RangeID::FULL); | 724 if (iter_next != steps->end()) { |
| 756 SkColorSpaceTransferFn from_linear_fn; | 725 std::unique_ptr<ColorTransformStep>& next_step = *iter_next; |
| 757 bool from_linear_fn_valid = space.GetInverseTransferFunction(&from_linear_fn); | 726 if (this_step->Join(next_step.get())) { |
| 727 steps->erase(iter_next); | |
| 728 if (iter != steps->begin()) | |
| 729 --iter; | |
| 730 continue; | |
| 731 } | |
| 732 } | |
| 758 | 733 |
| 759 ColorTransformFromLinear from_linear_transform(transfer, from_linear_fn, | 734 // If |this_step| step is a no-op, remove it, and re-visit the step before |
| 760 from_linear_fn_valid); | 735 // |this_step|. |
| 761 TriStim color(v, v, v); | 736 if (this_step->IsNull()) { |
| 762 from_linear_transform.transform(&color, 1); | 737 iter = steps->erase(iter); |
| 763 return color.x(); | 738 if (iter != steps->begin()) |
| 739 --iter; | |
| 740 continue; | |
| 741 } | |
| 742 | |
| 743 ++iter; | |
| 744 } | |
| 764 } | 745 } |
| 765 | 746 |
| 766 } // namespace gfx | 747 } // namespace gfx |
| OLD | NEW |