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 "SkCodecPriv.h" 11 #include "SkColorPriv.h" 12 #include "SkColorSpace.h" 13 #include "SkColorSpace_A2B.h" 14 #include "SkColorSpace_XYZ.h" 15 #include "SkColorSpaceXform_Base.h" 16 #include "Test.h" 17 18 static constexpr int kChannels = 3; 19 20 class ColorSpaceXformTest { 21 public: 22 static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) { 23 // Logically we can pass any matrix here. For simplicty, pass I(), i.e. D50 XYZ gamut. 24 sk_sp<SkColorSpace> space(new SkColorSpace_XYZ( 25 kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr)); 26 27 // Use special testing entry point, so we don't skip the xform, even though src == dst. 28 return SlowIdentityXform(static_cast<SkColorSpace_XYZ*>(space.get())); 29 } 30 31 static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform_A2B( 32 SkGammaNamed gammaNamed, const sk_sp<SkGammas>& gammas) { 33 std::vector<SkColorSpace_A2B::Element> srcElements; 34 // sRGB 35 const float values[16] = { 36 0.4358f, 0.3853f, 0.1430f, 0.0f, 37 0.2224f, 0.7170f, 0.0606f, 0.0f, 38 0.0139f, 0.0971f, 0.7139f, 0.0f, 39 0.0000f, 0.0000f, 0.0000f, 1.0f 40 }; 41 SkMatrix44 arbitraryMatrix{SkMatrix44::kUninitialized_Constructor}; 42 arbitraryMatrix.setRowMajorf(values); 43 if (kNonStandard_SkGammaNamed == gammaNamed) { 44 SkASSERT(gammas); 45 srcElements.push_back(SkColorSpace_A2B::Element(gammas)); 46 } else { 47 srcElements.push_back(SkColorSpace_A2B::Element(gammaNamed, kChannels)); 48 } 49 srcElements.push_back(SkColorSpace_A2B::Element(arbitraryMatrix)); 50 auto srcSpace = 51 ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ, 52 std::move(srcElements)); 53 sk_sp<SkColorSpace> dstSpace(new SkColorSpace_XYZ(gammaNamed, gammas, arbitraryMatrix, 54 nullptr)); 55 56 return SkColorSpaceXform::New(static_cast<SkColorSpace_A2B*>(srcSpace.get()), 57 static_cast<SkColorSpace_XYZ*>(dstSpace.get())); 58 } 59 60 static sk_sp<SkColorSpace> CreateA2BSpace(SkColorSpace_A2B::PCS pcs, 61 std::vector<SkColorSpace_A2B::Element> elements) { 62 return sk_sp<SkColorSpace>(new SkColorSpace_A2B(SkColorSpace::kRGB_Type, 63 std::move(elements), 64 pcs, nullptr)); 65 } 66 }; 67 68 static bool almost_equal(int x, int y, int tol=1) { 69 return SkTAbs(x-y) <= tol; 70 } 71 72 static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas, 73 bool repeat) { 74 // Arbitrary set of 10 pixels 75 constexpr int width = 10; 76 constexpr uint32_t srcPixels[width] = { 77 0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271, 78 0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, }; 79 uint32_t dstPixels[width]; 80 81 // Create and perform an identity xform. 82 std::unique_ptr<SkColorSpaceXform> xform = ColorSpaceXformTest::CreateIdentityXform(gammas); 83 bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels, 84 SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width, 85 kOpaque_SkAlphaType); 86 REPORTER_ASSERT(r, result); 87 88 // Since the src->dst matrix is the identity, and the gamma curves match, 89 // the pixels should be unchanged. 90 for (int i = 0; i < width; i++) { 91 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF), 92 SkGetPackedB32(dstPixels[i]))); 93 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF), 94 SkGetPackedG32(dstPixels[i]))); 95 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF), 96 SkGetPackedR32(dstPixels[i]))); 97 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF), 98 SkGetPackedA32(dstPixels[i]))); 99 } 100 101 if (repeat) { 102 // We should cache part of the transform after the run. So it is interesting 103 // to make sure it still runs correctly the second time. 104 test_identity_xform(r, gammas, false); 105 } 106 } 107 108 static void test_identity_xform_A2B(skiatest::Reporter* r, SkGammaNamed gammaNamed, 109 const sk_sp<SkGammas>& gammas, int tol=1) { 110 // Arbitrary set of 10 pixels 111 constexpr int width = 10; 112 constexpr uint32_t srcPixels[width] = { 113 0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271, 114 0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, }; 115 uint32_t dstPixels[width]; 116 117 // Create and perform an identity xform. 118 auto xform = ColorSpaceXformTest::CreateIdentityXform_A2B(gammaNamed, gammas); 119 bool result = xform->apply(select_xform_format(kN32_SkColorType), dstPixels, 120 SkColorSpaceXform::kBGRA_8888_ColorFormat, srcPixels, width, 121 kOpaque_SkAlphaType); 122 REPORTER_ASSERT(r, result); 123 124 // Since the src->dst matrix is the identity, and the gamma curves match, 125 // the pixels should be ~unchanged. 126 for (int i = 0; i < width; i++) { 127 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF), 128 SkGetPackedB32(dstPixels[i]), tol)); 129 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF), 130 SkGetPackedG32(dstPixels[i]), tol)); 131 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF), 132 SkGetPackedR32(dstPixels[i]), tol)); 133 REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF), 134 SkGetPackedA32(dstPixels[i]), tol)); 135 } 136 } 137 138 DEF_TEST(ColorSpaceXform_TableGamma, r) { 139 // Lookup-table based gamma curves 140 constexpr size_t tableSize = 10; 141 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize); 142 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); 143 for (int i = 0; i < kChannels; ++i) { 144 gammas->fType[i] = SkGammas::Type::kTable_Type; 145 gammas->fData[i].fTable.fSize = tableSize; 146 gammas->fData[i].fTable.fOffset = 0; 147 } 148 149 float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); 150 table[0] = 0.00f; 151 table[1] = 0.05f; 152 table[2] = 0.10f; 153 table[3] = 0.15f; 154 table[4] = 0.25f; 155 table[5] = 0.35f; 156 table[6] = 0.45f; 157 table[7] = 0.60f; 158 table[8] = 0.75f; 159 table[9] = 1.00f; 160 // This table's pretty small compared to real ones in the wild (think 256), 161 // so we give test_identity_xform_A2B a wide tolerance. 162 // This lets us implement table transfer functions with a single lookup. 163 const int tolerance = 13; 164 165 test_identity_xform(r, gammas, true); 166 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, tolerance); 167 } 168 169 DEF_TEST(ColorSpaceXform_ParametricGamma, r) { 170 // Parametric gamma curves 171 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); 172 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); 173 for (int i = 0; i < kChannels; ++i) { 174 gammas->fType[i] = SkGammas::Type::kParam_Type; 175 gammas->fData[i].fParamOffset = 0; 176 } 177 178 SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn> 179 (memory, sizeof(SkGammas)); 180 181 // Interval. 182 params->fD = 0.04045f; 183 184 // First equation: 185 params->fC = 1.0f / 12.92f; 186 params->fF = 0.0f; 187 188 // Second equation: 189 // Note that the function is continuous (it's actually sRGB). 190 params->fA = 1.0f / 1.055f; 191 params->fB = 0.055f / 1.055f; 192 params->fE = 0.0f; 193 params->fG = 2.4f; 194 test_identity_xform(r, gammas, true); 195 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); 196 } 197 198 DEF_TEST(ColorSpaceXform_ExponentialGamma, r) { 199 // Exponential gamma curves 200 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels)); 201 for (int i = 0; i < kChannels; ++i) { 202 gammas->fType[i] = SkGammas::Type::kValue_Type; 203 gammas->fData[i].fValue = 1.4f; 204 } 205 test_identity_xform(r, gammas, true); 206 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); 207 } 208 209 DEF_TEST(ColorSpaceXform_NamedGamma, r) { 210 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas(kChannels)); 211 gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type; 212 gammas->fData[0].fNamed = kSRGB_SkGammaNamed; 213 gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed; 214 gammas->fData[2].fNamed = kLinear_SkGammaNamed; 215 test_identity_xform(r, gammas, true); 216 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas); 217 test_identity_xform_A2B(r, kSRGB_SkGammaNamed, nullptr); 218 test_identity_xform_A2B(r, k2Dot2Curve_SkGammaNamed, nullptr); 219 test_identity_xform_A2B(r, kLinear_SkGammaNamed, nullptr); 220 } 221 222 DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) { 223 constexpr size_t tableSize = 10; 224 void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize + 225 sizeof(SkColorSpaceTransferFn)); 226 sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(kChannels)); 227 228 float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); 229 table[0] = 0.00f; 230 table[1] = 0.15f; 231 table[2] = 0.20f; 232 table[3] = 0.25f; 233 table[4] = 0.35f; 234 table[5] = 0.45f; 235 table[6] = 0.55f; 236 table[7] = 0.70f; 237 table[8] = 0.85f; 238 table[9] = 1.00f; 239 240 SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn>(memory, 241 sizeof(SkGammas) + sizeof(float) * tableSize); 242 params->fA = 1.0f / 1.055f; 243 params->fB = 0.055f / 1.055f; 244 params->fC = 1.0f / 12.92f; 245 params->fD = 0.04045f; 246 params->fE = 0.0f; 247 params->fF = 0.0f; 248 params->fG = 2.4f; 249 250 gammas->fType[0] = SkGammas::Type::kValue_Type; 251 gammas->fData[0].fValue = 1.2f; 252 253 // See ColorSpaceXform_TableGamma... we've decided to allow some tolerance 254 // for SkJumper's implementation of tables. 255 const int tolerance = 12; 256 gammas->fType[1] = SkGammas::Type::kTable_Type; 257 gammas->fData[1].fTable.fSize = tableSize; 258 gammas->fData[1].fTable.fOffset = 0; 259 260 gammas->fType[2] = SkGammas::Type::kParam_Type; 261 gammas->fData[2].fParamOffset = sizeof(float) * tableSize; 262 263 test_identity_xform(r, gammas, true); 264 test_identity_xform_A2B(r, kNonStandard_SkGammaNamed, gammas, tolerance); 265 } 266 267 DEF_TEST(ColorSpaceXform_A2BCLUT, r) { 268 constexpr int inputChannels = 3; 269 constexpr int gp = 4; // # grid points 270 271 constexpr int numEntries = gp*gp*gp*3; 272 const uint8_t gridPoints[3] = {gp, gp, gp}; 273 void* memory = sk_malloc_throw(sizeof(SkColorLookUpTable) + sizeof(float) * numEntries); 274 sk_sp<SkColorLookUpTable> colorLUT(new (memory) SkColorLookUpTable(inputChannels, gridPoints)); 275 // make a CLUT that rotates R, G, and B ie R->G, G->B, B->R 276 float* table = SkTAddOffset<float>(memory, sizeof(SkColorLookUpTable)); 277 for (int r = 0; r < gp; ++r) { 278 for (int g = 0; g < gp; ++g) { 279 for (int b = 0; b < gp; ++b) { 280 table[3*(gp*gp*r + gp*g + b) + 0] = g * (1.f / (gp - 1.f)); 281 table[3*(gp*gp*r + gp*g + b) + 1] = b * (1.f / (gp - 1.f)); 282 table[3*(gp*gp*r + gp*g + b) + 2] = r * (1.f / (gp - 1.f)); 283 } 284 } 285 } 286 287 // build an even distribution of pixels every (7 / 255) steps 288 // to test the xform on 289 constexpr int pixelgp = 7; 290 constexpr int numPixels = pixelgp*pixelgp*pixelgp; 291 SkAutoTMalloc<uint32_t> srcPixels(numPixels); 292 int srcIndex = 0; 293 for (int r = 0; r < pixelgp; ++r) { 294 for (int g = 0; g < pixelgp; ++g) { 295 for (int b = 0; b < pixelgp; ++b) { 296 const int red = (int) (r * (255.f / (pixelgp - 1.f))); 297 const int green = (int) (g * (255.f / (pixelgp - 1.f))); 298 const int blue = (int) (b * (255.f / (pixelgp - 1.f))); 299 srcPixels[srcIndex] = SkColorSetRGB(red, green, blue); 300 ++srcIndex; 301 } 302 } 303 } 304 SkAutoTMalloc<uint32_t> dstPixels(numPixels); 305 306 // src space is identity besides CLUT 307 std::vector<SkColorSpace_A2B::Element> srcElements; 308 srcElements.push_back(SkColorSpace_A2B::Element(std::move(colorLUT))); 309 auto srcSpace = ColorSpaceXformTest::CreateA2BSpace(SkColorSpace_A2B::PCS::kXYZ, 310 std::move(srcElements)); 311 // dst space is entirely identity 312 auto dstSpace = SkColorSpace::MakeRGB(SkColorSpace::kLinear_RenderTargetGamma, SkMatrix44::I()); 313 auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get()); 314 bool result = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, dstPixels.get(), 315 SkColorSpaceXform::kRGBA_8888_ColorFormat, srcPixels.get(), 316 numPixels, kOpaque_SkAlphaType); 317 REPORTER_ASSERT(r, result); 318 319 for (int i = 0; i < numPixels; ++i) { 320 REPORTER_ASSERT(r, almost_equal(SkColorGetR(srcPixels[i]), 321 SkColorGetG(dstPixels[i]))); 322 REPORTER_ASSERT(r, almost_equal(SkColorGetG(srcPixels[i]), 323 SkColorGetB(dstPixels[i]))); 324 REPORTER_ASSERT(r, almost_equal(SkColorGetB(srcPixels[i]), 325 SkColorGetR(dstPixels[i]))); 326 } 327 } 328 329 DEF_TEST(SkColorSpaceXform_LoadTail, r) { 330 std::unique_ptr<uint64_t[]> srcPixel(new uint64_t[1]); 331 srcPixel[0] = 0; 332 uint32_t dstPixel; 333 sk_sp<SkColorSpace> p3 = SkColorSpace::MakeRGB(SkColorSpace::kSRGB_RenderTargetGamma, 334 SkColorSpace::kDCIP3_D65_Gamut); 335 sk_sp<SkColorSpace> srgb = SkColorSpace::MakeSRGB(); 336 std::unique_ptr<SkColorSpaceXform> xform = SkColorSpaceXform::New(p3.get(), srgb.get()); 337 338 // ASAN will catch us if we read past the tail. 339 bool success = xform->apply(SkColorSpaceXform::kRGBA_8888_ColorFormat, &dstPixel, 340 SkColorSpaceXform::kRGBA_U16_BE_ColorFormat, srcPixel.get(), 1, 341 kUnpremul_SkAlphaType); 342 REPORTER_ASSERT(r, success); 343 } 344 345