SkColorSpace.cpp revision 94d7872ab3b0a32675b8e826637665fd3ada5b35
1/* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkColorSpace.h" 9#include "SkColorSpace_Base.h" 10#include "SkColorSpacePriv.h" 11#include "SkOnce.h" 12 13SkColorSpace::SkColorSpace(const SkMatrix44& toXYZD50) 14 : fToXYZD50(toXYZD50) 15{} 16 17SkColorSpace_Base::SkColorSpace_Base(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) 18 : INHERITED(toXYZD50) 19 , fGammaNamed(gammaNamed) 20 , fGammas(nullptr) 21 , fProfileData(nullptr) 22 , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) 23{} 24 25SkColorSpace_Base::SkColorSpace_Base(sk_sp<SkColorLookUpTable> colorLUT, SkGammaNamed gammaNamed, 26 sk_sp<SkGammas> gammas, const SkMatrix44& toXYZD50, 27 sk_sp<SkData> profileData) 28 : INHERITED(toXYZD50) 29 , fColorLUT(std::move(colorLUT)) 30 , fGammaNamed(gammaNamed) 31 , fGammas(std::move(gammas)) 32 , fProfileData(std::move(profileData)) 33 , fFromXYZD50(SkMatrix44::kUninitialized_Constructor) 34{} 35 36static constexpr float gSRGB_toXYZD50[] { 37 0.4358f, 0.3853f, 0.1430f, // Rx, Gx, Bx 38 0.2224f, 0.7170f, 0.0606f, // Ry, Gy, Gz 39 0.0139f, 0.0971f, 0.7139f, // Rz, Gz, Bz 40}; 41 42static constexpr float gAdobeRGB_toXYZD50[] { 43 0.6098f, 0.2052f, 0.1492f, // Rx, Gx, Bx 44 0.3111f, 0.6257f, 0.0632f, // Ry, Gy, By 45 0.0195f, 0.0609f, 0.7448f, // Rz, Gz, Bz 46}; 47 48/** 49 * Checks if our toXYZ matrix is a close match to a known color gamut. 50 * 51 * @param toXYZD50 transformation matrix deduced from profile data 52 * @param standard 3x3 canonical transformation matrix 53 */ 54static bool xyz_almost_equal(const SkMatrix44& toXYZD50, const float* standard) { 55 return color_space_almost_equal(toXYZD50.getFloat(0, 0), standard[0]) && 56 color_space_almost_equal(toXYZD50.getFloat(0, 1), standard[1]) && 57 color_space_almost_equal(toXYZD50.getFloat(0, 2), standard[2]) && 58 color_space_almost_equal(toXYZD50.getFloat(1, 0), standard[3]) && 59 color_space_almost_equal(toXYZD50.getFloat(1, 1), standard[4]) && 60 color_space_almost_equal(toXYZD50.getFloat(1, 2), standard[5]) && 61 color_space_almost_equal(toXYZD50.getFloat(2, 0), standard[6]) && 62 color_space_almost_equal(toXYZD50.getFloat(2, 1), standard[7]) && 63 color_space_almost_equal(toXYZD50.getFloat(2, 2), standard[8]) && 64 color_space_almost_equal(toXYZD50.getFloat(0, 3), 0.0f) && 65 color_space_almost_equal(toXYZD50.getFloat(1, 3), 0.0f) && 66 color_space_almost_equal(toXYZD50.getFloat(2, 3), 0.0f) && 67 color_space_almost_equal(toXYZD50.getFloat(3, 0), 0.0f) && 68 color_space_almost_equal(toXYZD50.getFloat(3, 1), 0.0f) && 69 color_space_almost_equal(toXYZD50.getFloat(3, 2), 0.0f) && 70 color_space_almost_equal(toXYZD50.getFloat(3, 3), 1.0f); 71} 72 73sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(const float values[3], const SkMatrix44& toXYZD50) { 74 if (0.0f > values[0] || 0.0f > values[1] || 0.0f > values[2]) { 75 return nullptr; 76 } 77 78 SkGammaNamed gammaNamed = kNonStandard_SkGammaNamed; 79 if (color_space_almost_equal(2.2f, values[0]) && 80 color_space_almost_equal(2.2f, values[1]) && 81 color_space_almost_equal(2.2f, values[2])) { 82 gammaNamed = k2Dot2Curve_SkGammaNamed; 83 } else if (color_space_almost_equal(1.0f, values[0]) && 84 color_space_almost_equal(1.0f, values[1]) && 85 color_space_almost_equal(1.0f, values[2])) { 86 gammaNamed = kLinear_SkGammaNamed; 87 } 88 89 if (kNonStandard_SkGammaNamed == gammaNamed) { 90 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas()); 91 gammas->fRedType = SkGammas::Type::kValue_Type; 92 gammas->fGreenType = SkGammas::Type::kValue_Type; 93 gammas->fBlueType = SkGammas::Type::kValue_Type; 94 gammas->fRedData.fValue = values[0]; 95 gammas->fGreenData.fValue = values[1]; 96 gammas->fBlueData.fValue = values[2]; 97 return sk_sp<SkColorSpace>(new SkColorSpace_Base(nullptr, kNonStandard_SkGammaNamed, gammas, 98 toXYZD50, nullptr)); 99 } 100 101 return SkColorSpace_Base::NewRGB(gammaNamed, toXYZD50); 102} 103 104sk_sp<SkColorSpace> SkColorSpace_Base::NewRGB(SkGammaNamed gammaNamed, const SkMatrix44& toXYZD50) { 105 switch (gammaNamed) { 106 case kSRGB_SkGammaNamed: 107 if (xyz_almost_equal(toXYZD50, gSRGB_toXYZD50)) { 108 return SkColorSpace::NewNamed(kSRGB_Named); 109 } 110 break; 111 case k2Dot2Curve_SkGammaNamed: 112 if (xyz_almost_equal(toXYZD50, gAdobeRGB_toXYZD50)) { 113 return SkColorSpace::NewNamed(kAdobeRGB_Named); 114 } 115 break; 116 case kNonStandard_SkGammaNamed: 117 // This is not allowed. 118 return nullptr; 119 default: 120 break; 121 } 122 123 return sk_sp<SkColorSpace>(new SkColorSpace_Base(gammaNamed, toXYZD50)); 124} 125 126sk_sp<SkColorSpace> SkColorSpace::NewRGB(RenderTargetGamma gamma, const SkMatrix44& toXYZD50) { 127 switch (gamma) { 128 case kLinear_RenderTargetGamma: 129 return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, toXYZD50); 130 case kSRGB_RenderTargetGamma: 131 return SkColorSpace_Base::NewRGB(kSRGB_SkGammaNamed, toXYZD50); 132 default: 133 return nullptr; 134 } 135} 136 137static SkColorSpace* gAdobeRGB; 138static SkColorSpace* gSRGB; 139 140sk_sp<SkColorSpace> SkColorSpace::NewNamed(Named named) { 141 static SkOnce sRGBOnce; 142 static SkOnce adobeRGBOnce; 143 144 switch (named) { 145 case kSRGB_Named: { 146 sRGBOnce([] { 147 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); 148 srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50); 149 150 // Force the mutable type mask to be computed. This avoids races. 151 (void)srgbToxyzD50.getType(); 152 gSRGB = new SkColorSpace_Base(kSRGB_SkGammaNamed, srgbToxyzD50); 153 }); 154 return sk_ref_sp<SkColorSpace>(gSRGB); 155 } 156 case kAdobeRGB_Named: { 157 adobeRGBOnce([] { 158 SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor); 159 adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50); 160 161 // Force the mutable type mask to be computed. This avoids races. 162 (void)adobergbToxyzD50.getType(); 163 gAdobeRGB = new SkColorSpace_Base(k2Dot2Curve_SkGammaNamed, adobergbToxyzD50); 164 }); 165 return sk_ref_sp<SkColorSpace>(gAdobeRGB); 166 } 167 default: 168 break; 169 } 170 return nullptr; 171} 172 173sk_sp<SkColorSpace> SkColorSpace::makeLinearGamma() { 174 if (this->gammaIsLinear()) { 175 return sk_ref_sp(this); 176 } 177 return SkColorSpace_Base::NewRGB(kLinear_SkGammaNamed, fToXYZD50); 178} 179 180/////////////////////////////////////////////////////////////////////////////////////////////////// 181 182bool SkColorSpace::gammaCloseToSRGB() const { 183 return kSRGB_SkGammaNamed == as_CSB(this)->fGammaNamed || 184 k2Dot2Curve_SkGammaNamed == as_CSB(this)->fGammaNamed; 185} 186 187bool SkColorSpace::gammaIsLinear() const { 188 return kLinear_SkGammaNamed == as_CSB(this)->fGammaNamed; 189} 190 191const SkMatrix44& SkColorSpace_Base::fromXYZD50() const { 192 fFromXYZOnce([this] { 193 if (!fToXYZD50.invert(&fFromXYZD50)) { 194 // If a client gives us a dst gamut with a transform that we can't invert, we will 195 // simply give them back a transform to sRGB gamut. 196 SkDEBUGFAIL("Non-invertible XYZ matrix, defaulting to sRGB"); 197 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); 198 srgbToxyzD50.set3x3RowMajorf(gSRGB_toXYZD50); 199 srgbToxyzD50.invert(&fFromXYZD50); 200 } 201 }); 202 return fFromXYZD50; 203} 204 205/////////////////////////////////////////////////////////////////////////////////////////////////// 206 207enum Version { 208 k0_Version, // Initial version, header + flags for matrix and profile 209}; 210 211struct ColorSpaceHeader { 212 /** 213 * If kMatrix_Flag is set, we will write 12 floats after the header. 214 * Should not be set at the same time as the kICC_Flag or kFloatGamma_Flag. 215 */ 216 static constexpr uint8_t kMatrix_Flag = 1 << 0; 217 218 /** 219 * If kICC_Flag is set, we will write an ICC profile after the header. 220 * The ICC profile will be written as a uint32 size, followed immediately 221 * by the data (padded to 4 bytes). 222 * Should not be set at the same time as the kMatrix_Flag or kFloatGamma_Flag. 223 */ 224 static constexpr uint8_t kICC_Flag = 1 << 1; 225 226 /** 227 * If kFloatGamma_Flag is set, we will write 15 floats after the header. 228 * The first three are the gamma values, and the next twelve are the 229 * matrix. 230 * Should not be set at the same time as the kICC_Flag or kMatrix_Flag. 231 */ 232 static constexpr uint8_t kFloatGamma_Flag = 1 << 2; 233 234 static ColorSpaceHeader Pack(Version version, uint8_t named, uint8_t gammaNamed, uint8_t flags) 235 { 236 ColorSpaceHeader header; 237 238 SkASSERT(k0_Version == version); 239 header.fVersion = (uint8_t) version; 240 241 SkASSERT(named <= SkColorSpace::kAdobeRGB_Named); 242 header.fNamed = (uint8_t) named; 243 244 SkASSERT(gammaNamed <= kNonStandard_SkGammaNamed); 245 header.fGammaNamed = (uint8_t) gammaNamed; 246 247 SkASSERT(flags <= kFloatGamma_Flag); 248 header.fFlags = flags; 249 return header; 250 } 251 252 uint8_t fVersion; // Always zero 253 uint8_t fNamed; // Must be a SkColorSpace::Named 254 uint8_t fGammaNamed; // Must be a SkGammaNamed 255 uint8_t fFlags; // Some combination of the flags listed above 256}; 257 258size_t SkColorSpace::writeToMemory(void* memory) const { 259 // Start by trying the serialization fast path. If we haven't saved ICC profile data, 260 // we must have a profile that we can serialize easily. 261 if (!as_CSB(this)->fProfileData) { 262 // If we have a named profile, only write the enum. 263 if (this == gSRGB) { 264 if (memory) { 265 *((ColorSpaceHeader*) memory) = 266 ColorSpaceHeader::Pack(k0_Version, kSRGB_Named, 267 as_CSB(this)->fGammaNamed, 0); 268 } 269 return sizeof(ColorSpaceHeader); 270 } else if (this == gAdobeRGB) { 271 if (memory) { 272 *((ColorSpaceHeader*) memory) = 273 ColorSpaceHeader::Pack(k0_Version, kAdobeRGB_Named, 274 as_CSB(this)->fGammaNamed, 0); 275 } 276 } 277 278 // If we have a named gamma, write the enum and the matrix. 279 switch (as_CSB(this)->fGammaNamed) { 280 case kSRGB_SkGammaNamed: 281 case k2Dot2Curve_SkGammaNamed: 282 case kLinear_SkGammaNamed: { 283 if (memory) { 284 *((ColorSpaceHeader*) memory) = 285 ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed, 286 ColorSpaceHeader::kMatrix_Flag); 287 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 288 fToXYZD50.as3x4RowMajorf((float*) memory); 289 } 290 return sizeof(ColorSpaceHeader) + 12 * sizeof(float); 291 } 292 default: 293 // Otherwise, write the gamma values and the matrix. 294 if (memory) { 295 *((ColorSpaceHeader*) memory) = 296 ColorSpaceHeader::Pack(k0_Version, 0, as_CSB(this)->fGammaNamed, 297 ColorSpaceHeader::kFloatGamma_Flag); 298 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 299 300 const SkGammas* gammas = as_CSB(this)->gammas(); 301 SkASSERT(gammas); 302 SkASSERT(SkGammas::Type::kValue_Type == gammas->fRedType && 303 SkGammas::Type::kValue_Type == gammas->fGreenType && 304 SkGammas::Type::kValue_Type == gammas->fBlueType); 305 *(((float*) memory) + 0) = gammas->fRedData.fValue; 306 *(((float*) memory) + 1) = gammas->fGreenData.fValue; 307 *(((float*) memory) + 2) = gammas->fBlueData.fValue; 308 memory = SkTAddOffset<void>(memory, 3 * sizeof(float)); 309 310 fToXYZD50.as3x4RowMajorf((float*) memory); 311 } 312 return sizeof(ColorSpaceHeader) + 15 * sizeof(float); 313 } 314 } 315 316 // Otherwise, serialize the ICC data. 317 size_t profileSize = as_CSB(this)->fProfileData->size(); 318 if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) { 319 return 0; 320 } 321 322 if (memory) { 323 *((ColorSpaceHeader*) memory) = ColorSpaceHeader::Pack(k0_Version, 0, 324 kNonStandard_SkGammaNamed, 325 ColorSpaceHeader::kICC_Flag); 326 memory = SkTAddOffset<void>(memory, sizeof(ColorSpaceHeader)); 327 328 *((uint32_t*) memory) = (uint32_t) SkAlign4(profileSize); 329 memory = SkTAddOffset<void>(memory, sizeof(uint32_t)); 330 331 memcpy(memory, as_CSB(this)->fProfileData->data(), profileSize); 332 memset(SkTAddOffset<void>(memory, profileSize), 0, SkAlign4(profileSize) - profileSize); 333 } 334 return sizeof(ColorSpaceHeader) + sizeof(uint32_t) + SkAlign4(profileSize); 335} 336 337sk_sp<SkData> SkColorSpace::serialize() const { 338 size_t size = this->writeToMemory(nullptr); 339 if (0 == size) { 340 return nullptr; 341 } 342 343 sk_sp<SkData> data = SkData::MakeUninitialized(size); 344 this->writeToMemory(data->writable_data()); 345 return data; 346} 347 348sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) { 349 if (length < sizeof(ColorSpaceHeader)) { 350 return nullptr; 351 } 352 353 ColorSpaceHeader header = *((const ColorSpaceHeader*) data); 354 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader)); 355 length -= sizeof(ColorSpaceHeader); 356 if (0 == header.fFlags) { 357 return NewNamed((Named) header.fNamed); 358 } 359 360 switch ((SkGammaNamed) header.fGammaNamed) { 361 case kSRGB_SkGammaNamed: 362 case k2Dot2Curve_SkGammaNamed: 363 case kLinear_SkGammaNamed: { 364 if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { 365 return nullptr; 366 } 367 368 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 369 toXYZ.set3x4RowMajorf((const float*) data); 370 return SkColorSpace_Base::NewRGB((SkGammaNamed) header.fGammaNamed, toXYZ); 371 } 372 default: 373 break; 374 } 375 376 switch (header.fFlags) { 377 case ColorSpaceHeader::kICC_Flag: { 378 if (length < sizeof(uint32_t)) { 379 return nullptr; 380 } 381 382 uint32_t profileSize = *((uint32_t*) data); 383 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); 384 length -= sizeof(uint32_t); 385 if (length < profileSize) { 386 return nullptr; 387 } 388 389 return NewICC(data, profileSize); 390 } 391 case ColorSpaceHeader::kFloatGamma_Flag: { 392 if (length < 15 * sizeof(float)) { 393 return nullptr; 394 } 395 396 float gammas[3]; 397 gammas[0] = *(((const float*) data) + 0); 398 gammas[1] = *(((const float*) data) + 1); 399 gammas[2] = *(((const float*) data) + 2); 400 data = SkTAddOffset<const void>(data, 3 * sizeof(float)); 401 402 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 403 toXYZ.set3x4RowMajorf((const float*) data); 404 return SkColorSpace_Base::NewRGB(gammas, toXYZ); 405 } 406 default: 407 return nullptr; 408 } 409} 410 411bool SkColorSpace::Equals(const SkColorSpace* src, const SkColorSpace* dst) { 412 if (src == dst) { 413 return true; 414 } 415 416 if (!src || !dst) { 417 return false; 418 } 419 420 SkData* srcData = as_CSB(src)->fProfileData.get(); 421 SkData* dstData = as_CSB(dst)->fProfileData.get(); 422 if (srcData || dstData) { 423 if (srcData && dstData) { 424 return srcData->size() == dstData->size() && 425 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); 426 } 427 428 return false; 429 } 430 431 // It's important to check fProfileData before named gammas. Some profiles may have named 432 // gammas, but also include other wacky features that cause us to save the data. 433 switch (as_CSB(src)->fGammaNamed) { 434 case kSRGB_SkGammaNamed: 435 case k2Dot2Curve_SkGammaNamed: 436 case kLinear_SkGammaNamed: 437 return (as_CSB(src)->fGammaNamed == as_CSB(dst)->fGammaNamed) && 438 (src->fToXYZD50 == dst->fToXYZD50); 439 default: 440 if (as_CSB(src)->fGammaNamed != as_CSB(dst)->fGammaNamed) { 441 return false; 442 } 443 444 // It is unlikely that we will reach this case. 445 sk_sp<SkData> srcData = src->serialize(); 446 sk_sp<SkData> dstData = dst->serialize(); 447 return srcData->size() == dstData->size() && 448 0 == memcmp(srcData->data(), dstData->data(), srcData->size()); 449 } 450} 451