Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 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 "SkColorSpace.h" |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 10 | #include "SkColorSpacePriv.h" |
Matt Sarett | 6f67fc2 | 2017-01-26 13:10:49 -0500 | [diff] [blame] | 11 | #include "SkColorSpace_XYZ.h" |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 12 | #include "SkData.h" |
| 13 | #include "SkICC.h" |
Hal Canary | 7a808d6 | 2017-06-16 13:38:34 -0400 | [diff] [blame] | 14 | #include "SkICCPriv.h" |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 15 | #include "SkMatrix44.h" |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 16 | #include "SkStream.h" |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 17 | #include "Test.h" |
| 18 | |
| 19 | static bool almost_equal(float a, float b) { |
| 20 | return SkTAbs(a - b) < 0.001f; |
| 21 | } |
| 22 | |
| 23 | static inline void test_to_xyz_d50(skiatest::Reporter* r, SkICC* icc, bool shouldSucceed, |
| 24 | const float* reference) { |
Matt Sarett | d4b9c35 | 2016-12-18 13:49:43 -0500 | [diff] [blame] | 25 | SkMatrix44 result(SkMatrix44::kUninitialized_Constructor); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 26 | REPORTER_ASSERT(r, shouldSucceed == icc->toXYZD50(&result)); |
| 27 | if (shouldSucceed) { |
| 28 | float resultVals[16]; |
| 29 | result.asColMajorf(resultVals); |
| 30 | for (int i = 0; i < 16; i++) { |
| 31 | REPORTER_ASSERT(r, almost_equal(resultVals[i], reference[i])); |
| 32 | } |
| 33 | } |
| 34 | } |
| 35 | |
| 36 | DEF_TEST(ICC_ToXYZD50, r) { |
| 37 | const float z30Reference[16] = { |
| 38 | 0.59825f, 0.27103f, 0.00603f, 0.0f, 0.22243f, 0.67447f, 0.07368f, 0.0f, 0.14352f, 0.05449f, |
| 39 | 0.74519f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, |
| 40 | }; |
| 41 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 42 | sk_sp<SkData> data = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 43 | sk_sp<SkICC> z30 = SkICC::Make(data->data(), data->size()); |
| 44 | test_to_xyz_d50(r, z30.get(), true, z30Reference); |
| 45 | |
| 46 | const float z32Reference[16] = { |
| 47 | 0.61583f, 0.28789f, 0.00513f, 0.0f, 0.20428f, 0.66972f, 0.06609f, 0.0f, 0.14409f, 0.04237f, |
| 48 | 0.75368f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, |
| 49 | }; |
| 50 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 51 | data = GetResourceAsData("icc_profiles/HP_Z32x.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 52 | sk_sp<SkICC> z32 = SkICC::Make(data->data(), data->size()); |
| 53 | test_to_xyz_d50(r, z32.get(), true, z32Reference); |
| 54 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 55 | data = GetResourceAsData("icc_profiles/upperLeft.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 56 | sk_sp<SkICC> upperLeft = SkICC::Make(data->data(), data->size()); |
| 57 | test_to_xyz_d50(r, upperLeft.get(), false, z32Reference); |
| 58 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 59 | data = GetResourceAsData("icc_profiles/upperRight.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 60 | sk_sp<SkICC> upperRight = SkICC::Make(data->data(), data->size()); |
| 61 | test_to_xyz_d50(r, upperRight.get(), false, z32Reference); |
| 62 | } |
| 63 | |
| 64 | static inline void test_is_numerical_transfer_fn(skiatest::Reporter* r, SkICC* icc, |
| 65 | bool shouldSucceed, |
| 66 | const SkColorSpaceTransferFn& reference) { |
| 67 | SkColorSpaceTransferFn result; |
| 68 | REPORTER_ASSERT(r, shouldSucceed == icc->isNumericalTransferFn(&result)); |
| 69 | if (shouldSucceed) { |
| 70 | REPORTER_ASSERT(r, 0 == memcmp(&result, &reference, sizeof(SkColorSpaceTransferFn))); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | DEF_TEST(ICC_IsNumericalTransferFn, r) { |
| 75 | SkColorSpaceTransferFn referenceFn; |
| 76 | referenceFn.fA = 1.0f; |
| 77 | referenceFn.fB = 0.0f; |
| 78 | referenceFn.fC = 0.0f; |
| 79 | referenceFn.fD = 0.0f; |
| 80 | referenceFn.fE = 0.0f; |
| 81 | referenceFn.fF = 0.0f; |
| 82 | referenceFn.fG = 2.2f; |
| 83 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 84 | sk_sp<SkData> data = GetResourceAsData("icc_profiles/HP_ZR30w.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 85 | sk_sp<SkICC> z30 = SkICC::Make(data->data(), data->size()); |
| 86 | test_is_numerical_transfer_fn(r, z30.get(), true, referenceFn); |
| 87 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 88 | data = GetResourceAsData("icc_profiles/HP_Z32x.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 89 | sk_sp<SkICC> z32 = SkICC::Make(data->data(), data->size()); |
| 90 | test_is_numerical_transfer_fn(r, z32.get(), true, referenceFn); |
| 91 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 92 | data = GetResourceAsData("icc_profiles/upperLeft.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 93 | sk_sp<SkICC> upperLeft = SkICC::Make(data->data(), data->size()); |
| 94 | test_is_numerical_transfer_fn(r, upperLeft.get(), false, referenceFn); |
| 95 | |
Mike Reed | 0933bc9 | 2017-12-09 01:27:41 +0000 | [diff] [blame] | 96 | data = GetResourceAsData("icc_profiles/upperRight.icc"); |
Ravi Mistry | 113d05f | 2016-12-17 01:31:03 +0000 | [diff] [blame] | 97 | sk_sp<SkICC> upperRight = SkICC::Make(data->data(), data->size()); |
| 98 | test_is_numerical_transfer_fn(r, upperRight.get(), false, referenceFn); |
| 99 | } |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 100 | |
Brian Osman | a12f795 | 2017-12-19 14:44:12 -0500 | [diff] [blame] | 101 | DEF_TEST(ICC_Adobe, r) { |
| 102 | // Test that the color spaces produced by our procedural Adobe factory, and the official |
| 103 | // Adobe ICC profile match exactly. |
| 104 | sk_sp<SkData> data = GetResourceAsData("icc_profiles/AdobeRGB1998.icc"); |
| 105 | sk_sp<SkColorSpace> fromIcc = SkColorSpace::MakeICC(data->data(), data->size()); |
| 106 | sk_sp<SkColorSpace> procedural = SkColorSpace::MakeRGB(g2Dot2_TransferFn, |
| 107 | SkColorSpace::kAdobeRGB_Gamut); |
| 108 | REPORTER_ASSERT(r, SkColorSpace::Equals(fromIcc.get(), procedural.get())); |
| 109 | } |
| 110 | |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 111 | static inline void test_write_icc(skiatest::Reporter* r, const SkColorSpaceTransferFn& fn, |
Brian Osman | 10b8142 | 2017-12-14 10:27:21 -0500 | [diff] [blame] | 112 | const SkMatrix44& toXYZD50, bool writeToFile) { |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 113 | sk_sp<SkData> profile = SkICC::WriteToICC(fn, toXYZD50); |
| 114 | if (writeToFile) { |
| 115 | SkFILEWStream stream("out.icc"); |
| 116 | stream.write(profile->data(), profile->size()); |
| 117 | } |
| 118 | |
| 119 | sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeICC(profile->data(), profile->size()); |
Brian Osman | 10b8142 | 2017-12-14 10:27:21 -0500 | [diff] [blame] | 120 | sk_sp<SkColorSpace> reference = SkColorSpace::MakeRGB(fn, toXYZD50); |
| 121 | REPORTER_ASSERT(r, SkColorSpace::Equals(reference.get(), colorSpace.get())); |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | DEF_TEST(ICC_WriteICC, r) { |
| 125 | SkColorSpaceTransferFn adobeFn; |
| 126 | adobeFn.fA = 1.0f; |
| 127 | adobeFn.fB = 0.0f; |
| 128 | adobeFn.fC = 0.0f; |
| 129 | adobeFn.fD = 0.0f; |
| 130 | adobeFn.fE = 0.0f; |
| 131 | adobeFn.fF = 0.0f; |
| 132 | adobeFn.fG = 2.2f; |
| 133 | SkMatrix44 adobeMatrix(SkMatrix44::kUninitialized_Constructor); |
| 134 | adobeMatrix.set3x3RowMajorf(gAdobeRGB_toXYZD50); |
Brian Osman | a12f795 | 2017-12-19 14:44:12 -0500 | [diff] [blame] | 135 | test_write_icc(r, adobeFn, adobeMatrix, false); |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 136 | |
| 137 | SkColorSpaceTransferFn srgbFn; |
| 138 | srgbFn.fA = 1.0f / 1.055f; |
| 139 | srgbFn.fB = 0.055f / 1.055f; |
| 140 | srgbFn.fC = 1.0f / 12.92f; |
| 141 | srgbFn.fD = 0.04045f; |
| 142 | srgbFn.fE = 0.0f; |
| 143 | srgbFn.fF = 0.0f; |
| 144 | srgbFn.fG = 2.4f; |
| 145 | SkMatrix44 srgbMatrix(SkMatrix44::kUninitialized_Constructor); |
| 146 | srgbMatrix.set3x3RowMajorf(gSRGB_toXYZD50); |
Brian Osman | 10b8142 | 2017-12-14 10:27:21 -0500 | [diff] [blame] | 147 | test_write_icc(r, srgbFn, srgbMatrix, false); |
Hal Canary | 7a808d6 | 2017-06-16 13:38:34 -0400 | [diff] [blame] | 148 | |
Hal Canary | a8565e5 | 2017-06-19 12:43:28 -0400 | [diff] [blame] | 149 | SkString adobeTag = SkICCGetColorProfileTag(adobeFn, adobeMatrix); |
| 150 | SkString srgbTag = SkICCGetColorProfileTag(srgbFn, srgbMatrix); |
| 151 | REPORTER_ASSERT(r, adobeTag != srgbTag); |
| 152 | REPORTER_ASSERT(r, srgbTag.equals("sRGB")); |
| 153 | REPORTER_ASSERT(r, adobeTag.equals("AdobeRGB")); |
Matt Sarett | 1bfcf88 | 2016-12-19 18:37:34 -0500 | [diff] [blame] | 154 | } |
Matt Sarett | 6f67fc2 | 2017-01-26 13:10:49 -0500 | [diff] [blame] | 155 | |
| 156 | static inline void test_raw_transfer_fn(skiatest::Reporter* r, SkICC* icc) { |
| 157 | SkICC::Tables tables; |
| 158 | bool result = icc->rawTransferFnData(&tables); |
| 159 | REPORTER_ASSERT(r, result); |
| 160 | |
| 161 | REPORTER_ASSERT(r, 0.0f == tables.red()[0]); |
| 162 | REPORTER_ASSERT(r, 0.0f == tables.green()[0]); |
| 163 | REPORTER_ASSERT(r, 0.0f == tables.blue()[0]); |
| 164 | REPORTER_ASSERT(r, 1.0f == tables.red()[tables.fRed.fCount - 1]); |
| 165 | REPORTER_ASSERT(r, 1.0f == tables.green()[tables.fGreen.fCount - 1]); |
| 166 | REPORTER_ASSERT(r, 1.0f == tables.blue()[tables.fBlue.fCount - 1]); |
| 167 | } |
| 168 | |
| 169 | class ICCTest { |
| 170 | public: |
| 171 | static sk_sp<SkICC> MakeICC(sk_sp<SkColorSpace> space) { |
| 172 | return sk_sp<SkICC>(new SkICC(std::move(space))); |
| 173 | } |
| 174 | static sk_sp<SkICC> MakeICC(sk_sp<SkGammas> gammas) { |
| 175 | return MakeICC(sk_sp<SkColorSpace>(new SkColorSpace_XYZ( |
| 176 | kNonStandard_SkGammaNamed, std::move(gammas), |
Matt Sarett | f388093 | 2017-03-24 10:06:03 -0400 | [diff] [blame] | 177 | SkMatrix44(SkMatrix44::kIdentity_Constructor), nullptr))); |
Matt Sarett | 6f67fc2 | 2017-01-26 13:10:49 -0500 | [diff] [blame] | 178 | } |
| 179 | }; |
| 180 | |
| 181 | DEF_TEST(ICC_RawTransferFns, r) { |
Matt Sarett | 77a7a1b | 2017-02-07 13:56:11 -0500 | [diff] [blame] | 182 | sk_sp<SkICC> srgb = ICCTest::MakeICC(SkColorSpace::MakeSRGB()); |
Matt Sarett | 6f67fc2 | 2017-01-26 13:10:49 -0500 | [diff] [blame] | 183 | test_raw_transfer_fn(r, srgb.get()); |
| 184 | |
Matt Sarett | 6f67fc2 | 2017-01-26 13:10:49 -0500 | [diff] [blame] | 185 | // Lookup-table based gamma curves |
| 186 | constexpr size_t tableSize = 10; |
| 187 | void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize); |
| 188 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 189 | for (int i = 0; i < 3; ++i) { |
| 190 | gammas->fType[i] = SkGammas::Type::kTable_Type; |
| 191 | gammas->fData[i].fTable.fSize = tableSize; |
| 192 | gammas->fData[i].fTable.fOffset = 0; |
| 193 | } |
| 194 | |
| 195 | float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); |
| 196 | table[0] = 0.00f; |
| 197 | table[1] = 0.05f; |
| 198 | table[2] = 0.10f; |
| 199 | table[3] = 0.15f; |
| 200 | table[4] = 0.25f; |
| 201 | table[5] = 0.35f; |
| 202 | table[6] = 0.45f; |
| 203 | table[7] = 0.60f; |
| 204 | table[8] = 0.75f; |
| 205 | table[9] = 1.00f; |
| 206 | sk_sp<SkICC> tbl = ICCTest::MakeICC(gammas); |
| 207 | test_raw_transfer_fn(r, tbl.get()); |
| 208 | |
| 209 | // Parametric gamma curves |
| 210 | memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkColorSpaceTransferFn)); |
| 211 | gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 212 | for (int i = 0; i < 3; ++i) { |
| 213 | gammas->fType[i] = SkGammas::Type::kParam_Type; |
| 214 | gammas->fData[i].fParamOffset = 0; |
| 215 | } |
| 216 | |
| 217 | SkColorSpaceTransferFn* params = SkTAddOffset<SkColorSpaceTransferFn> |
| 218 | (memory, sizeof(SkGammas)); |
| 219 | |
| 220 | // Interval. |
| 221 | params->fD = 0.04045f; |
| 222 | |
| 223 | // First equation: |
| 224 | params->fC = 1.0f / 12.92f; |
| 225 | params->fF = 0.0f; |
| 226 | |
| 227 | // Second equation: |
| 228 | // Note that the function is continuous (it's actually sRGB). |
| 229 | params->fA = 1.0f / 1.055f; |
| 230 | params->fB = 0.055f / 1.055f; |
| 231 | params->fE = 0.0f; |
| 232 | params->fG = 2.4f; |
| 233 | sk_sp<SkICC> param = ICCTest::MakeICC(gammas); |
| 234 | test_raw_transfer_fn(r, param.get()); |
| 235 | |
| 236 | // Exponential gamma curves |
| 237 | gammas = sk_sp<SkGammas>(new SkGammas(3)); |
| 238 | for (int i = 0; i < 3; ++i) { |
| 239 | gammas->fType[i] = SkGammas::Type::kValue_Type; |
| 240 | gammas->fData[i].fValue = 1.4f; |
| 241 | } |
| 242 | sk_sp<SkICC> exp = ICCTest::MakeICC(gammas); |
| 243 | test_raw_transfer_fn(r, exp.get()); |
| 244 | |
| 245 | gammas = sk_sp<SkGammas>(new SkGammas(3)); |
| 246 | gammas->fType[0] = gammas->fType[1] = gammas->fType[2] = SkGammas::Type::kNamed_Type; |
| 247 | gammas->fData[0].fNamed = kSRGB_SkGammaNamed; |
| 248 | gammas->fData[1].fNamed = k2Dot2Curve_SkGammaNamed; |
| 249 | gammas->fData[2].fNamed = kLinear_SkGammaNamed; |
| 250 | sk_sp<SkICC> named = ICCTest::MakeICC(gammas); |
| 251 | test_raw_transfer_fn(r, named.get()); |
| 252 | |
| 253 | memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize + |
| 254 | sizeof(SkColorSpaceTransferFn)); |
| 255 | gammas = sk_sp<SkGammas>(new (memory) SkGammas(3)); |
| 256 | |
| 257 | table = SkTAddOffset<float>(memory, sizeof(SkGammas)); |
| 258 | table[0] = 0.00f; |
| 259 | table[1] = 0.15f; |
| 260 | table[2] = 0.20f; |
| 261 | table[3] = 0.25f; |
| 262 | table[4] = 0.35f; |
| 263 | table[5] = 0.45f; |
| 264 | table[6] = 0.55f; |
| 265 | table[7] = 0.70f; |
| 266 | table[8] = 0.85f; |
| 267 | table[9] = 1.00f; |
| 268 | |
| 269 | params = SkTAddOffset<SkColorSpaceTransferFn>(memory, |
| 270 | sizeof(SkGammas) + sizeof(float) * tableSize); |
| 271 | params->fA = 1.0f / 1.055f; |
| 272 | params->fB = 0.055f / 1.055f; |
| 273 | params->fC = 1.0f / 12.92f; |
| 274 | params->fD = 0.04045f; |
| 275 | params->fE = 0.0f; |
| 276 | params->fF = 0.0f; |
| 277 | params->fG = 2.4f; |
| 278 | |
| 279 | gammas->fType[0] = SkGammas::Type::kValue_Type; |
| 280 | gammas->fData[0].fValue = 1.2f; |
| 281 | |
| 282 | gammas->fType[1] = SkGammas::Type::kTable_Type; |
| 283 | gammas->fData[1].fTable.fSize = tableSize; |
| 284 | gammas->fData[1].fTable.fOffset = 0; |
| 285 | |
| 286 | gammas->fType[2] = SkGammas::Type::kParam_Type; |
| 287 | gammas->fData[2].fParamOffset = sizeof(float) * tableSize; |
| 288 | sk_sp<SkICC> nonstd = ICCTest::MakeICC(gammas); |
| 289 | test_raw_transfer_fn(r, nonstd.get()); |
| 290 | |
| 291 | // Reverse order of table and exponent |
| 292 | gammas->fType[1] = SkGammas::Type::kValue_Type; |
| 293 | gammas->fData[1].fValue = 1.2f; |
| 294 | |
| 295 | gammas->fType[0] = SkGammas::Type::kTable_Type; |
| 296 | gammas->fData[0].fTable.fSize = tableSize; |
| 297 | gammas->fData[0].fTable.fOffset = 0; |
| 298 | sk_sp<SkICC> nonstd2 = ICCTest::MakeICC(gammas); |
| 299 | test_raw_transfer_fn(r, nonstd2.get()); |
| 300 | } |