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 "Resources.h" 9 #include "SkCodec.h" 10 #include "SkColorSpace.h" 11 #include "SkColorSpace_XYZ.h" 12 #include "SkColorSpacePriv.h" 13 #include "Test.h" 14 15 #include "png.h" 16 17 static bool almost_equal(float a, float b) { 18 return SkTAbs(a - b) < 0.001f; 19 } 20 21 static void test_space(skiatest::Reporter* r, SkColorSpace* space, 22 const float red[], const float green[], const float blue[], 23 const SkGammaNamed expectedGamma) { 24 25 REPORTER_ASSERT(r, nullptr != space); 26 REPORTER_ASSERT(r, expectedGamma == space->gammaNamed()); 27 28 const SkMatrix44& mat = *space->toXYZD50(); 29 const float src[] = { 30 1, 0, 0, 1, 31 0, 1, 0, 1, 32 0, 0, 1, 1, 33 }; 34 const float* ref[3] = { red, green, blue }; 35 float dst[4]; 36 for (int i = 0; i < 3; ++i) { 37 mat.mapScalars(&src[i*4], dst); 38 REPORTER_ASSERT(r, almost_equal(ref[i][0], dst[0])); 39 REPORTER_ASSERT(r, almost_equal(ref[i][1], dst[1])); 40 REPORTER_ASSERT(r, almost_equal(ref[i][2], dst[2])); 41 } 42 } 43 44 static void test_path(skiatest::Reporter* r, const char* path, 45 const float red[], const float green[], const float blue[], 46 const SkGammaNamed expectedGamma) { 47 std::unique_ptr<SkStream> stream(GetResourceAsStream(path)); 48 REPORTER_ASSERT(r, nullptr != stream); 49 if (!stream) { 50 return; 51 } 52 53 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromStream(std::move(stream))); 54 REPORTER_ASSERT(r, nullptr != codec); 55 if (!codec) { 56 return; 57 } 58 59 SkColorSpace* colorSpace = codec->getInfo().colorSpace(); 60 test_space(r, colorSpace, red, green, blue, expectedGamma); 61 } 62 63 static constexpr float g_sRGB_XYZ[]{ 64 0.4358f, 0.3853f, 0.1430f, // Rx, Gx, Bx 65 0.2224f, 0.7170f, 0.0606f, // Ry, Gy, Gz 66 0.0139f, 0.0971f, 0.7139f, // Rz, Gz, Bz 67 }; 68 69 static constexpr float g_sRGB_R[]{ 0.4358f, 0.2224f, 0.0139f }; 70 static constexpr float g_sRGB_G[]{ 0.3853f, 0.7170f, 0.0971f }; 71 static constexpr float g_sRGB_B[]{ 0.1430f, 0.0606f, 0.7139f }; 72 73 DEF_TEST(ColorSpace_sRGB, r) { 74 test_space(r, SkColorSpace::MakeSRGB().get(), 75 g_sRGB_R, g_sRGB_G, g_sRGB_B, kSRGB_SkGammaNamed); 76 77 } 78 79 DEF_TEST(ColorSpaceParseICCProfiles, r) { 80 81 #if (PNG_LIBPNG_VER_MAJOR > 1) || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR >= 6) 82 test_path(r, "images/color_wheel_with_profile.png", g_sRGB_R, g_sRGB_G, g_sRGB_B, 83 kSRGB_SkGammaNamed); 84 #endif 85 86 const float red[] = { 0.385117f, 0.716904f, 0.0970612f }; 87 const float green[] = { 0.143051f, 0.0606079f, 0.713913f }; 88 const float blue[] = { 0.436035f, 0.222488f, 0.013916f }; 89 test_path(r, "images/icc-v2-gbr.jpg", red, green, blue, k2Dot2Curve_SkGammaNamed); 90 91 test_path(r, "images/webp-color-profile-crash.webp", 92 red, green, blue, kNonStandard_SkGammaNamed); 93 test_path(r, "images/webp-color-profile-lossless.webp", 94 red, green, blue, kNonStandard_SkGammaNamed); 95 test_path(r, "images/webp-color-profile-lossy.webp", 96 red, green, blue, kNonStandard_SkGammaNamed); 97 test_path(r, "images/webp-color-profile-lossy-alpha.webp", 98 red, green, blue, kNonStandard_SkGammaNamed); 99 } 100 101 DEF_TEST(ColorSpaceSRGBCompare, r) { 102 // Create an sRGB color space by name 103 sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::MakeSRGB(); 104 105 // Create an sRGB color space by value 106 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); 107 srgbToxyzD50.set3x3RowMajorf(g_sRGB_XYZ); 108 sk_sp<SkColorSpace> rgbColorSpace = 109 SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, srgbToxyzD50); 110 REPORTER_ASSERT(r, rgbColorSpace == namedColorSpace); 111 112 SkColorSpaceTransferFn srgbFn; 113 srgbFn.fA = (1.0f / 1.055f); 114 srgbFn.fB = (0.055f / 1.055f); 115 srgbFn.fC = (1.0f / 12.92f); 116 srgbFn.fD = 0.04045f; 117 srgbFn.fE = 0.0f; 118 srgbFn.fF = 0.0f; 119 srgbFn.fG = 2.4f; 120 sk_sp<SkColorSpace> rgbColorSpace2 = SkColorSpace::MakeRGB(srgbFn, srgbToxyzD50); 121 REPORTER_ASSERT(r, rgbColorSpace2 == namedColorSpace); 122 123 // Change a single value from the sRGB matrix 124 srgbToxyzD50.set(2, 2, 0.5f); 125 sk_sp<SkColorSpace> strangeColorSpace = 126 SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, srgbToxyzD50); 127 REPORTER_ASSERT(r, strangeColorSpace != namedColorSpace); 128 } 129 130 DEF_TEST(ColorSpaceSRGBLinearCompare, r) { 131 // Create the linear sRGB color space by name 132 sk_sp<SkColorSpace> namedColorSpace = SkColorSpace::MakeSRGBLinear(); 133 134 // Create the linear sRGB color space via the sRGB color space's makeLinearGamma() 135 auto srgb = SkColorSpace::MakeSRGB(); 136 auto srgbXYZ = static_cast<SkColorSpace_XYZ*>(srgb.get()); 137 sk_sp<SkColorSpace> viaSrgbColorSpace = srgbXYZ->makeLinearGamma(); 138 REPORTER_ASSERT(r, namedColorSpace == viaSrgbColorSpace); 139 140 // Create a linear sRGB color space by value 141 SkMatrix44 srgbToxyzD50(SkMatrix44::kUninitialized_Constructor); 142 srgbToxyzD50.set3x3RowMajorf(g_sRGB_XYZ); 143 sk_sp<SkColorSpace> rgbColorSpace = 144 SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, srgbToxyzD50); 145 REPORTER_ASSERT(r, rgbColorSpace == namedColorSpace); 146 147 SkColorSpaceTransferFn linearExpFn; 148 linearExpFn.fA = 1.0f; 149 linearExpFn.fB = 0.0f; 150 linearExpFn.fC = 0.0f; 151 linearExpFn.fD = 0.0f; 152 linearExpFn.fE = 0.0f; 153 linearExpFn.fF = 0.0f; 154 linearExpFn.fG = 1.0f; 155 sk_sp<SkColorSpace> rgbColorSpace2 = SkColorSpace::MakeRGB(linearExpFn, srgbToxyzD50); 156 REPORTER_ASSERT(r, rgbColorSpace2 == namedColorSpace); 157 158 SkColorSpaceTransferFn linearFn; 159 linearFn.fA = 0.0f; 160 linearFn.fB = 0.0f; 161 linearFn.fC = 1.0f; 162 linearFn.fD = 1.0f; 163 linearFn.fE = 0.0f; 164 linearFn.fF = 0.0f; 165 linearFn.fG = 0.0f; 166 sk_sp<SkColorSpace> rgbColorSpace3 = SkColorSpace::MakeRGB(linearFn, srgbToxyzD50); 167 REPORTER_ASSERT(r, rgbColorSpace3 == namedColorSpace); 168 169 // Change a single value from the sRGB matrix 170 srgbToxyzD50.set(2, 2, 0.5f); 171 sk_sp<SkColorSpace> strangeColorSpace = 172 SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, srgbToxyzD50); 173 REPORTER_ASSERT(r, strangeColorSpace != namedColorSpace); 174 } 175 176 static void test_serialize(skiatest::Reporter* r, SkColorSpace* space, bool isNamed) { 177 sk_sp<SkData> data1 = space->serialize(); 178 179 size_t bytes = space->writeToMemory(nullptr); 180 sk_sp<SkData> data2 = SkData::MakeUninitialized(bytes); 181 space->writeToMemory(data2->writable_data()); 182 183 sk_sp<SkColorSpace> newSpace1 = SkColorSpace::Deserialize(data1->data(), data1->size()); 184 sk_sp<SkColorSpace> newSpace2 = SkColorSpace::Deserialize(data2->data(), data2->size()); 185 186 if (isNamed) { 187 REPORTER_ASSERT(r, space == newSpace1.get()); 188 REPORTER_ASSERT(r, space == newSpace2.get()); 189 } else { 190 REPORTER_ASSERT(r, SkColorSpace::Equals(space, newSpace1.get())); 191 REPORTER_ASSERT(r, SkColorSpace::Equals(space, newSpace2.get())); 192 } 193 } 194 195 DEF_TEST(ColorSpace_Serialize, r) { 196 test_serialize(r, SkColorSpace::MakeSRGB().get(), true); 197 test_serialize(r, SkColorSpace::MakeSRGBLinear().get(), true); 198 199 sk_sp<SkData> monitorData = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); 200 test_serialize(r, SkColorSpace::MakeICC(monitorData->data(), monitorData->size()).get(), false); 201 monitorData = GetResourceAsData("icc_profiles/HP_Z32x.icc"); 202 test_serialize(r, SkColorSpace::MakeICC(monitorData->data(), monitorData->size()).get(), false); 203 monitorData = GetResourceAsData("icc_profiles/upperLeft.icc"); 204 test_serialize(r, SkColorSpace::MakeICC(monitorData->data(), monitorData->size()).get(), false); 205 monitorData = GetResourceAsData("icc_profiles/upperRight.icc"); 206 test_serialize(r, SkColorSpace::MakeICC(monitorData->data(), monitorData->size()).get(), false); 207 208 SkColorSpaceTransferFn fn; 209 fn.fA = 1.0f; 210 fn.fB = 0.0f; 211 fn.fC = 1.0f; 212 fn.fD = 0.5f; 213 fn.fE = 0.0f; 214 fn.fF = 0.0f; 215 fn.fG = 1.0f; 216 SkMatrix44 toXYZ(SkMatrix44::kIdentity_Constructor); 217 test_serialize(r, SkColorSpace::MakeRGB(fn, toXYZ).get(), false); 218 } 219 220 DEF_TEST(ColorSpace_Equals, r) { 221 sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB(); 222 sk_sp<SkData> data = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); 223 sk_sp<SkColorSpace> z30 = SkColorSpace::MakeICC(data->data(), data->size()); 224 data = GetResourceAsData("icc_profiles/HP_Z32x.icc"); 225 sk_sp<SkColorSpace> z32 = SkColorSpace::MakeICC(data->data(), data->size()); 226 data = GetResourceAsData("icc_profiles/upperLeft.icc"); 227 sk_sp<SkColorSpace> upperLeft = SkColorSpace::MakeICC(data->data(), data->size()); 228 data = GetResourceAsData("icc_profiles/upperRight.icc"); 229 sk_sp<SkColorSpace> upperRight = SkColorSpace::MakeICC(data->data(), data->size()); 230 231 SkColorSpaceTransferFn fn; 232 fn.fA = 1.0f; 233 fn.fB = 0.0f; 234 fn.fC = 1.0f; 235 fn.fD = 0.5f; 236 fn.fE = 0.0f; 237 fn.fF = 0.0f; 238 fn.fG = 1.0f; 239 SkMatrix44 toXYZ(SkMatrix44::kIdentity_Constructor); 240 sk_sp<SkColorSpace> rgb4 = SkColorSpace::MakeRGB(fn, toXYZ); 241 242 REPORTER_ASSERT(r, SkColorSpace::Equals(nullptr, nullptr)); 243 REPORTER_ASSERT(r, SkColorSpace::Equals(srgb.get(), srgb.get())); 244 REPORTER_ASSERT(r, SkColorSpace::Equals(z30.get(), z30.get())); 245 REPORTER_ASSERT(r, SkColorSpace::Equals(z32.get(), z32.get())); 246 REPORTER_ASSERT(r, SkColorSpace::Equals(upperLeft.get(), upperLeft.get())); 247 REPORTER_ASSERT(r, SkColorSpace::Equals(upperRight.get(), upperRight.get())); 248 REPORTER_ASSERT(r, SkColorSpace::Equals(rgb4.get(), rgb4.get())); 249 250 REPORTER_ASSERT(r, !SkColorSpace::Equals(nullptr, srgb.get())); 251 REPORTER_ASSERT(r, !SkColorSpace::Equals(srgb.get(), nullptr)); 252 REPORTER_ASSERT(r, !SkColorSpace::Equals(z30.get(), srgb.get())); 253 REPORTER_ASSERT(r, !SkColorSpace::Equals(z32.get(), z30.get())); 254 REPORTER_ASSERT(r, !SkColorSpace::Equals(upperLeft.get(), srgb.get())); 255 REPORTER_ASSERT(r, !SkColorSpace::Equals(upperLeft.get(), upperRight.get())); 256 REPORTER_ASSERT(r, !SkColorSpace::Equals(z30.get(), upperRight.get())); 257 REPORTER_ASSERT(r, !SkColorSpace::Equals(z30.get(), rgb4.get())); 258 REPORTER_ASSERT(r, !SkColorSpace::Equals(srgb.get(), rgb4.get())); 259 } 260 261 static inline bool matrix_almost_equal(const SkMatrix44& a, const SkMatrix44& b) { 262 return almost_equal(a.get(0, 0), b.get(0, 0)) && 263 almost_equal(a.get(0, 1), b.get(0, 1)) && 264 almost_equal(a.get(0, 2), b.get(0, 2)) && 265 almost_equal(a.get(0, 3), b.get(0, 3)) && 266 almost_equal(a.get(1, 0), b.get(1, 0)) && 267 almost_equal(a.get(1, 1), b.get(1, 1)) && 268 almost_equal(a.get(1, 2), b.get(1, 2)) && 269 almost_equal(a.get(1, 3), b.get(1, 3)) && 270 almost_equal(a.get(2, 0), b.get(2, 0)) && 271 almost_equal(a.get(2, 1), b.get(2, 1)) && 272 almost_equal(a.get(2, 2), b.get(2, 2)) && 273 almost_equal(a.get(2, 3), b.get(2, 3)) && 274 almost_equal(a.get(3, 0), b.get(3, 0)) && 275 almost_equal(a.get(3, 1), b.get(3, 1)) && 276 almost_equal(a.get(3, 2), b.get(3, 2)) && 277 almost_equal(a.get(3, 3), b.get(3, 3)); 278 } 279 280 static inline void check_primaries(skiatest::Reporter* r, const SkColorSpacePrimaries& primaries, 281 const SkMatrix44& reference) { 282 SkMatrix44 toXYZ(SkMatrix44::kUninitialized_Constructor); 283 bool result = primaries.toXYZD50(&toXYZ); 284 REPORTER_ASSERT(r, result); 285 REPORTER_ASSERT(r, matrix_almost_equal(toXYZ, reference)); 286 } 287 288 DEF_TEST(ColorSpace_Primaries, r) { 289 // sRGB primaries (D65) 290 SkColorSpacePrimaries srgb; 291 srgb.fRX = 0.64f; 292 srgb.fRY = 0.33f; 293 srgb.fGX = 0.30f; 294 srgb.fGY = 0.60f; 295 srgb.fBX = 0.15f; 296 srgb.fBY = 0.06f; 297 srgb.fWX = 0.3127f; 298 srgb.fWY = 0.3290f; 299 SkMatrix44 srgbToXYZ(SkMatrix44::kUninitialized_Constructor); 300 bool result = srgb.toXYZD50(&srgbToXYZ); 301 REPORTER_ASSERT(r, result); 302 303 sk_sp<SkColorSpace> space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, 304 srgbToXYZ); 305 REPORTER_ASSERT(r, SkColorSpace::MakeSRGB() == space); 306 307 // ProPhoto (D50) 308 SkColorSpacePrimaries proPhoto; 309 proPhoto.fRX = 0.7347f; 310 proPhoto.fRY = 0.2653f; 311 proPhoto.fGX = 0.1596f; 312 proPhoto.fGY = 0.8404f; 313 proPhoto.fBX = 0.0366f; 314 proPhoto.fBY = 0.0001f; 315 proPhoto.fWX = 0.34567f; 316 proPhoto.fWY = 0.35850f; 317 SkMatrix44 proToXYZ(SkMatrix44::kUninitialized_Constructor); 318 proToXYZ.set3x3(0.7976749f, 0.2880402f, 0.0000000f, 319 0.1351917f, 0.7118741f, 0.0000000f, 320 0.0313534f, 0.0000857f, 0.8252100f); 321 check_primaries(r, proPhoto, proToXYZ); 322 323 // NTSC (C) 324 SkColorSpacePrimaries ntsc; 325 ntsc.fRX = 0.67f; 326 ntsc.fRY = 0.33f; 327 ntsc.fGX = 0.21f; 328 ntsc.fGY = 0.71f; 329 ntsc.fBX = 0.14f; 330 ntsc.fBY = 0.08f; 331 ntsc.fWX = 0.31006f; 332 ntsc.fWY = 0.31616f; 333 SkMatrix44 ntscToXYZ(SkMatrix44::kUninitialized_Constructor); 334 ntscToXYZ.set3x3(0.6343706f, 0.3109496f, -0.0011817f, 335 0.1852204f, 0.5915984f, 0.0555518f, 336 0.1446290f, 0.0974520f, 0.7708399f); 337 check_primaries(r, ntsc, ntscToXYZ); 338 339 // DCI P3 (D65) 340 SkColorSpacePrimaries p3; 341 p3.fRX = 0.680f; 342 p3.fRY = 0.320f; 343 p3.fGX = 0.265f; 344 p3.fGY = 0.690f; 345 p3.fBX = 0.150f; 346 p3.fBY = 0.060f; 347 p3.fWX = 0.3127f; 348 p3.fWY = 0.3290f; 349 space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, 350 SkColorSpace::kDCIP3_D65_Gamut); 351 SkMatrix44 reference(SkMatrix44::kUninitialized_Constructor); 352 SkAssertResult(space->toXYZD50(&reference)); 353 check_primaries(r, p3, reference); 354 355 // Rec 2020 (D65) 356 SkColorSpacePrimaries rec2020; 357 rec2020.fRX = 0.708f; 358 rec2020.fRY = 0.292f; 359 rec2020.fGX = 0.170f; 360 rec2020.fGY = 0.797f; 361 rec2020.fBX = 0.131f; 362 rec2020.fBY = 0.046f; 363 rec2020.fWX = 0.3127f; 364 rec2020.fWY = 0.3290f; 365 space = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, 366 SkColorSpace::kRec2020_Gamut); 367 SkAssertResult(space->toXYZD50(&reference)); 368 check_primaries(r, rec2020, reference); 369 } 370 371 DEF_TEST(ColorSpace_InvalidICC, r) { 372 // This color space has a matrix that is not D50. 373 sk_sp<SkData> data = GetResourceAsData("icc_profiles/SM2333SW.icc"); 374 if (!data) { 375 return; 376 } 377 sk_sp<SkColorSpace> cs = SkColorSpace::MakeICC(data->data(), data->size()); 378 REPORTER_ASSERT(r, !cs); 379 380 // The color space has a color lut with only one entry in each dimension. 381 data = GetResourceAsData("icc_profiles/invalid_color_lut.icc"); 382 if (!data) { 383 return; 384 } 385 386 cs = SkColorSpace::MakeICC(data->data(), data->size()); 387 REPORTER_ASSERT(r, !cs); 388 } 389 390 DEF_TEST(ColorSpace_MatrixHash, r) { 391 sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB(); 392 393 SkColorSpaceTransferFn fn; 394 fn.fA = 1.0f; 395 fn.fB = 0.0f; 396 fn.fC = 0.0f; 397 fn.fD = 0.0f; 398 fn.fE = 0.0f; 399 fn.fF = 0.0f; 400 fn.fG = 3.0f; 401 402 SkMatrix44 srgbMat(SkMatrix44::kUninitialized_Constructor); 403 srgbMat.set3x3RowMajorf(gSRGB_toXYZD50); 404 sk_sp<SkColorSpace> strange = SkColorSpace::MakeRGB(fn, srgbMat); 405 406 REPORTER_ASSERT(r, *srgb->toXYZD50() == *strange->toXYZD50()); 407 REPORTER_ASSERT(r, srgb->toXYZD50Hash() == strange->toXYZD50Hash()); 408 } 409 410 DEF_TEST(ColorSpace_IsSRGB, r) { 411 sk_sp<SkColorSpace> srgb0 = SkColorSpace::MakeSRGB(); 412 413 SkColorSpaceTransferFn fn; 414 fn.fA = 1.0f; 415 fn.fB = 0.0f; 416 fn.fC = 0.0f; 417 fn.fD = 0.0f; 418 fn.fE = 0.0f; 419 fn.fF = 0.0f; 420 fn.fG = 2.2f; 421 sk_sp<SkColorSpace> twoDotTwo = SkColorSpace::MakeRGB(fn, SkColorSpace::kSRGB_Gamut); 422 423 REPORTER_ASSERT(r, srgb0->isSRGB()); 424 REPORTER_ASSERT(r, !twoDotTwo->isSRGB()); 425 } 426