msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [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 "SkCodec.h" |
msarett | c044461 | 2016-09-16 11:45:58 -0700 | [diff] [blame] | 10 | #include "SkCodecPriv.h" |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 11 | #include "SkColorPriv.h" |
| 12 | #include "SkColorSpace.h" |
| 13 | #include "SkColorSpace_Base.h" |
| 14 | #include "SkColorSpaceXform.h" |
| 15 | #include "Test.h" |
| 16 | |
| 17 | class ColorSpaceXformTest { |
| 18 | public: |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 19 | static std::unique_ptr<SkColorSpaceXform> CreateIdentityXform(const sk_sp<SkGammas>& gammas) { |
| 20 | // Logically we can pass any matrix here. For simplicty, pass I(), i.e. D50 XYZ gamut. |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 21 | sk_sp<SkColorSpace> space(new SkColorSpace_Base( |
msarett | 600c737 | 2016-09-07 12:03:53 -0700 | [diff] [blame] | 22 | nullptr, kNonStandard_SkGammaNamed, gammas, SkMatrix::I(), nullptr)); |
msarett | 9dc6cf6 | 2016-08-23 17:53:06 -0700 | [diff] [blame] | 23 | |
| 24 | // Use special testing entry point, so we don't skip the xform, even though src == dst. |
msarett | 4be0e7c | 2016-09-22 07:02:24 -0700 | [diff] [blame] | 25 | return SlowIdentityXform(space.get()); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 26 | } |
| 27 | }; |
| 28 | |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 29 | static bool almost_equal(int x, int y) { |
| 30 | return SkTAbs(x - y) <= 1; |
| 31 | } |
| 32 | |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 33 | static void test_identity_xform(skiatest::Reporter* r, const sk_sp<SkGammas>& gammas) { |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 34 | // Arbitrary set of 10 pixels |
| 35 | constexpr int width = 10; |
| 36 | constexpr uint32_t srcPixels[width] = { |
| 37 | 0xFFABCDEF, 0xFF146829, 0xFF382759, 0xFF184968, 0xFFDE8271, |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 38 | 0xFF32AB52, 0xFF0383BC, 0xFF000102, 0xFFFFFFFF, 0xFFDDEEFF, }; |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 39 | uint32_t dstPixels[width]; |
| 40 | |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 41 | // Create and perform an identity xform. |
| 42 | std::unique_ptr<SkColorSpaceXform> xform = ColorSpaceXformTest::CreateIdentityXform(gammas); |
msarett | c044461 | 2016-09-16 11:45:58 -0700 | [diff] [blame] | 43 | xform->apply(dstPixels, srcPixels, width, select_xform_format(kN32_SkColorType), |
msarett | cf7b877 | 2016-09-22 12:37:04 -0700 | [diff] [blame] | 44 | SkColorSpaceXform::kBGRA_8888_ColorFormat, kOpaque_SkAlphaType); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 45 | |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 46 | // Since the src->dst matrix is the identity, and the gamma curves match, |
| 47 | // the pixels should be unchanged. |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 48 | for (int i = 0; i < width; i++) { |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 49 | REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 0) & 0xFF), |
msarett | cf7b877 | 2016-09-22 12:37:04 -0700 | [diff] [blame] | 50 | SkGetPackedB32(dstPixels[i]))); |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 51 | REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 8) & 0xFF), |
msarett | 9dc6cf6 | 2016-08-23 17:53:06 -0700 | [diff] [blame] | 52 | SkGetPackedG32(dstPixels[i]))); |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 53 | REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 16) & 0xFF), |
msarett | cf7b877 | 2016-09-22 12:37:04 -0700 | [diff] [blame] | 54 | SkGetPackedR32(dstPixels[i]))); |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 55 | REPORTER_ASSERT(r, almost_equal(((srcPixels[i] >> 24) & 0xFF), |
msarett | 9dc6cf6 | 2016-08-23 17:53:06 -0700 | [diff] [blame] | 56 | SkGetPackedA32(dstPixels[i]))); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 57 | } |
| 58 | } |
| 59 | |
| 60 | DEF_TEST(ColorSpaceXform_TableGamma, r) { |
| 61 | // Lookup-table based gamma curves |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 62 | constexpr size_t tableSize = 10; |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 63 | void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize); |
| 64 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); |
| 65 | gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kTable_Type; |
| 66 | gammas->fRedData.fTable.fSize = gammas->fGreenData.fTable.fSize = |
| 67 | gammas->fBlueData.fTable.fSize = tableSize; |
| 68 | gammas->fRedData.fTable.fOffset = gammas->fGreenData.fTable.fOffset = |
| 69 | gammas->fBlueData.fTable.fOffset = 0; |
| 70 | float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); |
| 71 | |
| 72 | table[0] = 0.00f; |
| 73 | table[1] = 0.05f; |
| 74 | table[2] = 0.10f; |
| 75 | table[3] = 0.15f; |
| 76 | table[4] = 0.25f; |
| 77 | table[5] = 0.35f; |
| 78 | table[6] = 0.45f; |
| 79 | table[7] = 0.60f; |
| 80 | table[8] = 0.75f; |
| 81 | table[9] = 1.00f; |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 82 | test_identity_xform(r, gammas); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 83 | } |
| 84 | |
| 85 | DEF_TEST(ColorSpaceXform_ParametricGamma, r) { |
| 86 | // Parametric gamma curves |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 87 | void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(SkGammas::Params)); |
| 88 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); |
| 89 | gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kParam_Type; |
| 90 | gammas->fRedData.fParamOffset = gammas->fGreenData.fParamOffset = |
| 91 | gammas->fBlueData.fParamOffset = 0; |
| 92 | SkGammas::Params* params = SkTAddOffset<SkGammas::Params>(memory, sizeof(SkGammas)); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 93 | |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 94 | // Interval, switch xforms at 0.0031308f |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 95 | params->fD = 0.04045f; |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 96 | |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 97 | // First equation: |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 98 | params->fE = 1.0f / 12.92f; |
| 99 | params->fF = 0.0f; |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 100 | |
msarett | b390676 | 2016-06-22 14:07:48 -0700 | [diff] [blame] | 101 | // Second equation: |
| 102 | // Note that the function is continuous (it's actually sRGB). |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 103 | params->fA = 1.0f / 1.055f; |
| 104 | params->fB = 0.055f / 1.055f; |
| 105 | params->fC = 0.0f; |
| 106 | params->fG = 2.4f; |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 107 | test_identity_xform(r, gammas); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 108 | } |
| 109 | |
| 110 | DEF_TEST(ColorSpaceXform_ExponentialGamma, r) { |
| 111 | // Exponential gamma curves |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 112 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas()); |
| 113 | gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kValue_Type; |
| 114 | gammas->fRedData.fValue = gammas->fGreenData.fValue = gammas->fBlueData.fValue = 1.4f; |
| 115 | test_identity_xform(r, gammas); |
| 116 | } |
| 117 | |
msarett | 55bcc8e | 2016-09-09 07:48:05 -0700 | [diff] [blame] | 118 | DEF_TEST(ColorSpaceXform_NamedGamma, r) { |
| 119 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new SkGammas()); |
| 120 | gammas->fRedType = gammas->fGreenType = gammas->fBlueType = SkGammas::Type::kNamed_Type; |
| 121 | gammas->fRedData.fNamed = kSRGB_SkGammaNamed; |
| 122 | gammas->fGreenData.fNamed = k2Dot2Curve_SkGammaNamed; |
| 123 | gammas->fBlueData.fNamed = kLinear_SkGammaNamed; |
| 124 | test_identity_xform(r, gammas); |
| 125 | } |
| 126 | |
msarett | 1b93bd1 | 2016-07-21 07:11:26 -0700 | [diff] [blame] | 127 | DEF_TEST(ColorSpaceXform_NonMatchingGamma, r) { |
| 128 | constexpr size_t tableSize = 10; |
| 129 | void* memory = sk_malloc_throw(sizeof(SkGammas) + sizeof(float) * tableSize + |
| 130 | sizeof(SkGammas::Params)); |
| 131 | sk_sp<SkGammas> gammas = sk_sp<SkGammas>(new (memory) SkGammas()); |
| 132 | |
| 133 | float* table = SkTAddOffset<float>(memory, sizeof(SkGammas)); |
| 134 | table[0] = 0.00f; |
| 135 | table[1] = 0.15f; |
| 136 | table[2] = 0.20f; |
| 137 | table[3] = 0.25f; |
| 138 | table[4] = 0.35f; |
| 139 | table[5] = 0.45f; |
| 140 | table[6] = 0.55f; |
| 141 | table[7] = 0.70f; |
| 142 | table[8] = 0.85f; |
| 143 | table[9] = 1.00f; |
| 144 | |
| 145 | SkGammas::Params* params = SkTAddOffset<SkGammas::Params>(memory, sizeof(SkGammas) + |
| 146 | sizeof(float) * tableSize); |
| 147 | params->fA = 1.0f / 1.055f; |
| 148 | params->fB = 0.055f / 1.055f; |
| 149 | params->fC = 0.0f; |
| 150 | params->fD = 0.04045f; |
| 151 | params->fE = 1.0f / 12.92f; |
| 152 | params->fF = 0.0f; |
| 153 | params->fG = 2.4f; |
| 154 | |
| 155 | gammas->fRedType = SkGammas::Type::kValue_Type; |
| 156 | gammas->fRedData.fValue = 1.2f; |
| 157 | |
| 158 | gammas->fGreenType = SkGammas::Type::kTable_Type; |
| 159 | gammas->fGreenData.fTable.fSize = tableSize; |
| 160 | gammas->fGreenData.fTable.fOffset = 0; |
| 161 | |
| 162 | gammas->fBlueType = SkGammas::Type::kParam_Type; |
| 163 | gammas->fBlueData.fParamOffset = sizeof(float) * tableSize; |
| 164 | |
mtklein | b435963 | 2016-07-18 11:16:14 -0700 | [diff] [blame] | 165 | test_identity_xform(r, gammas); |
msarett | dc27a64 | 2016-06-06 12:02:31 -0700 | [diff] [blame] | 166 | } |
raftias | c6cc28c | 2016-09-29 14:31:44 -0400 | [diff] [blame] | 167 | |
| 168 | DEF_TEST(ColorSpaceXform_applyCLUTMemoryAccess, r) { |
| 169 | // buffers larger than 1024 (or 256 in GOOGLE3) will force ColorSpaceXform_Base::apply() |
| 170 | // to heap-allocate a buffer that is used for CLUT application, and this test is here to |
| 171 | // ensure that it no longer causes potential invalid memory accesses when this happens |
| 172 | const size_t len = 2048; |
| 173 | SkAutoTMalloc<uint32_t> src(len); |
| 174 | SkAutoTMalloc<uint32_t> dst(len); |
| 175 | for (uint32_t i = 0; i < len; ++i) { |
| 176 | src[i] = i; |
| 177 | } |
| 178 | // this ICC profile has a CLUT in it |
| 179 | const SkString filename(GetResourcePath("icc_profiles/upperRight.icc")); |
| 180 | sk_sp<SkData> iccData = SkData::MakeFromFileName(filename.c_str()); |
| 181 | REPORTER_ASSERT_MESSAGE(r, iccData, "upperRight.icc profile required for test"); |
| 182 | sk_sp<SkColorSpace> srcSpace = SkColorSpace::NewICC(iccData->bytes(), iccData->size()); |
| 183 | sk_sp<SkColorSpace> dstSpace = SkColorSpace::NewNamed(SkColorSpace::kSRGB_Named); |
| 184 | auto xform = SkColorSpaceXform::New(srcSpace.get(), dstSpace.get()); |
| 185 | xform->apply(dst.get(), src.get(), len, SkColorSpaceXform::kRGBA_8888_ColorFormat, |
| 186 | SkColorSpaceXform::kRGBA_8888_ColorFormat, kUnpremul_SkAlphaType); |
| 187 | } |