Home | History | Annotate | Download | only in tests
      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