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