| /* | |
| * Copyright 2016 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkData.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/encode/SkICC.h" | |
| #include "modules/skcms/skcms.h" | |
| #include "tests/Test.h" | |
| #include "tools/Resources.h" | |
| #include <array> | |
| #include <cmath> | |
| #include <cstdint> | |
| #include <cstdlib> | |
| DEF_TEST(AdobeRGB, r) { | |
| if (sk_sp<SkData> profile = GetResourceAsData("icc_profiles/AdobeRGB1998.icc")) { | |
| skcms_ICCProfile parsed; | |
| REPORTER_ASSERT(r, skcms_Parse(profile->data(), profile->size(), &parsed)); | |
| REPORTER_ASSERT(r, !parsed.has_CICP); | |
| auto got = SkColorSpace::Make(parsed); | |
| auto want = SkColorSpace::MakeRGB(SkNamedTransferFn::k2Dot2, SkNamedGamut::kAdobeRGB); | |
| REPORTER_ASSERT(r, SkColorSpace::Equals(got.get(), want.get())); | |
| } | |
| } | |
| DEF_TEST(HDR_ICC, r) { | |
| constexpr size_t kTestCount = 3; | |
| std::array<sk_sp<SkData>, kTestCount> profile = { | |
| SkWriteICCProfile(SkNamedTransferFn::kPQ, SkNamedGamut::kRec2020), | |
| SkWriteICCProfile(SkNamedTransferFn::kHLG, SkNamedGamut::kDisplayP3), | |
| SkWriteICCProfile(SkNamedTransferFn::kSRGB, SkNamedGamut::kSRGB), | |
| }; | |
| std::array<sk_sp<SkData>, kTestCount> dst_profile = { | |
| SkWriteICCProfile(SkNamedTransferFn::kLinear, SkNamedGamut::kRec2020), | |
| SkWriteICCProfile(SkNamedTransferFn::kLinear, SkNamedGamut::kDisplayP3), | |
| SkWriteICCProfile(SkNamedTransferFn::kLinear, SkNamedGamut::kSRGB), | |
| }; | |
| constexpr size_t kPixelCount = 7; | |
| // clang-format off | |
| float pixels[kPixelCount][3] = { | |
| { 0.00f, 0.00f, 0.00f, }, | |
| { 0.50f, 0.50f, 0.50f, }, | |
| { 0.50f, 0.00f, 0.00f, }, | |
| { 0.00f, 0.50f, 0.00f, }, | |
| { 0.00f, 0.00f, 0.50f, }, | |
| { 0.25f, 0.50f, 0.00f, }, | |
| { 0.75f, 0.75f, 0.75f, }, | |
| }; | |
| // The tone mapped value of PQ 0.5 and 0.75. | |
| constexpr float kPQ_05 = 0.3182877451f; | |
| constexpr float kPQ_075 = 0.9943588777f; | |
| // The tone mapped value of PQ 0.25, when maxRGB is 0.5. | |
| constexpr float kPQ_025 = 0.020679904f; | |
| // The tone mapped value of HLG 0.5 and 0.75 (when all channels are equal). | |
| constexpr float kHLG_05 = 0.20188954163f; | |
| constexpr float kHLG_075 = 0.5208149688f; | |
| // The linearized values of sRGB 0.25, 0.5, and 0.75. | |
| constexpr float kSRGB_025 = 0.05087607f; | |
| constexpr float kSRGB_05 = 0.21404112f; | |
| constexpr float kSRGB_075 = 0.52252153f; | |
| float dst_pixels_expected[kTestCount][kPixelCount][3] = { | |
| { | |
| { 0.f, 0.f, 0.f, }, | |
| { kPQ_05, kPQ_05, kPQ_05, }, | |
| { kPQ_05, 0.f, 0.f, }, | |
| { 0.f, kPQ_05, 0.f, }, | |
| { 0.f, 0.f, kPQ_05, }, | |
| { kPQ_025, kPQ_05, 0.f, }, | |
| { kPQ_075, kPQ_075, kPQ_075, }, // PQ maps 0.75 ~ 1000 nits to 1.0 | |
| }, | |
| { | |
| { 0.f, 0.f, 0.f, }, | |
| { kHLG_05, kHLG_05, kHLG_05, }, | |
| { 0.1618f, 0.f, 0.f, }, // HLG will map 0.5 to different values | |
| { 0.f, 0.1895f, 0.f, }, // if it is the R, G, or B channel, because | |
| { 0.f, 0.f, 0.1251f, }, // of the OOTF. | |
| { 0.0513f, 0.1924f, 0.f, }, | |
| { kHLG_075, kHLG_075, kHLG_075, }, | |
| }, | |
| { | |
| { 0.f, 0.f, 0.f, }, | |
| { kSRGB_05, kSRGB_05, kSRGB_05, }, // This is just the sRGB transfer function | |
| { kSRGB_05, 0.f, 0.f, }, | |
| { 0.f, kSRGB_05, 0.f, }, | |
| { 0.f, 0.f, kSRGB_05, }, | |
| { kSRGB_025, kSRGB_05, 0.f, }, | |
| { kSRGB_075, kSRGB_075, kSRGB_075, }, | |
| }, | |
| }; | |
| // clang-format on | |
| std::array<bool, kTestCount> cicp_expected = {true, true, false}; | |
| std::array<bool, kTestCount> a2b_expected = {true, true, false}; | |
| std::array<uint32_t, kTestCount> cicp_primaries_expected = {9, 12, 0}; | |
| std::array<uint32_t, kTestCount> cicp_trfn_expected = {16, 18, 0}; | |
| for (size_t test = 0; test < kTestCount; ++test) { | |
| skcms_ICCProfile parsed; | |
| REPORTER_ASSERT(r, skcms_Parse(profile[test]->data(), profile[test]->size(), &parsed)); | |
| REPORTER_ASSERT(r, parsed.has_A2B == a2b_expected[test]); | |
| REPORTER_ASSERT(r, parsed.has_CICP == cicp_expected[test]); | |
| if (cicp_expected[test]) { | |
| REPORTER_ASSERT(r, parsed.CICP.color_primaries == cicp_primaries_expected[test]); | |
| REPORTER_ASSERT(r, parsed.CICP.transfer_characteristics == cicp_trfn_expected[test]); | |
| REPORTER_ASSERT(r, parsed.CICP.matrix_coefficients == 0); | |
| REPORTER_ASSERT(r, parsed.CICP.video_full_range_flag == 1); | |
| } | |
| // Disable the CICP tag so we can test the A2B transform (otherwise the CICP takes | |
| // precedence). | |
| parsed.has_CICP = false; | |
| skcms_ICCProfile dst_parsed; | |
| REPORTER_ASSERT( | |
| r, skcms_Parse(dst_profile[test]->data(), dst_profile[test]->size(), &dst_parsed)); | |
| for (size_t pixel = 0; pixel < kPixelCount; ++pixel) { | |
| std::array<float, 3> dst_pixel_actual = {0.f, 0.f, 0.f}; | |
| bool xform_result = skcms_Transform(pixels[pixel], | |
| skcms_PixelFormat_RGB_fff, | |
| skcms_AlphaFormat_Opaque, | |
| &parsed, | |
| dst_pixel_actual.data(), | |
| skcms_PixelFormat_RGB_fff, | |
| skcms_AlphaFormat_Opaque, | |
| &dst_parsed, | |
| 1); | |
| REPORTER_ASSERT(r, xform_result); | |
| auto approx_equal = [=](float x, float y) { return std::abs(x - y) < 1.f / 64.f; }; | |
| for (size_t i = 0; i < 3; ++i) { | |
| REPORTER_ASSERT( | |
| r, approx_equal(dst_pixel_actual[i], dst_pixels_expected[test][pixel][i])); | |
| } | |
| } | |
| } | |
| } |