1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include <ui/ColorSpace.h> 18 19 using namespace std::placeholders; 20 21 namespace android { 22 23 static constexpr float linearResponse(float v) { 24 return v; 25 } 26 27 static constexpr float rcpResponse(float x, const ColorSpace::TransferParameters& p) { 28 return x >= p.d * p.c ? (std::pow(x, 1.0f / p.g) - p.b) / p.a : x / p.c; 29 } 30 31 static constexpr float response(float x, const ColorSpace::TransferParameters& p) { 32 return x >= p.d ? std::pow(p.a * x + p.b, p.g) : p.c * x; 33 } 34 35 static constexpr float rcpFullResponse(float x, const ColorSpace::TransferParameters& p) { 36 return x >= p.d * p.c ? (std::pow(x - p.e, 1.0f / p.g) - p.b) / p.a : (x - p.f) / p.c; 37 } 38 39 static constexpr float fullResponse(float x, const ColorSpace::TransferParameters& p) { 40 return x >= p.d ? std::pow(p.a * x + p.b, p.g) + p.e : p.c * x + p.f; 41 } 42 43 static float absRcpResponse(float x, float g,float a, float b, float c, float d) { 44 float xx = std::abs(x); 45 return std::copysign(xx >= d * c ? (std::pow(xx, 1.0f / g) - b) / a : xx / c, x); 46 } 47 48 static float absResponse(float x, float g, float a, float b, float c, float d) { 49 float xx = std::abs(x); 50 return std::copysign(xx >= d ? std::pow(a * xx + b, g) : c * xx, x); 51 } 52 53 static float safePow(float x, float e) { 54 return powf(x < 0.0f ? 0.0f : x, e); 55 } 56 57 static ColorSpace::transfer_function toOETF(const ColorSpace::TransferParameters& parameters) { 58 if (parameters.e == 0.0f && parameters.f == 0.0f) { 59 return std::bind(rcpResponse, _1, parameters); 60 } 61 return std::bind(rcpFullResponse, _1, parameters); 62 } 63 64 static ColorSpace::transfer_function toEOTF( const ColorSpace::TransferParameters& parameters) { 65 if (parameters.e == 0.0f && parameters.f == 0.0f) { 66 return std::bind(response, _1, parameters); 67 } 68 return std::bind(fullResponse, _1, parameters); 69 } 70 71 static ColorSpace::transfer_function toOETF(float gamma) { 72 if (gamma == 1.0f) { 73 return linearResponse; 74 } 75 return std::bind(safePow, _1, 1.0f / gamma); 76 } 77 78 static ColorSpace::transfer_function toEOTF(float gamma) { 79 if (gamma == 1.0f) { 80 return linearResponse; 81 } 82 return std::bind(safePow, _1, gamma); 83 } 84 85 static constexpr std::array<float2, 3> computePrimaries(const mat3& rgbToXYZ) { 86 float3 r(rgbToXYZ * float3{1, 0, 0}); 87 float3 g(rgbToXYZ * float3{0, 1, 0}); 88 float3 b(rgbToXYZ * float3{0, 0, 1}); 89 90 return {{r.xy / dot(r, float3{1}), 91 g.xy / dot(g, float3{1}), 92 b.xy / dot(b, float3{1})}}; 93 } 94 95 static constexpr float2 computeWhitePoint(const mat3& rgbToXYZ) { 96 float3 w(rgbToXYZ * float3{1}); 97 return w.xy / dot(w, float3{1}); 98 } 99 100 ColorSpace::ColorSpace( 101 const std::string& name, 102 const mat3& rgbToXYZ, 103 transfer_function OETF, 104 transfer_function EOTF, 105 clamping_function clamper) noexcept 106 : mName(name) 107 , mRGBtoXYZ(rgbToXYZ) 108 , mXYZtoRGB(inverse(rgbToXYZ)) 109 , mOETF(std::move(OETF)) 110 , mEOTF(std::move(EOTF)) 111 , mClamper(std::move(clamper)) 112 , mPrimaries(computePrimaries(rgbToXYZ)) 113 , mWhitePoint(computeWhitePoint(rgbToXYZ)) { 114 } 115 116 ColorSpace::ColorSpace( 117 const std::string& name, 118 const mat3& rgbToXYZ, 119 const TransferParameters parameters, 120 clamping_function clamper) noexcept 121 : mName(name) 122 , mRGBtoXYZ(rgbToXYZ) 123 , mXYZtoRGB(inverse(rgbToXYZ)) 124 , mParameters(parameters) 125 , mOETF(toOETF(mParameters)) 126 , mEOTF(toEOTF(mParameters)) 127 , mClamper(std::move(clamper)) 128 , mPrimaries(computePrimaries(rgbToXYZ)) 129 , mWhitePoint(computeWhitePoint(rgbToXYZ)) { 130 } 131 132 ColorSpace::ColorSpace( 133 const std::string& name, 134 const mat3& rgbToXYZ, 135 float gamma, 136 clamping_function clamper) noexcept 137 : mName(name) 138 , mRGBtoXYZ(rgbToXYZ) 139 , mXYZtoRGB(inverse(rgbToXYZ)) 140 , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}) 141 , mOETF(toOETF(gamma)) 142 , mEOTF(toEOTF(gamma)) 143 , mClamper(std::move(clamper)) 144 , mPrimaries(computePrimaries(rgbToXYZ)) 145 , mWhitePoint(computeWhitePoint(rgbToXYZ)) { 146 } 147 148 ColorSpace::ColorSpace( 149 const std::string& name, 150 const std::array<float2, 3>& primaries, 151 const float2& whitePoint, 152 transfer_function OETF, 153 transfer_function EOTF, 154 clamping_function clamper) noexcept 155 : mName(name) 156 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) 157 , mXYZtoRGB(inverse(mRGBtoXYZ)) 158 , mOETF(std::move(OETF)) 159 , mEOTF(std::move(EOTF)) 160 , mClamper(std::move(clamper)) 161 , mPrimaries(primaries) 162 , mWhitePoint(whitePoint) { 163 } 164 165 ColorSpace::ColorSpace( 166 const std::string& name, 167 const std::array<float2, 3>& primaries, 168 const float2& whitePoint, 169 const TransferParameters parameters, 170 clamping_function clamper) noexcept 171 : mName(name) 172 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) 173 , mXYZtoRGB(inverse(mRGBtoXYZ)) 174 , mParameters(parameters) 175 , mOETF(toOETF(mParameters)) 176 , mEOTF(toEOTF(mParameters)) 177 , mClamper(std::move(clamper)) 178 , mPrimaries(primaries) 179 , mWhitePoint(whitePoint) { 180 } 181 182 ColorSpace::ColorSpace( 183 const std::string& name, 184 const std::array<float2, 3>& primaries, 185 const float2& whitePoint, 186 float gamma, 187 clamping_function clamper) noexcept 188 : mName(name) 189 , mRGBtoXYZ(computeXYZMatrix(primaries, whitePoint)) 190 , mXYZtoRGB(inverse(mRGBtoXYZ)) 191 , mParameters({gamma, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}) 192 , mOETF(toOETF(gamma)) 193 , mEOTF(toEOTF(gamma)) 194 , mClamper(std::move(clamper)) 195 , mPrimaries(primaries) 196 , mWhitePoint(whitePoint) { 197 } 198 199 constexpr mat3 ColorSpace::computeXYZMatrix( 200 const std::array<float2, 3>& primaries, const float2& whitePoint) { 201 const float2& R = primaries[0]; 202 const float2& G = primaries[1]; 203 const float2& B = primaries[2]; 204 const float2& W = whitePoint; 205 206 float oneRxRy = (1 - R.x) / R.y; 207 float oneGxGy = (1 - G.x) / G.y; 208 float oneBxBy = (1 - B.x) / B.y; 209 float oneWxWy = (1 - W.x) / W.y; 210 211 float RxRy = R.x / R.y; 212 float GxGy = G.x / G.y; 213 float BxBy = B.x / B.y; 214 float WxWy = W.x / W.y; 215 216 float BY = 217 ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) / 218 ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy)); 219 float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy); 220 float RY = 1 - GY - BY; 221 222 float RYRy = RY / R.y; 223 float GYGy = GY / G.y; 224 float BYBy = BY / B.y; 225 226 return { 227 float3{RYRy * R.x, RY, RYRy * (1 - R.x - R.y)}, 228 float3{GYGy * G.x, GY, GYGy * (1 - G.x - G.y)}, 229 float3{BYBy * B.x, BY, BYBy * (1 - B.x - B.y)} 230 }; 231 } 232 233 const ColorSpace ColorSpace::sRGB() { 234 return { 235 "sRGB IEC61966-2.1", 236 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, 237 {0.3127f, 0.3290f}, 238 {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f, 0.0f, 0.0f} 239 }; 240 } 241 242 const ColorSpace ColorSpace::linearSRGB() { 243 return { 244 "sRGB IEC61966-2.1 (Linear)", 245 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, 246 {0.3127f, 0.3290f} 247 }; 248 } 249 250 const ColorSpace ColorSpace::extendedSRGB() { 251 return { 252 "scRGB-nl IEC 61966-2-2:2003", 253 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, 254 {0.3127f, 0.3290f}, 255 std::bind(absRcpResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f), 256 std::bind(absResponse, _1, 2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.04045f), 257 std::bind(clamp<float>, _1, -0.799f, 2.399f) 258 }; 259 } 260 261 const ColorSpace ColorSpace::linearExtendedSRGB() { 262 return { 263 "scRGB IEC 61966-2-2:2003", 264 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, 265 {0.3127f, 0.3290f}, 266 1.0f, 267 std::bind(clamp<float>, _1, -0.5f, 7.499f) 268 }; 269 } 270 271 const ColorSpace ColorSpace::NTSC() { 272 return { 273 "NTSC (1953)", 274 {{float2{0.67f, 0.33f}, {0.21f, 0.71f}, {0.14f, 0.08f}}}, 275 {0.310f, 0.316f}, 276 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} 277 }; 278 } 279 280 const ColorSpace ColorSpace::BT709() { 281 return { 282 "Rec. ITU-R BT.709-5", 283 {{float2{0.640f, 0.330f}, {0.300f, 0.600f}, {0.150f, 0.060f}}}, 284 {0.3127f, 0.3290f}, 285 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} 286 }; 287 } 288 289 const ColorSpace ColorSpace::BT2020() { 290 return { 291 "Rec. ITU-R BT.2020-1", 292 {{float2{0.708f, 0.292f}, {0.170f, 0.797f}, {0.131f, 0.046f}}}, 293 {0.3127f, 0.3290f}, 294 {1 / 0.45f, 1 / 1.099f, 0.099f / 1.099f, 1 / 4.5f, 0.081f, 0.0f, 0.0f} 295 }; 296 } 297 298 const ColorSpace ColorSpace::AdobeRGB() { 299 return { 300 "Adobe RGB (1998)", 301 {{float2{0.64f, 0.33f}, {0.21f, 0.71f}, {0.15f, 0.06f}}}, 302 {0.3127f, 0.3290f}, 303 2.2f 304 }; 305 } 306 307 const ColorSpace ColorSpace::ProPhotoRGB() { 308 return { 309 "ROMM RGB ISO 22028-2:2013", 310 {{float2{0.7347f, 0.2653f}, {0.1596f, 0.8404f}, {0.0366f, 0.0001f}}}, 311 {0.34567f, 0.35850f}, 312 {1.8f, 1.0f, 0.0f, 1 / 16.0f, 0.031248f, 0.0f, 0.0f} 313 }; 314 } 315 316 const ColorSpace ColorSpace::DisplayP3() { 317 return { 318 "Display P3", 319 {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, 320 {0.3127f, 0.3290f}, 321 {2.4f, 1 / 1.055f, 0.055f / 1.055f, 1 / 12.92f, 0.039f, 0.0f, 0.0f} 322 }; 323 } 324 325 const ColorSpace ColorSpace::DCIP3() { 326 return { 327 "SMPTE RP 431-2-2007 DCI (P3)", 328 {{float2{0.680f, 0.320f}, {0.265f, 0.690f}, {0.150f, 0.060f}}}, 329 {0.314f, 0.351f}, 330 2.6f 331 }; 332 } 333 334 const ColorSpace ColorSpace::ACES() { 335 return { 336 "SMPTE ST 2065-1:2012 ACES", 337 {{float2{0.73470f, 0.26530f}, {0.0f, 1.0f}, {0.00010f, -0.0770f}}}, 338 {0.32168f, 0.33767f}, 339 1.0f, 340 std::bind(clamp<float>, _1, -65504.0f, 65504.0f) 341 }; 342 } 343 344 const ColorSpace ColorSpace::ACEScg() { 345 return { 346 "Academy S-2014-004 ACEScg", 347 {{float2{0.713f, 0.293f}, {0.165f, 0.830f}, {0.128f, 0.044f}}}, 348 {0.32168f, 0.33767f}, 349 1.0f, 350 std::bind(clamp<float>, _1, -65504.0f, 65504.0f) 351 }; 352 } 353 354 std::unique_ptr<float3> ColorSpace::createLUT(uint32_t size, 355 const ColorSpace& src, const ColorSpace& dst) { 356 357 size = clamp(size, 2u, 256u); 358 float m = 1.0f / float(size - 1); 359 360 std::unique_ptr<float3> lut(new float3[size * size * size]); 361 float3* data = lut.get(); 362 363 ColorSpaceConnector connector(src, dst); 364 365 for (uint32_t z = 0; z < size; z++) { 366 for (int32_t y = int32_t(size - 1); y >= 0; y--) { 367 for (uint32_t x = 0; x < size; x++) { 368 *data++ = connector.transform({x * m, y * m, z * m}); 369 } 370 } 371 } 372 373 return lut; 374 } 375 376 static const float2 ILLUMINANT_D50_XY = {0.34567f, 0.35850f}; 377 static const float3 ILLUMINANT_D50_XYZ = {0.964212f, 1.0f, 0.825188f}; 378 static const mat3 BRADFORD = mat3{ 379 float3{ 0.8951f, -0.7502f, 0.0389f}, 380 float3{ 0.2664f, 1.7135f, -0.0685f}, 381 float3{-0.1614f, 0.0367f, 1.0296f} 382 }; 383 384 static mat3 adaptation(const mat3& matrix, const float3& srcWhitePoint, const float3& dstWhitePoint) { 385 float3 srcLMS = matrix * srcWhitePoint; 386 float3 dstLMS = matrix * dstWhitePoint; 387 return inverse(matrix) * mat3{dstLMS / srcLMS} * matrix; 388 } 389 390 ColorSpaceConnector::ColorSpaceConnector( 391 const ColorSpace& src, 392 const ColorSpace& dst) noexcept 393 : mSource(src) 394 , mDestination(dst) { 395 396 if (all(lessThan(abs(src.getWhitePoint() - dst.getWhitePoint()), float2{1e-3f}))) { 397 mTransform = dst.getXYZtoRGB() * src.getRGBtoXYZ(); 398 } else { 399 mat3 rgbToXYZ(src.getRGBtoXYZ()); 400 mat3 xyzToRGB(dst.getXYZtoRGB()); 401 402 float3 srcXYZ = ColorSpace::XYZ(float3{src.getWhitePoint(), 1}); 403 float3 dstXYZ = ColorSpace::XYZ(float3{dst.getWhitePoint(), 1}); 404 405 if (any(greaterThan(abs(src.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { 406 rgbToXYZ = adaptation(BRADFORD, srcXYZ, ILLUMINANT_D50_XYZ) * src.getRGBtoXYZ(); 407 } 408 409 if (any(greaterThan(abs(dst.getWhitePoint() - ILLUMINANT_D50_XY), float2{1e-3f}))) { 410 xyzToRGB = inverse(adaptation(BRADFORD, dstXYZ, ILLUMINANT_D50_XYZ) * dst.getRGBtoXYZ()); 411 } 412 413 mTransform = xyzToRGB * rgbToXYZ; 414 } 415 } 416 417 }; // namespace android 418