Home | History | Annotate | Download | only in core
      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 "SkColorSpaceXform_A2B.h"
      9 
     10 #include "SkColorData.h"
     11 #include "SkColorSpace_A2B.h"
     12 #include "SkColorSpace_XYZ.h"
     13 #include "SkColorSpacePriv.h"
     14 #include "SkColorSpaceXformPriv.h"
     15 #include "SkMakeUnique.h"
     16 #include "SkNx.h"
     17 #include "SkSRGB.h"
     18 #include "SkTypes.h"
     19 #include "../jumper/SkJumper.h"
     20 
     21 bool SkColorSpaceXform_A2B::onApply(ColorFormat dstFormat, void* dst, ColorFormat srcFormat,
     22                                     const void* src, int count, SkAlphaType alphaType) const {
     23     SkRasterPipeline_<256> pipeline;
     24 
     25     SkJumper_MemoryCtx src_ctx = { (void*)src, 0 },
     26                        dst_ctx = { (void*)dst, 0 };
     27 
     28     switch (srcFormat) {
     29         case kBGRA_8888_ColorFormat:
     30             pipeline.append(SkRasterPipeline::load_bgra, &src_ctx);
     31             break;
     32         case kRGBA_8888_ColorFormat:
     33             pipeline.append(SkRasterPipeline::load_8888, &src_ctx);
     34             break;
     35         case kRGBA_U16_BE_ColorFormat:
     36             pipeline.append(SkRasterPipeline::load_u16_be, &src_ctx);
     37             break;
     38         case kRGB_U16_BE_ColorFormat:
     39             pipeline.append(SkRasterPipeline::load_rgb_u16_be, &src_ctx);
     40             break;
     41         default:
     42             SkCSXformPrintf("F16/F32 sources must be linear.\n");
     43             return false;
     44     }
     45 
     46     pipeline.extend(fElementsPipeline);
     47 
     48     if (kPremul_SkAlphaType == alphaType) {
     49         pipeline.append(SkRasterPipeline::premul);
     50     }
     51 
     52     switch (dstFormat) {
     53         case kBGRA_8888_ColorFormat:
     54             pipeline.append(SkRasterPipeline::store_bgra, &dst_ctx);
     55             break;
     56         case kRGBA_8888_ColorFormat:
     57             pipeline.append(SkRasterPipeline::store_8888, &dst_ctx);
     58             break;
     59         case kRGBA_F16_ColorFormat:
     60             if (!fLinearDstGamma) {
     61                 return false;
     62             }
     63             pipeline.append(SkRasterPipeline::store_f16, &dst_ctx);
     64             break;
     65         case kRGBA_F32_ColorFormat:
     66             if (!fLinearDstGamma) {
     67                 return false;
     68             }
     69             pipeline.append(SkRasterPipeline::store_f32, &dst_ctx);
     70             break;
     71         case kBGR_565_ColorFormat:
     72             if (kOpaque_SkAlphaType != alphaType) {
     73                 return false;
     74             }
     75             pipeline.append(SkRasterPipeline::store_565, &dst_ctx);
     76             break;
     77         default:
     78             return false;
     79     }
     80     pipeline.run(0,0, count,1);
     81 
     82     return true;
     83 }
     84 
     85 static inline bool gamma_to_parametric(SkColorSpaceTransferFn* coeffs, const SkGammas& gammas,
     86                                        int channel) {
     87     switch (gammas.type(channel)) {
     88         case SkGammas::Type::kNamed_Type:
     89             return named_to_parametric(coeffs, gammas.data(channel).fNamed);
     90         case SkGammas::Type::kValue_Type:
     91             value_to_parametric(coeffs, gammas.data(channel).fValue);
     92             return true;
     93         case SkGammas::Type::kParam_Type:
     94             *coeffs = gammas.params(channel);
     95             return true;
     96         default:
     97             return false;
     98     }
     99 }
    100 
    101 SkColorSpaceXform_A2B::SkColorSpaceXform_A2B(SkColorSpace_A2B* srcSpace,
    102                                              SkColorSpace_XYZ* dstSpace)
    103     : fElementsPipeline(&fAlloc)
    104     , fLinearDstGamma(kLinear_SkGammaNamed == dstSpace->gammaNamed()) {
    105 #if (SkCSXformPrintfDefined)
    106     static const char* debugGammaNamed[4] = {
    107         "Linear", "SRGB", "2.2", "NonStandard"
    108     };
    109     static const char* debugGammas[5] = {
    110         "None", "Named", "Value", "Table", "Param"
    111     };
    112 #endif
    113     int currentChannels;
    114     switch (srcSpace->iccType()) {
    115         case SkColorSpace::kRGB_Type:
    116             currentChannels = 3;
    117             break;
    118         case SkColorSpace::kCMYK_Type: {
    119             currentChannels = 4;
    120             // CMYK images from JPEGs (the only format that supports it) are actually
    121             // inverted CMYK, so we need to invert every channel.
    122             fElementsPipeline.append(SkRasterPipeline::invert);
    123             break;
    124         }
    125         default:
    126             currentChannels = 0;
    127             SkASSERT(false);
    128     }
    129     // add in all input color space -> PCS xforms
    130     for (int i = 0; i < srcSpace->count(); ++i) {
    131         const SkColorSpace_A2B::Element& e = srcSpace->element(i);
    132         SkASSERT(e.inputChannels() == currentChannels);
    133         currentChannels = e.outputChannels();
    134         switch (e.type()) {
    135             case SkColorSpace_A2B::Element::Type::kGammaNamed: {
    136                 if (kLinear_SkGammaNamed == e.gammaNamed()) {
    137                     break;
    138                 }
    139 
    140                 // Take the fast path for ordinary sRGB.
    141                 if (3 == currentChannels && kSRGB_SkGammaNamed == e.gammaNamed()) {
    142                     SkCSXformPrintf("fast path from sRGB\n");
    143                     fElementsPipeline.append(SkRasterPipeline::from_srgb);
    144                     break;
    145                 }
    146 
    147                 SkCSXformPrintf("Gamma stage added: %s\n", debugGammaNamed[(int)e.gammaNamed()]);
    148                 auto fn = fAlloc.make<SkColorSpaceTransferFn>();
    149                 SkAssertResult(named_to_parametric(fn, e.gammaNamed()));
    150 
    151                 if (is_just_gamma(*fn)) {
    152                     fElementsPipeline.append(SkRasterPipeline::gamma, &fn->fG);
    153                 } else {
    154                     fElementsPipeline.append(SkRasterPipeline::parametric_r, fn);
    155                     fElementsPipeline.append(SkRasterPipeline::parametric_g, fn);
    156                     fElementsPipeline.append(SkRasterPipeline::parametric_b, fn);
    157                 }
    158                 break;
    159             }
    160             case SkColorSpace_A2B::Element::Type::kGammas: {
    161                 const SkGammas& gammas = e.gammas();
    162                 SkCSXformPrintf("Gamma stage added:");
    163                 for (int channel = 0; channel < gammas.channels(); ++channel) {
    164                     SkCSXformPrintf("  %s", debugGammas[(int)gammas.type(channel)]);
    165                 }
    166                 SkCSXformPrintf("\n");
    167                 bool gammaNeedsRef = false;
    168                 for (int channel = 0; channel < gammas.channels(); ++channel) {
    169                     if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
    170                         SkTableTransferFn table = {
    171                                 gammas.table(channel),
    172                                 gammas.data(channel).fTable.fSize,
    173                         };
    174 
    175                         gammaNeedsRef |= !this->buildTableFn(&table);
    176                         this->addTableFn(table, channel);
    177                     } else {
    178                         SkColorSpaceTransferFn fn;
    179                         SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
    180                         this->addTransferFn(fn, channel);
    181                     }
    182                 }
    183                 if (gammaNeedsRef) {
    184                     this->copy(sk_ref_sp(&gammas));
    185                 }
    186                 break;
    187             }
    188             case SkColorSpace_A2B::Element::Type::kCLUT: {
    189                 SkCSXformPrintf("CLUT (%d -> %d) stage added\n", e.colorLUT().inputChannels(),
    190                                                                  e.colorLUT().outputChannels());
    191 
    192                 struct Ctx : SkJumper_ColorLookupTableCtx {
    193                     sk_sp<const SkColorLookUpTable> clut;
    194                 };
    195                 auto ctx = fAlloc.make<Ctx>();
    196                 ctx->clut  = sk_ref_sp(&e.colorLUT());
    197                 ctx->table = ctx->clut->table();
    198                 for (int i = 0; i < ctx->clut->inputChannels(); i++) {
    199                     ctx->limits[i] = ctx->clut->gridPoints(i);
    200                 }
    201 
    202                 switch  (e.colorLUT().inputChannels()) {
    203                     case 3: fElementsPipeline.append(SkRasterPipeline::clut_3D, ctx); break;
    204                     case 4: fElementsPipeline.append(SkRasterPipeline::clut_4D, ctx); break;
    205                     default: SkDEBUGFAIL("need to handle 1 or 2 channel color lookup tables.");
    206                 }
    207                 fElementsPipeline.append(SkRasterPipeline::clamp_0);
    208                 fElementsPipeline.append(SkRasterPipeline::clamp_1);
    209                 break;
    210             }
    211             case SkColorSpace_A2B::Element::Type::kMatrix:
    212                 if (!e.matrix().isIdentity()) {
    213                     SkCSXformPrintf("Matrix stage added\n");
    214                     addMatrix(e.matrix());
    215                 }
    216                 break;
    217         }
    218     }
    219 
    220     // Lab PCS -> XYZ PCS
    221     if (SkColorSpace_A2B::PCS::kLAB == srcSpace->pcs()) {
    222         SkCSXformPrintf("Lab -> XYZ element added\n");
    223         fElementsPipeline.append(SkRasterPipeline::lab_to_xyz);
    224     }
    225 
    226     // we should now be in XYZ PCS
    227     SkASSERT(3 == currentChannels);
    228 
    229     // and XYZ PCS -> output color space xforms
    230     if (!dstSpace->fromXYZD50()->isIdentity()) {
    231         addMatrix(*dstSpace->fromXYZD50());
    232     }
    233 
    234     switch (dstSpace->gammaNamed()) {
    235         case kLinear_SkGammaNamed:
    236             // do nothing
    237             break;
    238         case k2Dot2Curve_SkGammaNamed: {
    239             fElementsPipeline.append(SkRasterPipeline::gamma, this->copy(1/2.2f));
    240             break;
    241         }
    242         case kSRGB_SkGammaNamed:
    243             fElementsPipeline.append(SkRasterPipeline::to_srgb);
    244             break;
    245         case kNonStandard_SkGammaNamed: {
    246             for (int channel = 0; channel < 3; ++channel) {
    247                 const SkGammas& gammas = *dstSpace->gammas();
    248                 if (SkGammas::Type::kTable_Type == gammas.type(channel)) {
    249                     static constexpr int kInvTableSize = 256;
    250                     auto storage = fAlloc.makeArray<float>(kInvTableSize);
    251                     invert_table_gamma(storage, nullptr, kInvTableSize,
    252                                        gammas.table(channel),
    253                                        gammas.data(channel).fTable.fSize);
    254                     SkTableTransferFn table = { storage, kInvTableSize };
    255                     this->addTableFn(table, channel);
    256                 } else {
    257                     SkColorSpaceTransferFn fn;
    258                     SkAssertResult(gamma_to_parametric(&fn, gammas, channel));
    259                     this->addTransferFn(fn.invert(), channel);
    260                 }
    261             }
    262         }
    263         break;
    264     }
    265 }
    266 
    267 void SkColorSpaceXform_A2B::addTransferFn(const SkColorSpaceTransferFn& fn, int channelIndex) {
    268     switch (channelIndex) {
    269         case 0:
    270             fElementsPipeline.append(SkRasterPipeline::parametric_r, this->copy(fn));
    271             break;
    272         case 1:
    273             fElementsPipeline.append(SkRasterPipeline::parametric_g, this->copy(fn));
    274             break;
    275         case 2:
    276             fElementsPipeline.append(SkRasterPipeline::parametric_b, this->copy(fn));
    277             break;
    278         case 3:
    279             fElementsPipeline.append(SkRasterPipeline::parametric_a, this->copy(fn));
    280             break;
    281         default:
    282             SkASSERT(false);
    283     }
    284 }
    285 
    286 /**
    287  *  |fn| is an in-out parameter.  If the table is too small to perform reasonable table-lookups
    288  *  without interpolation, we will build a bigger table.
    289  *
    290  *  This returns false if we use the original table, meaning we do nothing here but need to keep
    291  *  a reference to the original table.  This returns true if we build a new table and the original
    292  *  table can be discarded.
    293  */
    294 bool SkColorSpaceXform_A2B::buildTableFn(SkTableTransferFn* fn) {
    295     // Arbitrary, but seems like a reasonable guess.
    296     static constexpr int kMinTableSize = 256;
    297 
    298     if (fn->fSize >= kMinTableSize) {
    299         return false;
    300     }
    301 
    302     float* outTable = fAlloc.makeArray<float>(kMinTableSize);
    303     float step = 1.0f / (kMinTableSize - 1);
    304     for (int i = 0; i < kMinTableSize; i++) {
    305         outTable[i] = interp_lut(i * step, fn->fData, fn->fSize);
    306     }
    307 
    308     fn->fData = outTable;
    309     fn->fSize = kMinTableSize;
    310     return true;
    311 }
    312 
    313 void SkColorSpaceXform_A2B::addTableFn(const SkTableTransferFn& fn, int channelIndex) {
    314     switch (channelIndex) {
    315         case 0:
    316             fElementsPipeline.append(SkRasterPipeline::table_r, this->copy(fn));
    317             break;
    318         case 1:
    319             fElementsPipeline.append(SkRasterPipeline::table_g, this->copy(fn));
    320             break;
    321         case 2:
    322             fElementsPipeline.append(SkRasterPipeline::table_b, this->copy(fn));
    323             break;
    324         case 3:
    325             fElementsPipeline.append(SkRasterPipeline::table_a, this->copy(fn));
    326             break;
    327         default:
    328             SkASSERT(false);
    329     }
    330 }
    331 
    332 void SkColorSpaceXform_A2B::addMatrix(const SkMatrix44& m44) {
    333     auto m = fAlloc.makeArray<float>(12);
    334     m[0] = m44.get(0,0); m[ 1] = m44.get(1,0); m[ 2] = m44.get(2,0);
    335     m[3] = m44.get(0,1); m[ 4] = m44.get(1,1); m[ 5] = m44.get(2,1);
    336     m[6] = m44.get(0,2); m[ 7] = m44.get(1,2); m[ 8] = m44.get(2,2);
    337     m[9] = m44.get(0,3); m[10] = m44.get(1,3); m[11] = m44.get(2,3);
    338 
    339     SkASSERT(m44.get(3,0) == 0.0f);
    340     SkASSERT(m44.get(3,1) == 0.0f);
    341     SkASSERT(m44.get(3,2) == 0.0f);
    342     SkASSERT(m44.get(3,3) == 1.0f);
    343 
    344     fElementsPipeline.append(SkRasterPipeline::matrix_3x4, m);
    345     fElementsPipeline.append(SkRasterPipeline::clamp_0);
    346     fElementsPipeline.append(SkRasterPipeline::clamp_1);
    347 }
    348