SkColorSpace.cpp revision 959ccc1f3f49e1ddeb51c32c30ac4a2d94653856
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 SkColorSpace* sRGB; 131 static SkOnce adobeRGBOnce; 132 static 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 = new SkColorSpace_Base(kSRGB_GammaNamed, srgbToxyzD50, kSRGB_Named); 140 }); 141 return sk_ref_sp(sRGB); 142 } 143 case kAdobeRGB_Named: { 144 adobeRGBOnce([] { 145 SkMatrix44 adobergbToxyzD50(SkMatrix44::kUninitialized_Constructor); 146 adobergbToxyzD50.set3x3RowMajorf(gAdobeRGB_toXYZD50); 147 adobeRGB = new SkColorSpace_Base(k2Dot2Curve_GammaNamed, adobergbToxyzD50, 148 kAdobeRGB_Named); 149 }); 150 return sk_ref_sp(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 203sk_sp<SkData> SkColorSpace::serialize() const { 204 // If we have a named profile, only write the enum. 205 switch (fNamed) { 206 case kSRGB_Named: 207 case kAdobeRGB_Named: { 208 sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader)); 209 *((ColorSpaceHeader*) data->writable_data()) = 210 ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 0); 211 return data; 212 } 213 default: 214 break; 215 } 216 217 // If we have a named gamma, write the enum and the matrix. 218 switch (fGammaNamed) { 219 case kSRGB_GammaNamed: 220 case k2Dot2Curve_GammaNamed: 221 case kLinear_GammaNamed: { 222 sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) + 223 12 * sizeof(float)); 224 void* dataPtr = data->writable_data(); 225 226 *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 227 ColorSpaceHeader::kMatrix_Flag); 228 dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader)); 229 230 fToXYZD50.as4x3ColMajorf((float*) dataPtr); 231 return data; 232 } 233 default: 234 break; 235 } 236 237 // If we do not have a named gamma, this must have been created from an ICC profile. 238 // Since we were unable to recognize the gamma, we will have saved the ICC data. 239 SkASSERT(as_CSB(this)->fProfileData); 240 241 size_t profileSize = as_CSB(this)->fProfileData->size(); 242 if (SkAlign4(profileSize) != (uint32_t) SkAlign4(profileSize)) { 243 return nullptr; 244 } 245 246 sk_sp<SkData> data = SkData::MakeUninitialized(sizeof(ColorSpaceHeader) + sizeof(uint32_t) + 247 SkAlign4(profileSize)); 248 void* dataPtr = data->writable_data(); 249 250 *((ColorSpaceHeader*) dataPtr) = ColorSpaceHeader::Pack(k0_Version, fNamed, fGammaNamed, 251 ColorSpaceHeader::kICC_Flag); 252 dataPtr = SkTAddOffset<void>(dataPtr, sizeof(ColorSpaceHeader)); 253 254 *((uint32_t*) dataPtr) = (uint32_t) SkAlign4(profileSize); 255 dataPtr = SkTAddOffset<void>(dataPtr, sizeof(uint32_t)); 256 257 memcpy(dataPtr, as_CSB(this)->fProfileData->data(), profileSize); 258 memset(SkTAddOffset<void>(dataPtr, profileSize), 0, SkAlign4(profileSize) - profileSize); 259 return data; 260} 261 262sk_sp<SkColorSpace> SkColorSpace::Deserialize(const void* data, size_t length) { 263 if (length < sizeof(ColorSpaceHeader)) { 264 return nullptr; 265 } 266 267 ColorSpaceHeader header = *((const ColorSpaceHeader*) data); 268 data = SkTAddOffset<const void>(data, sizeof(ColorSpaceHeader)); 269 length -= sizeof(ColorSpaceHeader); 270 switch ((Named) header.fNamed) { 271 case kSRGB_Named: 272 case kAdobeRGB_Named: 273 return NewNamed((Named) header.fNamed); 274 default: 275 break; 276 } 277 278 switch ((GammaNamed) header.fGammaNamed) { 279 case kSRGB_GammaNamed: 280 case k2Dot2Curve_GammaNamed: 281 case kLinear_GammaNamed: { 282 if (ColorSpaceHeader::kMatrix_Flag != header.fFlags || length < 12 * sizeof(float)) { 283 return nullptr; 284 } 285 286 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 287 toXYZ.set4x3ColMajorf((const float*) data); 288 return NewRGB((GammaNamed) header.fGammaNamed, toXYZ); 289 } 290 default: 291 break; 292 } 293 294 if (ColorSpaceHeader::kICC_Flag != header.fFlags || length < sizeof(uint32_t)) { 295 return nullptr; 296 } 297 298 uint32_t profileSize = *((uint32_t*) data); 299 data = SkTAddOffset<const void>(data, sizeof(uint32_t)); 300 length -= sizeof(uint32_t); 301 if (length < profileSize) { 302 return nullptr; 303 } 304 305 return NewICC(data, profileSize); 306} 307