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 "SkAutoMalloc.h"
      9 #include "SkColorSpacePriv.h"
     10 #include "SkColorSpaceXformPriv.h"
     11 #include "SkColorSpace_Base.h"
     12 #include "SkColorSpace_XYZ.h"
     13 #include "SkEndian.h"
     14 #include "SkFixed.h"
     15 #include "SkICC.h"
     16 #include "SkICCPriv.h"
     17 #include "SkMD5.h"
     18 #include "SkString.h"
     19 #include "SkUtils.h"
     20 
     21 SkICC::SkICC(sk_sp<SkColorSpace> colorSpace)
     22     : fColorSpace(std::move(colorSpace))
     23 {}
     24 
     25 sk_sp<SkICC> SkICC::Make(const void* ptr, size_t len) {
     26     sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeICC(ptr, len);
     27     if (!colorSpace) {
     28         return nullptr;
     29     }
     30 
     31     return sk_sp<SkICC>(new SkICC(std::move(colorSpace)));
     32 }
     33 
     34 bool SkICC::toXYZD50(SkMatrix44* toXYZD50) const {
     35     const SkMatrix44* m = as_CSB(fColorSpace)->toXYZD50();
     36     if (!m) {
     37         return false;
     38     }
     39 
     40     *toXYZD50 = *m;
     41     return true;
     42 }
     43 
     44 bool SkICC::isNumericalTransferFn(SkColorSpaceTransferFn* coeffs) const {
     45     return as_CSB(fColorSpace)->onIsNumericalTransferFn(coeffs);
     46 }
     47 
     48 static const int kDefaultTableSize = 512; // Arbitrary
     49 
     50 void fn_to_table(float* tablePtr, const SkColorSpaceTransferFn& fn) {
     51     // Y = (aX + b)^g + e  for X >= d
     52     // Y = cX + f          otherwise
     53     for (int i = 0; i < kDefaultTableSize; i++) {
     54         float x = ((float) i) / ((float) (kDefaultTableSize - 1));
     55         if (x >= fn.fD) {
     56             tablePtr[i] = clamp_0_1(powf(fn.fA * x + fn.fB, fn.fG) + fn.fE);
     57         } else {
     58             tablePtr[i] = clamp_0_1(fn.fC * x + fn.fF);
     59         }
     60     }
     61 }
     62 
     63 void copy_to_table(float* tablePtr, const SkGammas* gammas, int index) {
     64     SkASSERT(gammas->isTable(index));
     65     const float* ptr = gammas->table(index);
     66     const size_t bytes = gammas->tableSize(index) * sizeof(float);
     67     memcpy(tablePtr, ptr, bytes);
     68 }
     69 
     70 bool SkICC::rawTransferFnData(Tables* tables) const {
     71     if (SkColorSpace_Base::Type::kA2B == as_CSB(fColorSpace)->type()) {
     72         return false;
     73     }
     74     SkColorSpace_XYZ* colorSpace = (SkColorSpace_XYZ*) fColorSpace.get();
     75 
     76     SkColorSpaceTransferFn fn;
     77     if (this->isNumericalTransferFn(&fn)) {
     78         tables->fStorage = SkData::MakeUninitialized(kDefaultTableSize * sizeof(float));
     79         fn_to_table((float*) tables->fStorage->writable_data(), fn);
     80         tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0;
     81         tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = kDefaultTableSize;
     82         return true;
     83     }
     84 
     85     const SkGammas* gammas = colorSpace->gammas();
     86     SkASSERT(gammas);
     87     if (gammas->data(0) == gammas->data(1) && gammas->data(0) == gammas->data(2)) {
     88         SkASSERT(gammas->isTable(0));
     89         tables->fStorage = SkData::MakeUninitialized(gammas->tableSize(0) * sizeof(float));
     90         copy_to_table((float*) tables->fStorage->writable_data(), gammas, 0);
     91         tables->fRed.fOffset = tables->fGreen.fOffset = tables->fBlue.fOffset = 0;
     92         tables->fRed.fCount = tables->fGreen.fCount = tables->fBlue.fCount = gammas->tableSize(0);
     93         return true;
     94     }
     95 
     96     // Determine the storage size.
     97     size_t storageSize = 0;
     98     for (int i = 0; i < 3; i++) {
     99         if (gammas->isTable(i)) {
    100             storageSize += gammas->tableSize(i) * sizeof(float);
    101         } else {
    102             storageSize += kDefaultTableSize * sizeof(float);
    103         }
    104     }
    105 
    106     // Fill in the tables.
    107     tables->fStorage = SkData::MakeUninitialized(storageSize);
    108     float* ptr = (float*) tables->fStorage->writable_data();
    109     size_t offset = 0;
    110     Channel rgb[3];
    111     for (int i = 0; i < 3; i++) {
    112         if (gammas->isTable(i)) {
    113             copy_to_table(ptr, gammas, i);
    114             rgb[i].fOffset = offset;
    115             rgb[i].fCount = gammas->tableSize(i);
    116             offset += rgb[i].fCount * sizeof(float);
    117             ptr += rgb[i].fCount;
    118             continue;
    119         }
    120 
    121         if (gammas->isNamed(i)) {
    122             SkAssertResult(named_to_parametric(&fn, gammas->data(i).fNamed));
    123         } else if (gammas->isValue(i)) {
    124             value_to_parametric(&fn, gammas->data(i).fValue);
    125         } else {
    126             SkASSERT(gammas->isParametric(i));
    127             fn = gammas->params(i);
    128         }
    129 
    130         fn_to_table(ptr, fn);
    131         rgb[i].fOffset = offset;
    132         rgb[i].fCount = kDefaultTableSize;
    133         offset += kDefaultTableSize * sizeof(float);
    134         ptr += kDefaultTableSize;
    135     }
    136 
    137     tables->fRed = rgb[0];
    138     tables->fGreen = rgb[1];
    139     tables->fBlue = rgb[2];
    140     return true;
    141 }
    142 
    143 ///////////////////////////////////////////////////////////////////////////////////////////////////
    144 
    145 static constexpr char kDescriptionTagBodyPrefix[12] =
    146         { 'G', 'o', 'o', 'g', 'l', 'e', '/', 'S', 'k', 'i', 'a' , '/'};
    147 
    148 static constexpr size_t kICCDescriptionTagSize = 44;
    149 
    150 static_assert(kICCDescriptionTagSize ==
    151               sizeof(kDescriptionTagBodyPrefix) + 2 * sizeof(SkMD5::Digest), "");
    152 static constexpr size_t kDescriptionTagBodySize = kICCDescriptionTagSize * 2;  // ascii->utf16be
    153 
    154 static_assert(SkIsAlign4(kDescriptionTagBodySize), "Description must be aligned to 4-bytes.");
    155 static constexpr uint32_t kDescriptionTagHeader[7] {
    156     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
    157     0,                                                       // Reserved
    158     SkEndian_SwapBE32(1),                                    // Number of records
    159     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
    160     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
    161     SkEndian_SwapBE32(kDescriptionTagBodySize),              // Length of string
    162     SkEndian_SwapBE32(28),                                   // Offset of string
    163 };
    164 
    165 static constexpr uint32_t kWhitePointTag[5] {
    166     SkEndian_SwapBE32(kXYZ_PCSSpace),
    167     0,
    168     SkEndian_SwapBE32(0x0000f6d6), // X = 0.96420 (D50)
    169     SkEndian_SwapBE32(0x00010000), // Y = 1.00000 (D50)
    170     SkEndian_SwapBE32(0x0000d32d), // Z = 0.82491 (D50)
    171 };
    172 
    173 // Google Inc. 2016 (UTF-16)
    174 static constexpr uint8_t kCopyrightTagBody[] = {
    175         0x00, 0x47, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x20, 0x00,
    176         0x49, 0x00, 0x6e, 0x00, 0x63, 0x00, 0x2e, 0x00, 0x20, 0x00, 0x32, 0x00, 0x30, 0x00, 0x31,
    177         0x00, 0x36,
    178 };
    179 static_assert(SkIsAlign4(sizeof(kCopyrightTagBody)), "Copyright must be aligned to 4-bytes.");
    180 static constexpr uint32_t kCopyrightTagHeader[7] {
    181     SkEndian_SwapBE32(kTAG_TextType),                        // Type signature
    182     0,                                                       // Reserved
    183     SkEndian_SwapBE32(1),                                    // Number of records
    184     SkEndian_SwapBE32(12),                                   // Record size (must be 12)
    185     SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
    186     SkEndian_SwapBE32(sizeof(kCopyrightTagBody)),            // Length of string
    187     SkEndian_SwapBE32(28),                                   // Offset of string
    188 };
    189 
    190 // We will write a profile with the minimum nine required tags.
    191 static constexpr uint32_t kICCNumEntries = 9;
    192 
    193 static constexpr uint32_t kTAG_desc = SkSetFourByteTag('d', 'e', 's', 'c');
    194 static constexpr uint32_t kTAG_desc_Bytes = sizeof(kDescriptionTagHeader) +
    195                                             kDescriptionTagBodySize;
    196 static constexpr uint32_t kTAG_desc_Offset = kICCHeaderSize +
    197                                              kICCNumEntries * kICCTagTableEntrySize;
    198 
    199 static constexpr uint32_t kTAG_XYZ_Bytes = 20;
    200 static constexpr uint32_t kTAG_rXYZ_Offset = kTAG_desc_Offset + kTAG_desc_Bytes;
    201 static constexpr uint32_t kTAG_gXYZ_Offset = kTAG_rXYZ_Offset + kTAG_XYZ_Bytes;
    202 static constexpr uint32_t kTAG_bXYZ_Offset = kTAG_gXYZ_Offset + kTAG_XYZ_Bytes;
    203 
    204 static constexpr uint32_t kTAG_TRC_Bytes = 40;
    205 static constexpr uint32_t kTAG_rTRC_Offset = kTAG_bXYZ_Offset + kTAG_XYZ_Bytes;
    206 static constexpr uint32_t kTAG_gTRC_Offset = kTAG_rTRC_Offset;
    207 static constexpr uint32_t kTAG_bTRC_Offset = kTAG_rTRC_Offset;
    208 
    209 static constexpr uint32_t kTAG_wtpt = SkSetFourByteTag('w', 't', 'p', 't');
    210 static constexpr uint32_t kTAG_wtpt_Offset = kTAG_bTRC_Offset + kTAG_TRC_Bytes;
    211 
    212 static constexpr uint32_t kTAG_cprt = SkSetFourByteTag('c', 'p', 'r', 't');
    213 static constexpr uint32_t kTAG_cprt_Bytes = sizeof(kCopyrightTagHeader) +
    214                                             sizeof(kCopyrightTagBody);
    215 static constexpr uint32_t kTAG_cprt_Offset = kTAG_wtpt_Offset + kTAG_XYZ_Bytes;
    216 
    217 static constexpr uint32_t kICCProfileSize = kTAG_cprt_Offset + kTAG_cprt_Bytes;
    218 
    219 static constexpr uint32_t kICCHeader[kICCHeaderSize / 4] {
    220     SkEndian_SwapBE32(kICCProfileSize),  // Size of the profile
    221     0,                                   // Preferred CMM type (ignored)
    222     SkEndian_SwapBE32(0x02100000),       // Version 2.1
    223     SkEndian_SwapBE32(kDisplay_Profile), // Display device profile
    224     SkEndian_SwapBE32(kRGB_ColorSpace),  // RGB input color space
    225     SkEndian_SwapBE32(kXYZ_PCSSpace),    // XYZ profile connection space
    226     0, 0, 0,                             // Date and time (ignored)
    227     SkEndian_SwapBE32(kACSP_Signature),  // Profile signature
    228     0,                                   // Platform target (ignored)
    229     0x00000000,                          // Flags: not embedded, can be used independently
    230     0,                                   // Device manufacturer (ignored)
    231     0,                                   // Device model (ignored)
    232     0, 0,                                // Device attributes (ignored)
    233     SkEndian_SwapBE32(1),                // Relative colorimetric rendering intent
    234     SkEndian_SwapBE32(0x0000f6d6),       // D50 standard illuminant (X)
    235     SkEndian_SwapBE32(0x00010000),       // D50 standard illuminant (Y)
    236     SkEndian_SwapBE32(0x0000d32d),       // D50 standard illuminant (Z)
    237     0,                                   // Profile creator (ignored)
    238     0, 0, 0, 0,                          // Profile id checksum (ignored)
    239     0, 0, 0, 0, 0, 0, 0,                 // Reserved (ignored)
    240     SkEndian_SwapBE32(kICCNumEntries),   // Number of tags
    241 };
    242 
    243 static constexpr uint32_t kICCTagTable[3 * kICCNumEntries] {
    244     // Profile description
    245     SkEndian_SwapBE32(kTAG_desc),
    246     SkEndian_SwapBE32(kTAG_desc_Offset),
    247     SkEndian_SwapBE32(kTAG_desc_Bytes),
    248 
    249     // rXYZ
    250     SkEndian_SwapBE32(kTAG_rXYZ),
    251     SkEndian_SwapBE32(kTAG_rXYZ_Offset),
    252     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
    253 
    254     // gXYZ
    255     SkEndian_SwapBE32(kTAG_gXYZ),
    256     SkEndian_SwapBE32(kTAG_gXYZ_Offset),
    257     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
    258 
    259     // bXYZ
    260     SkEndian_SwapBE32(kTAG_bXYZ),
    261     SkEndian_SwapBE32(kTAG_bXYZ_Offset),
    262     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
    263 
    264     // rTRC
    265     SkEndian_SwapBE32(kTAG_rTRC),
    266     SkEndian_SwapBE32(kTAG_rTRC_Offset),
    267     SkEndian_SwapBE32(kTAG_TRC_Bytes),
    268 
    269     // gTRC
    270     SkEndian_SwapBE32(kTAG_gTRC),
    271     SkEndian_SwapBE32(kTAG_gTRC_Offset),
    272     SkEndian_SwapBE32(kTAG_TRC_Bytes),
    273 
    274     // bTRC
    275     SkEndian_SwapBE32(kTAG_bTRC),
    276     SkEndian_SwapBE32(kTAG_bTRC_Offset),
    277     SkEndian_SwapBE32(kTAG_TRC_Bytes),
    278 
    279     // White point
    280     SkEndian_SwapBE32(kTAG_wtpt),
    281     SkEndian_SwapBE32(kTAG_wtpt_Offset),
    282     SkEndian_SwapBE32(kTAG_XYZ_Bytes),
    283 
    284     // Copyright
    285     SkEndian_SwapBE32(kTAG_cprt),
    286     SkEndian_SwapBE32(kTAG_cprt_Offset),
    287     SkEndian_SwapBE32(kTAG_cprt_Bytes),
    288 };
    289 
    290 static void write_xyz_tag(uint32_t* ptr, const SkMatrix44& toXYZ, int col) {
    291     ptr[0] = SkEndian_SwapBE32(kXYZ_PCSSpace);
    292     ptr[1] = 0;
    293     ptr[2] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(0, col)));
    294     ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(1, col)));
    295     ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(toXYZ.getFloat(2, col)));
    296 }
    297 
    298 static void write_trc_tag(uint32_t* ptr, const SkColorSpaceTransferFn& fn) {
    299     ptr[0] = SkEndian_SwapBE32(kTAG_ParaCurveType);
    300     ptr[1] = 0;
    301     ptr[2] = (uint32_t) (SkEndian_SwapBE16(kGABCDEF_ParaCurveType));
    302     ptr[3] = SkEndian_SwapBE32(SkFloatToFixed(fn.fG));
    303     ptr[4] = SkEndian_SwapBE32(SkFloatToFixed(fn.fA));
    304     ptr[5] = SkEndian_SwapBE32(SkFloatToFixed(fn.fB));
    305     ptr[6] = SkEndian_SwapBE32(SkFloatToFixed(fn.fC));
    306     ptr[7] = SkEndian_SwapBE32(SkFloatToFixed(fn.fD));
    307     ptr[8] = SkEndian_SwapBE32(SkFloatToFixed(fn.fE));
    308     ptr[9] = SkEndian_SwapBE32(SkFloatToFixed(fn.fF));
    309 }
    310 
    311 static bool is_3x3(const SkMatrix44& toXYZD50) {
    312     return 0.0f == toXYZD50.get(3, 0) && 0.0f == toXYZD50.get(3, 1) && 0.0f == toXYZD50.get(3, 2) &&
    313            0.0f == toXYZD50.get(0, 3) && 0.0f == toXYZD50.get(1, 3) && 0.0f == toXYZD50.get(2, 3) &&
    314            1.0f == toXYZD50.get(3, 3);
    315 }
    316 
    317 static bool nearly_equal(float x, float y) {
    318     // A note on why I chose this tolerance:  transfer_fn_almost_equal() uses a
    319     // tolerance of 0.001f, which doesn't seem to be enough to distinguish
    320     // between similar transfer functions, for example: gamma2.2 and sRGB.
    321     //
    322     // If the tolerance is 0.0f, then this we can't distinguish between two
    323     // different encodings of what is clearly the same colorspace.  Some
    324     // experimentation with example files lead to this number:
    325     static constexpr float kTolerance = 1.0f / (1 << 11);
    326     return ::fabsf(x - y) <= kTolerance;
    327 }
    328 
    329 static bool nearly_equal(const SkColorSpaceTransferFn& u,
    330                          const SkColorSpaceTransferFn& v) {
    331     return nearly_equal(u.fG, v.fG)
    332         && nearly_equal(u.fA, v.fA)
    333         && nearly_equal(u.fB, v.fB)
    334         && nearly_equal(u.fC, v.fC)
    335         && nearly_equal(u.fD, v.fD)
    336         && nearly_equal(u.fE, v.fE)
    337         && nearly_equal(u.fF, v.fF);
    338 }
    339 
    340 static bool nearly_equal(const SkMatrix44& toXYZD50, const float standard[9]) {
    341     return nearly_equal(toXYZD50.getFloat(0, 0), standard[0])
    342         && nearly_equal(toXYZD50.getFloat(0, 1), standard[1])
    343         && nearly_equal(toXYZD50.getFloat(0, 2), standard[2])
    344         && nearly_equal(toXYZD50.getFloat(1, 0), standard[3])
    345         && nearly_equal(toXYZD50.getFloat(1, 1), standard[4])
    346         && nearly_equal(toXYZD50.getFloat(1, 2), standard[5])
    347         && nearly_equal(toXYZD50.getFloat(2, 0), standard[6])
    348         && nearly_equal(toXYZD50.getFloat(2, 1), standard[7])
    349         && nearly_equal(toXYZD50.getFloat(2, 2), standard[8])
    350         && nearly_equal(toXYZD50.getFloat(0, 3), 0.0f)
    351         && nearly_equal(toXYZD50.getFloat(1, 3), 0.0f)
    352         && nearly_equal(toXYZD50.getFloat(2, 3), 0.0f)
    353         && nearly_equal(toXYZD50.getFloat(3, 0), 0.0f)
    354         && nearly_equal(toXYZD50.getFloat(3, 1), 0.0f)
    355         && nearly_equal(toXYZD50.getFloat(3, 2), 0.0f)
    356         && nearly_equal(toXYZD50.getFloat(3, 3), 1.0f);
    357 }
    358 
    359 // Return nullptr if the color profile doen't have a special name.
    360 const char* get_color_profile_description(const SkColorSpaceTransferFn& fn,
    361                                           const SkMatrix44& toXYZD50) {
    362     bool srgb_xfer = nearly_equal(fn, gSRGB_TransferFn);
    363     bool srgb_gamut = nearly_equal(toXYZD50, gSRGB_toXYZD50);
    364     if (srgb_xfer && srgb_gamut) {
    365         return "sRGB";
    366     }
    367     bool line_xfer = nearly_equal(fn, gLinear_TransferFn);
    368     if (line_xfer && srgb_gamut) {
    369         return "Linear Transfer with sRGB Gamut";
    370     }
    371     bool twoDotTwo = nearly_equal(fn, g2Dot2_TransferFn);
    372     if (twoDotTwo && srgb_gamut) {
    373         return "2.2 Transfer with sRGB Gamut";
    374     }
    375     if (twoDotTwo && nearly_equal(toXYZD50, gAdobeRGB_toXYZD50)) {
    376         return "AdobeRGB";
    377     }
    378     bool dcip3_gamut = nearly_equal(toXYZD50, gDCIP3_toXYZD50);
    379     if (srgb_xfer || line_xfer) {
    380         if (srgb_xfer && dcip3_gamut) {
    381             return "sRGB Transfer with DCI-P3 Gamut";
    382         }
    383         if (line_xfer && dcip3_gamut) {
    384             return "Linear Transfer with DCI-P3 Gamut";
    385         }
    386         bool rec2020 = nearly_equal(toXYZD50, gRec2020_toXYZD50);
    387         if (srgb_xfer && rec2020) {
    388             return "sRGB Transfer with Rec-BT-2020 Gamut";
    389         }
    390         if (line_xfer && rec2020) {
    391             return "Linear Transfer with Rec-BT-2020 Gamut";
    392         }
    393     }
    394     if (dcip3_gamut && nearly_equal(fn, gDCIP3_TransferFn)) {
    395         return "DCI-P3";
    396     }
    397     return nullptr;
    398 }
    399 
    400 static void get_color_profile_tag(char dst[kICCDescriptionTagSize],
    401                                  const SkColorSpaceTransferFn& fn,
    402                                  const SkMatrix44& toXYZD50) {
    403     SkASSERT(dst);
    404     if (const char* description = get_color_profile_description(fn, toXYZD50)) {
    405         SkASSERT(strlen(description) < kICCDescriptionTagSize);
    406         strncpy(dst, description, kICCDescriptionTagSize);
    407         // "If the length of src is less than n, strncpy() writes additional
    408         // null bytes to dest to ensure that a total of n bytes are written."
    409     } else {
    410         strncpy(dst, kDescriptionTagBodyPrefix, sizeof(kDescriptionTagBodyPrefix));
    411         SkMD5 md5;
    412         for (int i = 0; i < 3; ++i) {
    413             for (int j = 0; j < 3; ++j) {
    414                 float value = toXYZD50.getFloat(i,j);
    415                 md5.write(&value, sizeof(value));
    416             }
    417         }
    418         static_assert(sizeof(fn) == sizeof(float) * 7, "packed");
    419         md5.write(&fn, sizeof(fn));
    420         SkMD5::Digest digest;
    421         md5.finish(digest);
    422         char* ptr = dst + sizeof(kDescriptionTagBodyPrefix);
    423         for (unsigned i = 0; i < sizeof(SkMD5::Digest); ++i) {
    424             uint8_t byte = digest.data[i];
    425             *ptr++ = SkHexadecimalDigits::gUpper[byte >> 4];
    426             *ptr++ = SkHexadecimalDigits::gUpper[byte & 0xF];
    427         }
    428         SkASSERT(ptr == dst + kICCDescriptionTagSize);
    429     }
    430 }
    431 
    432 SkString SkICCGetColorProfileTag(const SkColorSpaceTransferFn& fn,
    433                                  const SkMatrix44& toXYZD50) {
    434     char tag[kICCDescriptionTagSize];
    435     get_color_profile_tag(tag, fn, toXYZD50);
    436     size_t len = kICCDescriptionTagSize;
    437     while (len > 0 && tag[len - 1] == '\0') {
    438         --len;  // tag is padded out with zeros
    439     }
    440     SkASSERT(len != 0);
    441     return SkString(tag, len);
    442 }
    443 
    444 // returns pointer just beyond where we just wrote.
    445 static uint8_t* string_copy_ascii_to_utf16be(uint8_t* dst, const char* src, size_t count) {
    446     while (count-- > 0) {
    447         *dst++ = 0;
    448         *dst++ = (uint8_t)(*src++);
    449     }
    450     return dst;
    451 }
    452 
    453 sk_sp<SkData> SkICC::WriteToICC(const SkColorSpaceTransferFn& fn, const SkMatrix44& toXYZD50) {
    454     if (!is_3x3(toXYZD50) || !is_valid_transfer_fn(fn)) {
    455         return nullptr;
    456     }
    457 
    458     SkAutoMalloc profile(kICCProfileSize);
    459     uint8_t* ptr = (uint8_t*) profile.get();
    460 
    461     // Write profile header
    462     memcpy(ptr, kICCHeader, sizeof(kICCHeader));
    463     ptr += sizeof(kICCHeader);
    464 
    465     // Write tag table
    466     memcpy(ptr, kICCTagTable, sizeof(kICCTagTable));
    467     ptr += sizeof(kICCTagTable);
    468 
    469     // Write profile description tag
    470     memcpy(ptr, kDescriptionTagHeader, sizeof(kDescriptionTagHeader));
    471     ptr += sizeof(kDescriptionTagHeader);
    472     {
    473         char colorProfileTag[kICCDescriptionTagSize];
    474         get_color_profile_tag(colorProfileTag, fn, toXYZD50);
    475         ptr = string_copy_ascii_to_utf16be(ptr, colorProfileTag, kICCDescriptionTagSize);
    476     }
    477 
    478     // Write XYZ tags
    479     write_xyz_tag((uint32_t*) ptr, toXYZD50, 0);
    480     ptr += kTAG_XYZ_Bytes;
    481     write_xyz_tag((uint32_t*) ptr, toXYZD50, 1);
    482     ptr += kTAG_XYZ_Bytes;
    483     write_xyz_tag((uint32_t*) ptr, toXYZD50, 2);
    484     ptr += kTAG_XYZ_Bytes;
    485 
    486     // Write TRC tag
    487     write_trc_tag((uint32_t*) ptr, fn);
    488     ptr += kTAG_TRC_Bytes;
    489 
    490     // Write white point tag (must be D50)
    491     memcpy(ptr, kWhitePointTag, sizeof(kWhitePointTag));
    492     ptr += sizeof(kWhitePointTag);
    493 
    494     // Write copyright tag
    495     memcpy(ptr, kCopyrightTagHeader, sizeof(kCopyrightTagHeader));
    496     ptr += sizeof(kCopyrightTagHeader);
    497     memcpy(ptr, kCopyrightTagBody, sizeof(kCopyrightTagBody));
    498     ptr += sizeof(kCopyrightTagBody);
    499 
    500     SkASSERT(kICCProfileSize == ptr - (uint8_t*) profile.get());
    501     return SkData::MakeFromMalloc(profile.release(), kICCProfileSize);
    502 }
    503