1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2016 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/encode/SkICC.h"
9*c8dee2aaSAndroid Build Coastguard Worker
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorSpace.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkFourByteTag.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFixed.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skcms/skcms.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkAutoMalloc.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkEndian.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkMD5.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkStreamPriv.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/encode/SkICCPriv.h"
24*c8dee2aaSAndroid Build Coastguard Worker
25*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
26*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
27*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
28*c8dee2aaSAndroid Build Coastguard Worker #include <string>
29*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
30*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
31*c8dee2aaSAndroid Build Coastguard Worker
32*c8dee2aaSAndroid Build Coastguard Worker namespace {
33*c8dee2aaSAndroid Build Coastguard Worker
34*c8dee2aaSAndroid Build Coastguard Worker // The number of input and output channels.
35*c8dee2aaSAndroid Build Coastguard Worker constexpr size_t kNumChannels = 3;
36*c8dee2aaSAndroid Build Coastguard Worker
37*c8dee2aaSAndroid Build Coastguard Worker // The D50 illuminant.
38*c8dee2aaSAndroid Build Coastguard Worker constexpr float kD50_x = 0.9642f;
39*c8dee2aaSAndroid Build Coastguard Worker constexpr float kD50_y = 1.0000f;
40*c8dee2aaSAndroid Build Coastguard Worker constexpr float kD50_z = 0.8249f;
41*c8dee2aaSAndroid Build Coastguard Worker
42*c8dee2aaSAndroid Build Coastguard Worker // This is like SkFloatToFixed, but rounds to nearest, preserving as much accuracy as possible
43*c8dee2aaSAndroid Build Coastguard Worker // when going float -> fixed -> float (it has the same accuracy when going fixed -> float -> fixed).
44*c8dee2aaSAndroid Build Coastguard Worker // The use of double is necessary to accommodate the full potential 32-bit mantissa of the 16.16
45*c8dee2aaSAndroid Build Coastguard Worker // SkFixed value, and so avoiding rounding problems with float. Also, see the comment in SkFixed.h.
float_round_to_fixed(float x)46*c8dee2aaSAndroid Build Coastguard Worker SkFixed float_round_to_fixed(float x) {
47*c8dee2aaSAndroid Build Coastguard Worker return sk_float_saturate2int((float)floor((double)x * SK_Fixed1 + 0.5));
48*c8dee2aaSAndroid Build Coastguard Worker }
49*c8dee2aaSAndroid Build Coastguard Worker
50*c8dee2aaSAndroid Build Coastguard Worker // Convert a float to a uInt16Number, with 0.0 mapping go 0 and 1.0 mapping to |one|.
float_to_uInt16Number(float x,uint16_t one)51*c8dee2aaSAndroid Build Coastguard Worker uint16_t float_to_uInt16Number(float x, uint16_t one) {
52*c8dee2aaSAndroid Build Coastguard Worker x = x * one + 0.5;
53*c8dee2aaSAndroid Build Coastguard Worker if (x > one) return one;
54*c8dee2aaSAndroid Build Coastguard Worker if (x < 0) return 0;
55*c8dee2aaSAndroid Build Coastguard Worker return static_cast<uint16_t>(x);
56*c8dee2aaSAndroid Build Coastguard Worker }
57*c8dee2aaSAndroid Build Coastguard Worker
58*c8dee2aaSAndroid Build Coastguard Worker // The uInt16Number used by curveType has 1.0 map to 0xFFFF. See section "10.6. curveType".
59*c8dee2aaSAndroid Build Coastguard Worker constexpr uint16_t kOne16CurveType = 0xFFFF;
60*c8dee2aaSAndroid Build Coastguard Worker
61*c8dee2aaSAndroid Build Coastguard Worker // The uInt16Number used to encoude XYZ values has 1.0 map to 0x8000. See section "6.3.4.2 General
62*c8dee2aaSAndroid Build Coastguard Worker // PCS encoding" and Table 11.
63*c8dee2aaSAndroid Build Coastguard Worker constexpr uint16_t kOne16XYZ = 0x8000;
64*c8dee2aaSAndroid Build Coastguard Worker
65*c8dee2aaSAndroid Build Coastguard Worker struct ICCHeader {
66*c8dee2aaSAndroid Build Coastguard Worker // Size of the profile (computed)
67*c8dee2aaSAndroid Build Coastguard Worker uint32_t size;
68*c8dee2aaSAndroid Build Coastguard Worker
69*c8dee2aaSAndroid Build Coastguard Worker // Preferred CMM type (ignored)
70*c8dee2aaSAndroid Build Coastguard Worker uint32_t cmm_type = 0;
71*c8dee2aaSAndroid Build Coastguard Worker
72*c8dee2aaSAndroid Build Coastguard Worker // Version 4.3 or 4.4 if CICP is included.
73*c8dee2aaSAndroid Build Coastguard Worker uint32_t version = SkEndian_SwapBE32(0x04300000);
74*c8dee2aaSAndroid Build Coastguard Worker
75*c8dee2aaSAndroid Build Coastguard Worker // Display device profile
76*c8dee2aaSAndroid Build Coastguard Worker uint32_t profile_class = SkEndian_SwapBE32(kDisplay_Profile);
77*c8dee2aaSAndroid Build Coastguard Worker
78*c8dee2aaSAndroid Build Coastguard Worker // RGB input color space;
79*c8dee2aaSAndroid Build Coastguard Worker uint32_t data_color_space = SkEndian_SwapBE32(kRGB_ColorSpace);
80*c8dee2aaSAndroid Build Coastguard Worker
81*c8dee2aaSAndroid Build Coastguard Worker // Profile connection space.
82*c8dee2aaSAndroid Build Coastguard Worker uint32_t pcs = SkEndian_SwapBE32(kXYZ_PCSSpace);
83*c8dee2aaSAndroid Build Coastguard Worker
84*c8dee2aaSAndroid Build Coastguard Worker // Date and time (ignored)
85*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_year = SkEndian_SwapBE16(2016);
86*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_month = SkEndian_SwapBE16(1); // 1-12
87*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_day = SkEndian_SwapBE16(1); // 1-31
88*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_hours = 0; // 0-23
89*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_minutes = 0; // 0-59
90*c8dee2aaSAndroid Build Coastguard Worker uint16_t creation_date_seconds = 0; // 0-59
91*c8dee2aaSAndroid Build Coastguard Worker
92*c8dee2aaSAndroid Build Coastguard Worker // Profile signature
93*c8dee2aaSAndroid Build Coastguard Worker uint32_t signature = SkEndian_SwapBE32(kACSP_Signature);
94*c8dee2aaSAndroid Build Coastguard Worker
95*c8dee2aaSAndroid Build Coastguard Worker // Platform target (ignored)
96*c8dee2aaSAndroid Build Coastguard Worker uint32_t platform = 0;
97*c8dee2aaSAndroid Build Coastguard Worker
98*c8dee2aaSAndroid Build Coastguard Worker // Flags: not embedded, can be used independently
99*c8dee2aaSAndroid Build Coastguard Worker uint32_t flags = 0x00000000;
100*c8dee2aaSAndroid Build Coastguard Worker
101*c8dee2aaSAndroid Build Coastguard Worker // Device manufacturer (ignored)
102*c8dee2aaSAndroid Build Coastguard Worker uint32_t device_manufacturer = 0;
103*c8dee2aaSAndroid Build Coastguard Worker
104*c8dee2aaSAndroid Build Coastguard Worker // Device model (ignored)
105*c8dee2aaSAndroid Build Coastguard Worker uint32_t device_model = 0;
106*c8dee2aaSAndroid Build Coastguard Worker
107*c8dee2aaSAndroid Build Coastguard Worker // Device attributes (ignored)
108*c8dee2aaSAndroid Build Coastguard Worker uint8_t device_attributes[8] = {0};
109*c8dee2aaSAndroid Build Coastguard Worker
110*c8dee2aaSAndroid Build Coastguard Worker // Relative colorimetric rendering intent
111*c8dee2aaSAndroid Build Coastguard Worker uint32_t rendering_intent = SkEndian_SwapBE32(1);
112*c8dee2aaSAndroid Build Coastguard Worker
113*c8dee2aaSAndroid Build Coastguard Worker // D50 standard illuminant (X, Y, Z)
114*c8dee2aaSAndroid Build Coastguard Worker uint32_t illuminant_X = SkEndian_SwapBE32(float_round_to_fixed(kD50_x));
115*c8dee2aaSAndroid Build Coastguard Worker uint32_t illuminant_Y = SkEndian_SwapBE32(float_round_to_fixed(kD50_y));
116*c8dee2aaSAndroid Build Coastguard Worker uint32_t illuminant_Z = SkEndian_SwapBE32(float_round_to_fixed(kD50_z));
117*c8dee2aaSAndroid Build Coastguard Worker
118*c8dee2aaSAndroid Build Coastguard Worker // Profile creator (ignored)
119*c8dee2aaSAndroid Build Coastguard Worker uint32_t creator = 0;
120*c8dee2aaSAndroid Build Coastguard Worker
121*c8dee2aaSAndroid Build Coastguard Worker // Profile id checksum (ignored)
122*c8dee2aaSAndroid Build Coastguard Worker uint8_t profile_id[16] = {0};
123*c8dee2aaSAndroid Build Coastguard Worker
124*c8dee2aaSAndroid Build Coastguard Worker // Reserved (ignored)
125*c8dee2aaSAndroid Build Coastguard Worker uint8_t reserved[28] = {0};
126*c8dee2aaSAndroid Build Coastguard Worker
127*c8dee2aaSAndroid Build Coastguard Worker // Technically not part of header, but required
128*c8dee2aaSAndroid Build Coastguard Worker uint32_t tag_count = 0;
129*c8dee2aaSAndroid Build Coastguard Worker };
130*c8dee2aaSAndroid Build Coastguard Worker
write_xyz_tag(float x,float y,float z)131*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_xyz_tag(float x, float y, float z) {
132*c8dee2aaSAndroid Build Coastguard Worker uint32_t data[] = {
133*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(kXYZ_PCSSpace),
134*c8dee2aaSAndroid Build Coastguard Worker 0,
135*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(float_round_to_fixed(x)),
136*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(float_round_to_fixed(y)),
137*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(float_round_to_fixed(z)),
138*c8dee2aaSAndroid Build Coastguard Worker };
139*c8dee2aaSAndroid Build Coastguard Worker return SkData::MakeWithCopy(data, sizeof(data));
140*c8dee2aaSAndroid Build Coastguard Worker }
141*c8dee2aaSAndroid Build Coastguard Worker
write_matrix(const skcms_Matrix3x4 * matrix)142*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_matrix(const skcms_Matrix3x4* matrix) {
143*c8dee2aaSAndroid Build Coastguard Worker uint32_t data[12];
144*c8dee2aaSAndroid Build Coastguard Worker // See layout details in section "10.12.5 Matrix".
145*c8dee2aaSAndroid Build Coastguard Worker size_t k = 0;
146*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < 3; ++i) {
147*c8dee2aaSAndroid Build Coastguard Worker for (int j = 0; j < 3; ++j) {
148*c8dee2aaSAndroid Build Coastguard Worker data[k++] = SkEndian_SwapBE32(float_round_to_fixed(matrix->vals[i][j]));
149*c8dee2aaSAndroid Build Coastguard Worker }
150*c8dee2aaSAndroid Build Coastguard Worker }
151*c8dee2aaSAndroid Build Coastguard Worker for (int i = 0; i < 3; ++i) {
152*c8dee2aaSAndroid Build Coastguard Worker data[k++] = SkEndian_SwapBE32(float_round_to_fixed(matrix->vals[i][3]));
153*c8dee2aaSAndroid Build Coastguard Worker }
154*c8dee2aaSAndroid Build Coastguard Worker return SkData::MakeWithCopy(data, sizeof(data));
155*c8dee2aaSAndroid Build Coastguard Worker }
156*c8dee2aaSAndroid Build Coastguard Worker
nearly_equal(float x,float y)157*c8dee2aaSAndroid Build Coastguard Worker bool nearly_equal(float x, float y) {
158*c8dee2aaSAndroid Build Coastguard Worker // A note on why I chose this tolerance: transfer_fn_almost_equal() uses a
159*c8dee2aaSAndroid Build Coastguard Worker // tolerance of 0.001f, which doesn't seem to be enough to distinguish
160*c8dee2aaSAndroid Build Coastguard Worker // between similar transfer functions, for example: gamma2.2 and sRGB.
161*c8dee2aaSAndroid Build Coastguard Worker //
162*c8dee2aaSAndroid Build Coastguard Worker // If the tolerance is 0.0f, then this we can't distinguish between two
163*c8dee2aaSAndroid Build Coastguard Worker // different encodings of what is clearly the same colorspace. Some
164*c8dee2aaSAndroid Build Coastguard Worker // experimentation with example files lead to this number:
165*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kTolerance = 1.0f / (1 << 11);
166*c8dee2aaSAndroid Build Coastguard Worker return ::fabsf(x - y) <= kTolerance;
167*c8dee2aaSAndroid Build Coastguard Worker }
168*c8dee2aaSAndroid Build Coastguard Worker
nearly_equal(const skcms_TransferFunction & u,const skcms_TransferFunction & v)169*c8dee2aaSAndroid Build Coastguard Worker bool nearly_equal(const skcms_TransferFunction& u,
170*c8dee2aaSAndroid Build Coastguard Worker const skcms_TransferFunction& v) {
171*c8dee2aaSAndroid Build Coastguard Worker return nearly_equal(u.g, v.g)
172*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.a, v.a)
173*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.b, v.b)
174*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.c, v.c)
175*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.d, v.d)
176*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.e, v.e)
177*c8dee2aaSAndroid Build Coastguard Worker && nearly_equal(u.f, v.f);
178*c8dee2aaSAndroid Build Coastguard Worker }
179*c8dee2aaSAndroid Build Coastguard Worker
nearly_equal(const skcms_Matrix3x3 & u,const skcms_Matrix3x3 & v)180*c8dee2aaSAndroid Build Coastguard Worker bool nearly_equal(const skcms_Matrix3x3& u, const skcms_Matrix3x3& v) {
181*c8dee2aaSAndroid Build Coastguard Worker for (int r = 0; r < 3; r++) {
182*c8dee2aaSAndroid Build Coastguard Worker for (int c = 0; c < 3; c++) {
183*c8dee2aaSAndroid Build Coastguard Worker if (!nearly_equal(u.vals[r][c], v.vals[r][c])) {
184*c8dee2aaSAndroid Build Coastguard Worker return false;
185*c8dee2aaSAndroid Build Coastguard Worker }
186*c8dee2aaSAndroid Build Coastguard Worker }
187*c8dee2aaSAndroid Build Coastguard Worker }
188*c8dee2aaSAndroid Build Coastguard Worker return true;
189*c8dee2aaSAndroid Build Coastguard Worker }
190*c8dee2aaSAndroid Build Coastguard Worker
191*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPPrimariesSRGB = 1;
192*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPPrimariesP3 = 12;
193*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPPrimariesRec2020 = 9;
194*c8dee2aaSAndroid Build Coastguard Worker
get_cicp_primaries(const skcms_Matrix3x3 & toXYZD50)195*c8dee2aaSAndroid Build Coastguard Worker uint32_t get_cicp_primaries(const skcms_Matrix3x3& toXYZD50) {
196*c8dee2aaSAndroid Build Coastguard Worker if (nearly_equal(toXYZD50, SkNamedGamut::kSRGB)) {
197*c8dee2aaSAndroid Build Coastguard Worker return kCICPPrimariesSRGB;
198*c8dee2aaSAndroid Build Coastguard Worker } else if (nearly_equal(toXYZD50, SkNamedGamut::kDisplayP3)) {
199*c8dee2aaSAndroid Build Coastguard Worker return kCICPPrimariesP3;
200*c8dee2aaSAndroid Build Coastguard Worker } else if (nearly_equal(toXYZD50, SkNamedGamut::kRec2020)) {
201*c8dee2aaSAndroid Build Coastguard Worker return kCICPPrimariesRec2020;
202*c8dee2aaSAndroid Build Coastguard Worker }
203*c8dee2aaSAndroid Build Coastguard Worker return 0;
204*c8dee2aaSAndroid Build Coastguard Worker }
205*c8dee2aaSAndroid Build Coastguard Worker
206*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPTrfnSRGB = 1;
207*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPTrfn2Dot2 = 4;
208*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPTrfnLinear = 8;
209*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPTrfnPQ = 16;
210*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kCICPTrfnHLG = 18;
211*c8dee2aaSAndroid Build Coastguard Worker
get_cicp_trfn(const skcms_TransferFunction & fn)212*c8dee2aaSAndroid Build Coastguard Worker uint32_t get_cicp_trfn(const skcms_TransferFunction& fn) {
213*c8dee2aaSAndroid Build Coastguard Worker switch (skcms_TransferFunction_getType(&fn)) {
214*c8dee2aaSAndroid Build Coastguard Worker case skcms_TFType_Invalid:
215*c8dee2aaSAndroid Build Coastguard Worker return 0;
216*c8dee2aaSAndroid Build Coastguard Worker case skcms_TFType_sRGBish:
217*c8dee2aaSAndroid Build Coastguard Worker if (nearly_equal(fn, SkNamedTransferFn::kSRGB)) {
218*c8dee2aaSAndroid Build Coastguard Worker return kCICPTrfnSRGB;
219*c8dee2aaSAndroid Build Coastguard Worker } else if (nearly_equal(fn, SkNamedTransferFn::k2Dot2)) {
220*c8dee2aaSAndroid Build Coastguard Worker return kCICPTrfn2Dot2;
221*c8dee2aaSAndroid Build Coastguard Worker } else if (nearly_equal(fn, SkNamedTransferFn::kLinear)) {
222*c8dee2aaSAndroid Build Coastguard Worker return kCICPTrfnLinear;
223*c8dee2aaSAndroid Build Coastguard Worker }
224*c8dee2aaSAndroid Build Coastguard Worker break;
225*c8dee2aaSAndroid Build Coastguard Worker case skcms_TFType_PQish:
226*c8dee2aaSAndroid Build Coastguard Worker // All PQ transfer functions are mapped to the single PQ value,
227*c8dee2aaSAndroid Build Coastguard Worker // ignoring their SDR white level.
228*c8dee2aaSAndroid Build Coastguard Worker return kCICPTrfnPQ;
229*c8dee2aaSAndroid Build Coastguard Worker case skcms_TFType_HLGish:
230*c8dee2aaSAndroid Build Coastguard Worker // All HLG transfer functions are mapped to the single HLG value.
231*c8dee2aaSAndroid Build Coastguard Worker return kCICPTrfnHLG;
232*c8dee2aaSAndroid Build Coastguard Worker case skcms_TFType_HLGinvish:
233*c8dee2aaSAndroid Build Coastguard Worker return 0;
234*c8dee2aaSAndroid Build Coastguard Worker }
235*c8dee2aaSAndroid Build Coastguard Worker return 0;
236*c8dee2aaSAndroid Build Coastguard Worker }
237*c8dee2aaSAndroid Build Coastguard Worker
get_desc_string(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)238*c8dee2aaSAndroid Build Coastguard Worker std::string get_desc_string(const skcms_TransferFunction& fn,
239*c8dee2aaSAndroid Build Coastguard Worker const skcms_Matrix3x3& toXYZD50) {
240*c8dee2aaSAndroid Build Coastguard Worker const uint32_t cicp_trfn = get_cicp_trfn(fn);
241*c8dee2aaSAndroid Build Coastguard Worker const uint32_t cicp_primaries = get_cicp_primaries(toXYZD50);
242*c8dee2aaSAndroid Build Coastguard Worker
243*c8dee2aaSAndroid Build Coastguard Worker // Use a unique string for sRGB.
244*c8dee2aaSAndroid Build Coastguard Worker if (cicp_trfn == kCICPPrimariesSRGB && cicp_primaries == kCICPTrfnSRGB) {
245*c8dee2aaSAndroid Build Coastguard Worker return "sRGB";
246*c8dee2aaSAndroid Build Coastguard Worker }
247*c8dee2aaSAndroid Build Coastguard Worker
248*c8dee2aaSAndroid Build Coastguard Worker // If available, use the named CICP primaries and transfer function.
249*c8dee2aaSAndroid Build Coastguard Worker if (cicp_primaries && cicp_trfn) {
250*c8dee2aaSAndroid Build Coastguard Worker std::string result;
251*c8dee2aaSAndroid Build Coastguard Worker switch (cicp_primaries) {
252*c8dee2aaSAndroid Build Coastguard Worker case kCICPPrimariesSRGB:
253*c8dee2aaSAndroid Build Coastguard Worker result += "sRGB";
254*c8dee2aaSAndroid Build Coastguard Worker break;
255*c8dee2aaSAndroid Build Coastguard Worker case kCICPPrimariesP3:
256*c8dee2aaSAndroid Build Coastguard Worker result += "Display P3";
257*c8dee2aaSAndroid Build Coastguard Worker break;
258*c8dee2aaSAndroid Build Coastguard Worker case kCICPPrimariesRec2020:
259*c8dee2aaSAndroid Build Coastguard Worker result += "Rec2020";
260*c8dee2aaSAndroid Build Coastguard Worker break;
261*c8dee2aaSAndroid Build Coastguard Worker default:
262*c8dee2aaSAndroid Build Coastguard Worker result += "Unknown";
263*c8dee2aaSAndroid Build Coastguard Worker break;
264*c8dee2aaSAndroid Build Coastguard Worker }
265*c8dee2aaSAndroid Build Coastguard Worker result += " Gamut with ";
266*c8dee2aaSAndroid Build Coastguard Worker switch (cicp_trfn) {
267*c8dee2aaSAndroid Build Coastguard Worker case kCICPTrfnSRGB:
268*c8dee2aaSAndroid Build Coastguard Worker result += "sRGB";
269*c8dee2aaSAndroid Build Coastguard Worker break;
270*c8dee2aaSAndroid Build Coastguard Worker case kCICPTrfnLinear:
271*c8dee2aaSAndroid Build Coastguard Worker result += "Linear";
272*c8dee2aaSAndroid Build Coastguard Worker break;
273*c8dee2aaSAndroid Build Coastguard Worker case kCICPTrfn2Dot2:
274*c8dee2aaSAndroid Build Coastguard Worker result += "2.2";
275*c8dee2aaSAndroid Build Coastguard Worker break;
276*c8dee2aaSAndroid Build Coastguard Worker case kCICPTrfnPQ:
277*c8dee2aaSAndroid Build Coastguard Worker result += "PQ";
278*c8dee2aaSAndroid Build Coastguard Worker break;
279*c8dee2aaSAndroid Build Coastguard Worker case kCICPTrfnHLG:
280*c8dee2aaSAndroid Build Coastguard Worker result += "HLG";
281*c8dee2aaSAndroid Build Coastguard Worker break;
282*c8dee2aaSAndroid Build Coastguard Worker default:
283*c8dee2aaSAndroid Build Coastguard Worker result += "Unknown";
284*c8dee2aaSAndroid Build Coastguard Worker break;
285*c8dee2aaSAndroid Build Coastguard Worker }
286*c8dee2aaSAndroid Build Coastguard Worker result += " Transfer";
287*c8dee2aaSAndroid Build Coastguard Worker return result;
288*c8dee2aaSAndroid Build Coastguard Worker }
289*c8dee2aaSAndroid Build Coastguard Worker
290*c8dee2aaSAndroid Build Coastguard Worker // Fall back to a prefix plus md5 hash.
291*c8dee2aaSAndroid Build Coastguard Worker SkMD5 md5;
292*c8dee2aaSAndroid Build Coastguard Worker md5.write(&toXYZD50, sizeof(toXYZD50));
293*c8dee2aaSAndroid Build Coastguard Worker md5.write(&fn, sizeof(fn));
294*c8dee2aaSAndroid Build Coastguard Worker SkMD5::Digest digest = md5.finish();
295*c8dee2aaSAndroid Build Coastguard Worker return std::string("Google/Skia/") + digest.toHexString().c_str();
296*c8dee2aaSAndroid Build Coastguard Worker }
297*c8dee2aaSAndroid Build Coastguard Worker
write_text_tag(const char * text)298*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_text_tag(const char* text) {
299*c8dee2aaSAndroid Build Coastguard Worker uint32_t text_length = strlen(text);
300*c8dee2aaSAndroid Build Coastguard Worker uint32_t header[] = {
301*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(kTAG_TextType), // Type signature
302*c8dee2aaSAndroid Build Coastguard Worker 0, // Reserved
303*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(1), // Number of records
304*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(12), // Record size (must be 12)
305*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(SkSetFourByteTag('e', 'n', 'U', 'S')), // English USA
306*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(2 * text_length), // Length of string in bytes
307*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(28), // Offset of string
308*c8dee2aaSAndroid Build Coastguard Worker };
309*c8dee2aaSAndroid Build Coastguard Worker SkDynamicMemoryWStream s;
310*c8dee2aaSAndroid Build Coastguard Worker s.write(header, sizeof(header));
311*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < text_length; i++) {
312*c8dee2aaSAndroid Build Coastguard Worker // Convert ASCII to big-endian UTF-16.
313*c8dee2aaSAndroid Build Coastguard Worker s.write8(0);
314*c8dee2aaSAndroid Build Coastguard Worker s.write8(text[i]);
315*c8dee2aaSAndroid Build Coastguard Worker }
316*c8dee2aaSAndroid Build Coastguard Worker s.padToAlign4();
317*c8dee2aaSAndroid Build Coastguard Worker return s.detachAsData();
318*c8dee2aaSAndroid Build Coastguard Worker }
319*c8dee2aaSAndroid Build Coastguard Worker
320*c8dee2aaSAndroid Build Coastguard Worker // Write a CICP tag.
write_cicp_tag(const skcms_CICP & cicp)321*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_cicp_tag(const skcms_CICP& cicp) {
322*c8dee2aaSAndroid Build Coastguard Worker SkDynamicMemoryWStream s;
323*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, kTAG_cicp); // Type signature
324*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, 0); // Reserved
325*c8dee2aaSAndroid Build Coastguard Worker s.write8(cicp.color_primaries); // Color primaries
326*c8dee2aaSAndroid Build Coastguard Worker s.write8(cicp.transfer_characteristics); // Transfer characteristics
327*c8dee2aaSAndroid Build Coastguard Worker s.write8(cicp.matrix_coefficients); // RGB matrix
328*c8dee2aaSAndroid Build Coastguard Worker s.write8(cicp.video_full_range_flag); // Full range
329*c8dee2aaSAndroid Build Coastguard Worker return s.detachAsData();
330*c8dee2aaSAndroid Build Coastguard Worker }
331*c8dee2aaSAndroid Build Coastguard Worker
332*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapInputMax = 1000.f / 203.f;
333*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapOutputMax = 1.f;
334*c8dee2aaSAndroid Build Coastguard Worker
335*c8dee2aaSAndroid Build Coastguard Worker // Scalar tone map gain function.
tone_map_gain(float x)336*c8dee2aaSAndroid Build Coastguard Worker float tone_map_gain(float x) {
337*c8dee2aaSAndroid Build Coastguard Worker // The PQ transfer function will map to the range [0, 1]. Linearly scale
338*c8dee2aaSAndroid Build Coastguard Worker // it up to the range [0, 1,000/203]. We will then tone map that back
339*c8dee2aaSAndroid Build Coastguard Worker // down to [0, 1].
340*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapA = kToneMapOutputMax / (kToneMapInputMax * kToneMapInputMax);
341*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapB = 1.f / kToneMapOutputMax;
342*c8dee2aaSAndroid Build Coastguard Worker return (1.f + kToneMapA * x) / (1.f + kToneMapB * x);
343*c8dee2aaSAndroid Build Coastguard Worker }
344*c8dee2aaSAndroid Build Coastguard Worker
345*c8dee2aaSAndroid Build Coastguard Worker // Scalar tone map inverse function
tone_map_inverse(float y)346*c8dee2aaSAndroid Build Coastguard Worker float tone_map_inverse(float y) {
347*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapA = kToneMapOutputMax / (kToneMapInputMax * kToneMapInputMax);
348*c8dee2aaSAndroid Build Coastguard Worker constexpr float kToneMapB = 1.f / kToneMapOutputMax;
349*c8dee2aaSAndroid Build Coastguard Worker
350*c8dee2aaSAndroid Build Coastguard Worker // This is a quadratic equation of the form a*x*x + b*x + c = 0
351*c8dee2aaSAndroid Build Coastguard Worker const float a = kToneMapA;
352*c8dee2aaSAndroid Build Coastguard Worker const float b = (1 - kToneMapB * y);
353*c8dee2aaSAndroid Build Coastguard Worker const float c = -y;
354*c8dee2aaSAndroid Build Coastguard Worker const float discriminant = b * b - 4.f * a * c;
355*c8dee2aaSAndroid Build Coastguard Worker if (discriminant < 0.f) {
356*c8dee2aaSAndroid Build Coastguard Worker return 0.f;
357*c8dee2aaSAndroid Build Coastguard Worker }
358*c8dee2aaSAndroid Build Coastguard Worker return (-b + sqrtf(discriminant)) / (2.f * a);
359*c8dee2aaSAndroid Build Coastguard Worker }
360*c8dee2aaSAndroid Build Coastguard Worker
361*c8dee2aaSAndroid Build Coastguard Worker // Evaluate PQ and HLG transfer functions without tonemapping. The maximum returned value is
362*c8dee2aaSAndroid Build Coastguard Worker // kToneMapInputMax.
hdr_trfn_eval(const skcms_TransferFunction & fn,float x)363*c8dee2aaSAndroid Build Coastguard Worker float hdr_trfn_eval(const skcms_TransferFunction& fn, float x) {
364*c8dee2aaSAndroid Build Coastguard Worker if (skcms_TransferFunction_isHLGish(&fn)) {
365*c8dee2aaSAndroid Build Coastguard Worker // For HLG this curve is the inverse OETF and then a per-channel OOTF.
366*c8dee2aaSAndroid Build Coastguard Worker x = skcms_TransferFunction_eval(&SkNamedTransferFn::kHLG, x) / 12.f;
367*c8dee2aaSAndroid Build Coastguard Worker x *= std::pow(x, 0.2);
368*c8dee2aaSAndroid Build Coastguard Worker } else if (skcms_TransferFunction_isPQish(&fn)) {
369*c8dee2aaSAndroid Build Coastguard Worker // For PQ this is the EOTF, scaled so that 1,000 nits maps to 1.0.
370*c8dee2aaSAndroid Build Coastguard Worker x = 10.f * skcms_TransferFunction_eval(&SkNamedTransferFn::kPQ, x);
371*c8dee2aaSAndroid Build Coastguard Worker x = std::min(x, 1.f);
372*c8dee2aaSAndroid Build Coastguard Worker }
373*c8dee2aaSAndroid Build Coastguard Worker
374*c8dee2aaSAndroid Build Coastguard Worker // Scale x so that 203 nits maps to 1.0.
375*c8dee2aaSAndroid Build Coastguard Worker x *= kToneMapInputMax;
376*c8dee2aaSAndroid Build Coastguard Worker return x;
377*c8dee2aaSAndroid Build Coastguard Worker }
378*c8dee2aaSAndroid Build Coastguard Worker
379*c8dee2aaSAndroid Build Coastguard Worker // Write a lookup table based 1D curve.
write_trc_tag(const skcms_Curve & trc)380*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_trc_tag(const skcms_Curve& trc) {
381*c8dee2aaSAndroid Build Coastguard Worker SkDynamicMemoryWStream s;
382*c8dee2aaSAndroid Build Coastguard Worker if (trc.table_entries) {
383*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, kTAG_CurveType); // Type
384*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, 0); // Reserved
385*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, trc.table_entries); // Value count
386*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t i = 0; i < trc.table_entries; ++i) {
387*c8dee2aaSAndroid Build Coastguard Worker uint16_t value = reinterpret_cast<const uint16_t*>(trc.table_16)[i];
388*c8dee2aaSAndroid Build Coastguard Worker s.write16(value);
389*c8dee2aaSAndroid Build Coastguard Worker }
390*c8dee2aaSAndroid Build Coastguard Worker } else {
391*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, kTAG_ParaCurveType); // Type
392*c8dee2aaSAndroid Build Coastguard Worker s.write32(0); // Reserved
393*c8dee2aaSAndroid Build Coastguard Worker const auto& fn = trc.parametric;
394*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(skcms_TransferFunction_isSRGBish(&fn));
395*c8dee2aaSAndroid Build Coastguard Worker if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f && fn.d == 0.f && fn.e == 0.f &&
396*c8dee2aaSAndroid Build Coastguard Worker fn.f == 0.f) {
397*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU16BE(&s, kExponential_ParaCurveType);
398*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU16BE(&s, 0);
399*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.g));
400*c8dee2aaSAndroid Build Coastguard Worker } else {
401*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU16BE(&s, kGABCDEF_ParaCurveType);
402*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU16BE(&s, 0);
403*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.g));
404*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.a));
405*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.b));
406*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.c));
407*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.d));
408*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.e));
409*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, float_round_to_fixed(fn.f));
410*c8dee2aaSAndroid Build Coastguard Worker }
411*c8dee2aaSAndroid Build Coastguard Worker }
412*c8dee2aaSAndroid Build Coastguard Worker s.padToAlign4();
413*c8dee2aaSAndroid Build Coastguard Worker return s.detachAsData();
414*c8dee2aaSAndroid Build Coastguard Worker }
415*c8dee2aaSAndroid Build Coastguard Worker
write_clut(const uint8_t * grid_points,const uint8_t * grid_16)416*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
417*c8dee2aaSAndroid Build Coastguard Worker SkDynamicMemoryWStream s;
418*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < 16; ++i) {
419*c8dee2aaSAndroid Build Coastguard Worker s.write8(i < kNumChannels ? grid_points[i] : 0); // Grid size
420*c8dee2aaSAndroid Build Coastguard Worker }
421*c8dee2aaSAndroid Build Coastguard Worker s.write8(2); // Grid byte width (always 16-bit)
422*c8dee2aaSAndroid Build Coastguard Worker s.write8(0); // Reserved
423*c8dee2aaSAndroid Build Coastguard Worker s.write8(0); // Reserved
424*c8dee2aaSAndroid Build Coastguard Worker s.write8(0); // Reserved
425*c8dee2aaSAndroid Build Coastguard Worker
426*c8dee2aaSAndroid Build Coastguard Worker uint32_t value_count = kNumChannels;
427*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t i = 0; i < kNumChannels; ++i) {
428*c8dee2aaSAndroid Build Coastguard Worker value_count *= grid_points[i];
429*c8dee2aaSAndroid Build Coastguard Worker }
430*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t i = 0; i < value_count; ++i) {
431*c8dee2aaSAndroid Build Coastguard Worker uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
432*c8dee2aaSAndroid Build Coastguard Worker s.write16(value);
433*c8dee2aaSAndroid Build Coastguard Worker }
434*c8dee2aaSAndroid Build Coastguard Worker s.padToAlign4();
435*c8dee2aaSAndroid Build Coastguard Worker return s.detachAsData();
436*c8dee2aaSAndroid Build Coastguard Worker }
437*c8dee2aaSAndroid Build Coastguard Worker
438*c8dee2aaSAndroid Build Coastguard Worker // Write an A2B or B2A tag.
write_mAB_or_mBA_tag(uint32_t type,const skcms_Curve * b_curves,const skcms_Curve * a_curves,const uint8_t * grid_points,const uint8_t * grid_16,const skcms_Curve * m_curves,const skcms_Matrix3x4 * matrix)439*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> write_mAB_or_mBA_tag(uint32_t type,
440*c8dee2aaSAndroid Build Coastguard Worker const skcms_Curve* b_curves,
441*c8dee2aaSAndroid Build Coastguard Worker const skcms_Curve* a_curves,
442*c8dee2aaSAndroid Build Coastguard Worker const uint8_t* grid_points,
443*c8dee2aaSAndroid Build Coastguard Worker const uint8_t* grid_16,
444*c8dee2aaSAndroid Build Coastguard Worker const skcms_Curve* m_curves,
445*c8dee2aaSAndroid Build Coastguard Worker const skcms_Matrix3x4* matrix) {
446*c8dee2aaSAndroid Build Coastguard Worker size_t offset = 32;
447*c8dee2aaSAndroid Build Coastguard Worker
448*c8dee2aaSAndroid Build Coastguard Worker // The "B" curve is required.
449*c8dee2aaSAndroid Build Coastguard Worker size_t b_curves_offset = offset;
450*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> b_curves_data[kNumChannels];
451*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(b_curves);
452*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
453*c8dee2aaSAndroid Build Coastguard Worker b_curves_data[i] = write_trc_tag(b_curves[i]);
454*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(b_curves_data[i]);
455*c8dee2aaSAndroid Build Coastguard Worker offset += b_curves_data[i]->size();
456*c8dee2aaSAndroid Build Coastguard Worker }
457*c8dee2aaSAndroid Build Coastguard Worker
458*c8dee2aaSAndroid Build Coastguard Worker // The CLUT.
459*c8dee2aaSAndroid Build Coastguard Worker size_t clut_offset = 0;
460*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> clut;
461*c8dee2aaSAndroid Build Coastguard Worker if (grid_points) {
462*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(grid_16);
463*c8dee2aaSAndroid Build Coastguard Worker clut_offset = offset;
464*c8dee2aaSAndroid Build Coastguard Worker clut = write_clut(grid_points, grid_16);
465*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(clut);
466*c8dee2aaSAndroid Build Coastguard Worker offset += clut->size();
467*c8dee2aaSAndroid Build Coastguard Worker }
468*c8dee2aaSAndroid Build Coastguard Worker
469*c8dee2aaSAndroid Build Coastguard Worker // The "A" curves.
470*c8dee2aaSAndroid Build Coastguard Worker size_t a_curves_offset = 0;
471*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> a_curves_data[kNumChannels];
472*c8dee2aaSAndroid Build Coastguard Worker if (a_curves) {
473*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(grid_points);
474*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(grid_16);
475*c8dee2aaSAndroid Build Coastguard Worker a_curves_offset = offset;
476*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
477*c8dee2aaSAndroid Build Coastguard Worker a_curves_data[i] = write_trc_tag(a_curves[i]);
478*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(a_curves_data[i]);
479*c8dee2aaSAndroid Build Coastguard Worker offset += a_curves_data[i]->size();
480*c8dee2aaSAndroid Build Coastguard Worker }
481*c8dee2aaSAndroid Build Coastguard Worker }
482*c8dee2aaSAndroid Build Coastguard Worker
483*c8dee2aaSAndroid Build Coastguard Worker // The matrix.
484*c8dee2aaSAndroid Build Coastguard Worker size_t matrix_offset = 0;
485*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> matrix_data;
486*c8dee2aaSAndroid Build Coastguard Worker if (matrix) {
487*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(m_curves);
488*c8dee2aaSAndroid Build Coastguard Worker matrix_offset = offset;
489*c8dee2aaSAndroid Build Coastguard Worker matrix_data = write_matrix(matrix);
490*c8dee2aaSAndroid Build Coastguard Worker offset += matrix_data->size();
491*c8dee2aaSAndroid Build Coastguard Worker }
492*c8dee2aaSAndroid Build Coastguard Worker
493*c8dee2aaSAndroid Build Coastguard Worker // The "M" curves.
494*c8dee2aaSAndroid Build Coastguard Worker size_t m_curves_offset = 0;
495*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> m_curves_data[kNumChannels];
496*c8dee2aaSAndroid Build Coastguard Worker if (m_curves) {
497*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(matrix);
498*c8dee2aaSAndroid Build Coastguard Worker m_curves_offset = offset;
499*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
500*c8dee2aaSAndroid Build Coastguard Worker m_curves_data[i] = write_trc_tag(m_curves[i]);
501*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(a_curves_data[i]);
502*c8dee2aaSAndroid Build Coastguard Worker offset += m_curves_data[i]->size();
503*c8dee2aaSAndroid Build Coastguard Worker }
504*c8dee2aaSAndroid Build Coastguard Worker }
505*c8dee2aaSAndroid Build Coastguard Worker
506*c8dee2aaSAndroid Build Coastguard Worker SkDynamicMemoryWStream s;
507*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, type); // Type signature
508*c8dee2aaSAndroid Build Coastguard Worker s.write32(0); // Reserved
509*c8dee2aaSAndroid Build Coastguard Worker s.write8(kNumChannels); // Input channels
510*c8dee2aaSAndroid Build Coastguard Worker s.write8(kNumChannels); // Output channels
511*c8dee2aaSAndroid Build Coastguard Worker s.write16(0); // Reserved
512*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, b_curves_offset); // B curve offset
513*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, matrix_offset); // Matrix offset
514*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, m_curves_offset); // M curve offset
515*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, clut_offset); // CLUT offset
516*c8dee2aaSAndroid Build Coastguard Worker SkWStreamWriteU32BE(&s, a_curves_offset); // A curve offset
517*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(s.bytesWritten() == b_curves_offset);
518*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
519*c8dee2aaSAndroid Build Coastguard Worker s.write(b_curves_data[i]->data(), b_curves_data[i]->size());
520*c8dee2aaSAndroid Build Coastguard Worker }
521*c8dee2aaSAndroid Build Coastguard Worker if (clut) {
522*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(s.bytesWritten() == clut_offset);
523*c8dee2aaSAndroid Build Coastguard Worker s.write(clut->data(), clut->size());
524*c8dee2aaSAndroid Build Coastguard Worker }
525*c8dee2aaSAndroid Build Coastguard Worker if (a_curves) {
526*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(s.bytesWritten() == a_curves_offset);
527*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
528*c8dee2aaSAndroid Build Coastguard Worker s.write(a_curves_data[i]->data(), a_curves_data[i]->size());
529*c8dee2aaSAndroid Build Coastguard Worker }
530*c8dee2aaSAndroid Build Coastguard Worker }
531*c8dee2aaSAndroid Build Coastguard Worker if (matrix_data) {
532*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(s.bytesWritten() == matrix_offset);
533*c8dee2aaSAndroid Build Coastguard Worker s.write(matrix_data->data(), matrix_data->size());
534*c8dee2aaSAndroid Build Coastguard Worker }
535*c8dee2aaSAndroid Build Coastguard Worker if (m_curves) {
536*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(s.bytesWritten() == m_curves_offset);
537*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
538*c8dee2aaSAndroid Build Coastguard Worker s.write(m_curves_data[i]->data(), m_curves_data[i]->size());
539*c8dee2aaSAndroid Build Coastguard Worker }
540*c8dee2aaSAndroid Build Coastguard Worker }
541*c8dee2aaSAndroid Build Coastguard Worker return s.detachAsData();
542*c8dee2aaSAndroid Build Coastguard Worker }
543*c8dee2aaSAndroid Build Coastguard Worker
544*c8dee2aaSAndroid Build Coastguard Worker } // namespace
545*c8dee2aaSAndroid Build Coastguard Worker
SkWriteICCProfile(const skcms_ICCProfile * profile,const char * desc)546*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> SkWriteICCProfile(const skcms_ICCProfile* profile, const char* desc) {
547*c8dee2aaSAndroid Build Coastguard Worker ICCHeader header;
548*c8dee2aaSAndroid Build Coastguard Worker
549*c8dee2aaSAndroid Build Coastguard Worker std::vector<std::pair<uint32_t, sk_sp<SkData>>> tags;
550*c8dee2aaSAndroid Build Coastguard Worker
551*c8dee2aaSAndroid Build Coastguard Worker // Compute primaries.
552*c8dee2aaSAndroid Build Coastguard Worker if (profile->has_toXYZD50) {
553*c8dee2aaSAndroid Build Coastguard Worker const auto& m = profile->toXYZD50;
554*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_rXYZ, write_xyz_tag(m.vals[0][0], m.vals[1][0], m.vals[2][0]));
555*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_gXYZ, write_xyz_tag(m.vals[0][1], m.vals[1][1], m.vals[2][1]));
556*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_bXYZ, write_xyz_tag(m.vals[0][2], m.vals[1][2], m.vals[2][2]));
557*c8dee2aaSAndroid Build Coastguard Worker }
558*c8dee2aaSAndroid Build Coastguard Worker
559*c8dee2aaSAndroid Build Coastguard Worker // Compute white point tag (must be D50)
560*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
561*c8dee2aaSAndroid Build Coastguard Worker
562*c8dee2aaSAndroid Build Coastguard Worker // Compute transfer curves.
563*c8dee2aaSAndroid Build Coastguard Worker if (profile->has_trc) {
564*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_rTRC, write_trc_tag(profile->trc[0]));
565*c8dee2aaSAndroid Build Coastguard Worker
566*c8dee2aaSAndroid Build Coastguard Worker // Use empty data to indicate that the entry should use the previous tag's
567*c8dee2aaSAndroid Build Coastguard Worker // data.
568*c8dee2aaSAndroid Build Coastguard Worker if (!memcmp(&profile->trc[1], &profile->trc[0], sizeof(profile->trc[0]))) {
569*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_gTRC, SkData::MakeEmpty());
570*c8dee2aaSAndroid Build Coastguard Worker } else {
571*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_gTRC, write_trc_tag(profile->trc[1]));
572*c8dee2aaSAndroid Build Coastguard Worker }
573*c8dee2aaSAndroid Build Coastguard Worker
574*c8dee2aaSAndroid Build Coastguard Worker if (!memcmp(&profile->trc[2], &profile->trc[1], sizeof(profile->trc[1]))) {
575*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_bTRC, SkData::MakeEmpty());
576*c8dee2aaSAndroid Build Coastguard Worker } else {
577*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_bTRC, write_trc_tag(profile->trc[2]));
578*c8dee2aaSAndroid Build Coastguard Worker }
579*c8dee2aaSAndroid Build Coastguard Worker }
580*c8dee2aaSAndroid Build Coastguard Worker
581*c8dee2aaSAndroid Build Coastguard Worker // Compute CICP.
582*c8dee2aaSAndroid Build Coastguard Worker if (profile->has_CICP) {
583*c8dee2aaSAndroid Build Coastguard Worker // The CICP tag is present in ICC 4.4, so update the header's version.
584*c8dee2aaSAndroid Build Coastguard Worker header.version = SkEndian_SwapBE32(0x04400000);
585*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_cicp, write_cicp_tag(profile->CICP));
586*c8dee2aaSAndroid Build Coastguard Worker }
587*c8dee2aaSAndroid Build Coastguard Worker
588*c8dee2aaSAndroid Build Coastguard Worker // Compute A2B0.
589*c8dee2aaSAndroid Build Coastguard Worker if (profile->has_A2B) {
590*c8dee2aaSAndroid Build Coastguard Worker const auto& a2b = profile->A2B;
591*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(a2b.output_channels == kNumChannels);
592*c8dee2aaSAndroid Build Coastguard Worker auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
593*c8dee2aaSAndroid Build Coastguard Worker a2b.output_curves,
594*c8dee2aaSAndroid Build Coastguard Worker a2b.input_channels ? a2b.input_curves : nullptr,
595*c8dee2aaSAndroid Build Coastguard Worker a2b.input_channels ? a2b.grid_points : nullptr,
596*c8dee2aaSAndroid Build Coastguard Worker a2b.input_channels ? a2b.grid_16 : nullptr,
597*c8dee2aaSAndroid Build Coastguard Worker a2b.matrix_channels ? a2b.matrix_curves : nullptr,
598*c8dee2aaSAndroid Build Coastguard Worker a2b.matrix_channels ? &a2b.matrix : nullptr);
599*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
600*c8dee2aaSAndroid Build Coastguard Worker }
601*c8dee2aaSAndroid Build Coastguard Worker
602*c8dee2aaSAndroid Build Coastguard Worker // Compute B2A0.
603*c8dee2aaSAndroid Build Coastguard Worker if (profile->has_B2A) {
604*c8dee2aaSAndroid Build Coastguard Worker const auto& b2a = profile->B2A;
605*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(b2a.input_channels == kNumChannels);
606*c8dee2aaSAndroid Build Coastguard Worker auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
607*c8dee2aaSAndroid Build Coastguard Worker b2a.input_curves,
608*c8dee2aaSAndroid Build Coastguard Worker b2a.output_channels ? b2a.input_curves : nullptr,
609*c8dee2aaSAndroid Build Coastguard Worker b2a.output_channels ? b2a.grid_points : nullptr,
610*c8dee2aaSAndroid Build Coastguard Worker b2a.output_channels ? b2a.grid_16 : nullptr,
611*c8dee2aaSAndroid Build Coastguard Worker b2a.matrix_channels ? b2a.matrix_curves : nullptr,
612*c8dee2aaSAndroid Build Coastguard Worker b2a.matrix_channels ? &b2a.matrix : nullptr);
613*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
614*c8dee2aaSAndroid Build Coastguard Worker }
615*c8dee2aaSAndroid Build Coastguard Worker
616*c8dee2aaSAndroid Build Coastguard Worker // Compute copyright tag
617*c8dee2aaSAndroid Build Coastguard Worker tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2016"));
618*c8dee2aaSAndroid Build Coastguard Worker
619*c8dee2aaSAndroid Build Coastguard Worker // Ensure that the desc isn't empty https://crbug.com/329032158
620*c8dee2aaSAndroid Build Coastguard Worker std::string generatedDesc;
621*c8dee2aaSAndroid Build Coastguard Worker if (!desc || *desc == '\0') {
622*c8dee2aaSAndroid Build Coastguard Worker SkMD5 md5;
623*c8dee2aaSAndroid Build Coastguard Worker for (const auto& tag : tags) {
624*c8dee2aaSAndroid Build Coastguard Worker md5.write(&tag.first, sizeof(tag.first));
625*c8dee2aaSAndroid Build Coastguard Worker md5.write(tag.second->bytes(), tag.second->size());
626*c8dee2aaSAndroid Build Coastguard Worker }
627*c8dee2aaSAndroid Build Coastguard Worker SkMD5::Digest digest = md5.finish();
628*c8dee2aaSAndroid Build Coastguard Worker generatedDesc = std::string("Google/Skia/") + digest.toHexString().c_str();
629*c8dee2aaSAndroid Build Coastguard Worker desc = generatedDesc.c_str();
630*c8dee2aaSAndroid Build Coastguard Worker }
631*c8dee2aaSAndroid Build Coastguard Worker // Compute profile description tag
632*c8dee2aaSAndroid Build Coastguard Worker tags.emplace(tags.begin(), kTAG_desc, write_text_tag(desc));
633*c8dee2aaSAndroid Build Coastguard Worker
634*c8dee2aaSAndroid Build Coastguard Worker // Compute the size of the profile.
635*c8dee2aaSAndroid Build Coastguard Worker size_t tag_data_size = 0;
636*c8dee2aaSAndroid Build Coastguard Worker for (const auto& tag : tags) {
637*c8dee2aaSAndroid Build Coastguard Worker tag_data_size += tag.second->size();
638*c8dee2aaSAndroid Build Coastguard Worker }
639*c8dee2aaSAndroid Build Coastguard Worker size_t tag_table_size = kICCTagTableEntrySize * tags.size();
640*c8dee2aaSAndroid Build Coastguard Worker size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
641*c8dee2aaSAndroid Build Coastguard Worker
642*c8dee2aaSAndroid Build Coastguard Worker // Write the header.
643*c8dee2aaSAndroid Build Coastguard Worker header.data_color_space = SkEndian_SwapBE32(profile->data_color_space);
644*c8dee2aaSAndroid Build Coastguard Worker header.pcs = SkEndian_SwapBE32(profile->pcs);
645*c8dee2aaSAndroid Build Coastguard Worker header.size = SkEndian_SwapBE32(profile_size);
646*c8dee2aaSAndroid Build Coastguard Worker header.tag_count = SkEndian_SwapBE32(tags.size());
647*c8dee2aaSAndroid Build Coastguard Worker
648*c8dee2aaSAndroid Build Coastguard Worker SkAutoMalloc profile_data(profile_size);
649*c8dee2aaSAndroid Build Coastguard Worker uint8_t* ptr = (uint8_t*)profile_data.get();
650*c8dee2aaSAndroid Build Coastguard Worker memcpy(ptr, &header, sizeof(header));
651*c8dee2aaSAndroid Build Coastguard Worker ptr += sizeof(header);
652*c8dee2aaSAndroid Build Coastguard Worker
653*c8dee2aaSAndroid Build Coastguard Worker // Write the tag table. Track the offset and size of the previous tag to
654*c8dee2aaSAndroid Build Coastguard Worker // compute each tag's offset. An empty SkData indicates that the previous
655*c8dee2aaSAndroid Build Coastguard Worker // tag is to be reused.
656*c8dee2aaSAndroid Build Coastguard Worker size_t last_tag_offset = sizeof(header) + tag_table_size;
657*c8dee2aaSAndroid Build Coastguard Worker size_t last_tag_size = 0;
658*c8dee2aaSAndroid Build Coastguard Worker for (const auto& tag : tags) {
659*c8dee2aaSAndroid Build Coastguard Worker if (!tag.second->isEmpty()) {
660*c8dee2aaSAndroid Build Coastguard Worker last_tag_offset = last_tag_offset + last_tag_size;
661*c8dee2aaSAndroid Build Coastguard Worker last_tag_size = tag.second->size();
662*c8dee2aaSAndroid Build Coastguard Worker }
663*c8dee2aaSAndroid Build Coastguard Worker uint32_t tag_table_entry[3] = {
664*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(tag.first),
665*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(last_tag_offset),
666*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE32(last_tag_size),
667*c8dee2aaSAndroid Build Coastguard Worker };
668*c8dee2aaSAndroid Build Coastguard Worker memcpy(ptr, tag_table_entry, sizeof(tag_table_entry));
669*c8dee2aaSAndroid Build Coastguard Worker ptr += sizeof(tag_table_entry);
670*c8dee2aaSAndroid Build Coastguard Worker }
671*c8dee2aaSAndroid Build Coastguard Worker
672*c8dee2aaSAndroid Build Coastguard Worker // Write the tags.
673*c8dee2aaSAndroid Build Coastguard Worker for (const auto& tag : tags) {
674*c8dee2aaSAndroid Build Coastguard Worker if (tag.second->isEmpty()) continue;
675*c8dee2aaSAndroid Build Coastguard Worker memcpy(ptr, tag.second->data(), tag.second->size());
676*c8dee2aaSAndroid Build Coastguard Worker ptr += tag.second->size();
677*c8dee2aaSAndroid Build Coastguard Worker }
678*c8dee2aaSAndroid Build Coastguard Worker
679*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(profile_size == static_cast<size_t>(ptr - (uint8_t*)profile_data.get()));
680*c8dee2aaSAndroid Build Coastguard Worker return SkData::MakeFromMalloc(profile_data.release(), profile_size);
681*c8dee2aaSAndroid Build Coastguard Worker }
682*c8dee2aaSAndroid Build Coastguard Worker
SkWriteICCProfile(const skcms_TransferFunction & fn,const skcms_Matrix3x3 & toXYZD50)683*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkData> SkWriteICCProfile(const skcms_TransferFunction& fn, const skcms_Matrix3x3& toXYZD50) {
684*c8dee2aaSAndroid Build Coastguard Worker skcms_ICCProfile profile;
685*c8dee2aaSAndroid Build Coastguard Worker memset(&profile, 0, sizeof(profile));
686*c8dee2aaSAndroid Build Coastguard Worker std::vector<uint16_t> trc_table;
687*c8dee2aaSAndroid Build Coastguard Worker std::vector<uint16_t> a2b_grid;
688*c8dee2aaSAndroid Build Coastguard Worker
689*c8dee2aaSAndroid Build Coastguard Worker profile.data_color_space = skcms_Signature_RGB;
690*c8dee2aaSAndroid Build Coastguard Worker profile.pcs = skcms_Signature_XYZ;
691*c8dee2aaSAndroid Build Coastguard Worker
692*c8dee2aaSAndroid Build Coastguard Worker // Populate toXYZD50.
693*c8dee2aaSAndroid Build Coastguard Worker {
694*c8dee2aaSAndroid Build Coastguard Worker profile.has_toXYZD50 = true;
695*c8dee2aaSAndroid Build Coastguard Worker profile.toXYZD50 = toXYZD50;
696*c8dee2aaSAndroid Build Coastguard Worker }
697*c8dee2aaSAndroid Build Coastguard Worker
698*c8dee2aaSAndroid Build Coastguard Worker // Populate the analytic TRC for sRGB-like curves.
699*c8dee2aaSAndroid Build Coastguard Worker if (skcms_TransferFunction_isSRGBish(&fn)) {
700*c8dee2aaSAndroid Build Coastguard Worker profile.has_trc = true;
701*c8dee2aaSAndroid Build Coastguard Worker profile.trc[0].table_entries = 0;
702*c8dee2aaSAndroid Build Coastguard Worker profile.trc[0].parametric = fn;
703*c8dee2aaSAndroid Build Coastguard Worker memcpy(&profile.trc[1], &profile.trc[0], sizeof(profile.trc[0]));
704*c8dee2aaSAndroid Build Coastguard Worker memcpy(&profile.trc[2], &profile.trc[0], sizeof(profile.trc[0]));
705*c8dee2aaSAndroid Build Coastguard Worker }
706*c8dee2aaSAndroid Build Coastguard Worker
707*c8dee2aaSAndroid Build Coastguard Worker // Populate A2B (PQ and HLG only).
708*c8dee2aaSAndroid Build Coastguard Worker if (skcms_TransferFunction_isPQish(&fn) || skcms_TransferFunction_isHLGish(&fn)) {
709*c8dee2aaSAndroid Build Coastguard Worker // Populate a 1D curve to perform per-channel conversion to linear and tone mapping.
710*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kTrcTableSize = 65;
711*c8dee2aaSAndroid Build Coastguard Worker trc_table.resize(kTrcTableSize);
712*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t i = 0; i < kTrcTableSize; ++i) {
713*c8dee2aaSAndroid Build Coastguard Worker float x = i / (kTrcTableSize - 1.f);
714*c8dee2aaSAndroid Build Coastguard Worker x = hdr_trfn_eval(fn, x);
715*c8dee2aaSAndroid Build Coastguard Worker x *= tone_map_gain(x);
716*c8dee2aaSAndroid Build Coastguard Worker trc_table[i] = SkEndian_SwapBE16(float_to_uInt16Number(x, kOne16CurveType));
717*c8dee2aaSAndroid Build Coastguard Worker }
718*c8dee2aaSAndroid Build Coastguard Worker
719*c8dee2aaSAndroid Build Coastguard Worker // Populate the grid with a 3D LUT to do cross-channel tone mapping.
720*c8dee2aaSAndroid Build Coastguard Worker constexpr uint32_t kGridSize = 11;
721*c8dee2aaSAndroid Build Coastguard Worker a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels);
722*c8dee2aaSAndroid Build Coastguard Worker size_t a2b_grid_index = 0;
723*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
724*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
725*c8dee2aaSAndroid Build Coastguard Worker for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
726*c8dee2aaSAndroid Build Coastguard Worker float rgb[3] = {
727*c8dee2aaSAndroid Build Coastguard Worker r_index / (kGridSize - 1.f),
728*c8dee2aaSAndroid Build Coastguard Worker g_index / (kGridSize - 1.f),
729*c8dee2aaSAndroid Build Coastguard Worker b_index / (kGridSize - 1.f),
730*c8dee2aaSAndroid Build Coastguard Worker };
731*c8dee2aaSAndroid Build Coastguard Worker
732*c8dee2aaSAndroid Build Coastguard Worker // Un-apply the per-channel tone mapping.
733*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
734*c8dee2aaSAndroid Build Coastguard Worker c = tone_map_inverse(c);
735*c8dee2aaSAndroid Build Coastguard Worker }
736*c8dee2aaSAndroid Build Coastguard Worker
737*c8dee2aaSAndroid Build Coastguard Worker // For HLG, mix the channels according to the OOTF.
738*c8dee2aaSAndroid Build Coastguard Worker if (skcms_TransferFunction_isHLGish(&fn)) {
739*c8dee2aaSAndroid Build Coastguard Worker // Scale to [0, 1].
740*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
741*c8dee2aaSAndroid Build Coastguard Worker c /= kToneMapInputMax;
742*c8dee2aaSAndroid Build Coastguard Worker }
743*c8dee2aaSAndroid Build Coastguard Worker
744*c8dee2aaSAndroid Build Coastguard Worker // Un-apply the per-channel OOTF.
745*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
746*c8dee2aaSAndroid Build Coastguard Worker c = std::pow(c, 1 / 1.2);
747*c8dee2aaSAndroid Build Coastguard Worker }
748*c8dee2aaSAndroid Build Coastguard Worker
749*c8dee2aaSAndroid Build Coastguard Worker // Re-apply the cross-channel OOTF.
750*c8dee2aaSAndroid Build Coastguard Worker float Y = 0.2627f * rgb[0] + 0.6780f * rgb[1] + 0.0593f * rgb[2];
751*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
752*c8dee2aaSAndroid Build Coastguard Worker c *= std::pow(Y, 0.2);
753*c8dee2aaSAndroid Build Coastguard Worker }
754*c8dee2aaSAndroid Build Coastguard Worker
755*c8dee2aaSAndroid Build Coastguard Worker // Scale back up to 1.0 being 1,000/203.
756*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
757*c8dee2aaSAndroid Build Coastguard Worker c *= kToneMapInputMax;
758*c8dee2aaSAndroid Build Coastguard Worker }
759*c8dee2aaSAndroid Build Coastguard Worker }
760*c8dee2aaSAndroid Build Coastguard Worker
761*c8dee2aaSAndroid Build Coastguard Worker // Apply tone mapping to take 1,000/203 to 1.0.
762*c8dee2aaSAndroid Build Coastguard Worker {
763*c8dee2aaSAndroid Build Coastguard Worker float max_rgb = std::max(std::max(rgb[0], rgb[1]), rgb[2]);
764*c8dee2aaSAndroid Build Coastguard Worker for (auto& c : rgb) {
765*c8dee2aaSAndroid Build Coastguard Worker c *= tone_map_gain(0.5 * (c + max_rgb));
766*c8dee2aaSAndroid Build Coastguard Worker c = std::min(c, 1.f);
767*c8dee2aaSAndroid Build Coastguard Worker }
768*c8dee2aaSAndroid Build Coastguard Worker }
769*c8dee2aaSAndroid Build Coastguard Worker
770*c8dee2aaSAndroid Build Coastguard Worker // Write the result to the LUT.
771*c8dee2aaSAndroid Build Coastguard Worker for (const auto& c : rgb) {
772*c8dee2aaSAndroid Build Coastguard Worker a2b_grid[a2b_grid_index++] =
773*c8dee2aaSAndroid Build Coastguard Worker SkEndian_SwapBE16(float_to_uInt16Number(c, kOne16XYZ));
774*c8dee2aaSAndroid Build Coastguard Worker }
775*c8dee2aaSAndroid Build Coastguard Worker }
776*c8dee2aaSAndroid Build Coastguard Worker }
777*c8dee2aaSAndroid Build Coastguard Worker }
778*c8dee2aaSAndroid Build Coastguard Worker
779*c8dee2aaSAndroid Build Coastguard Worker // Populate A2B as this tone mapping.
780*c8dee2aaSAndroid Build Coastguard Worker profile.has_A2B = true;
781*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.input_channels = kNumChannels;
782*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.output_channels = kNumChannels;
783*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.matrix_channels = kNumChannels;
784*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < kNumChannels; ++i) {
785*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.grid_points[i] = kGridSize;
786*c8dee2aaSAndroid Build Coastguard Worker // Set the input curve to convert to linear pre-OOTF space.
787*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.input_curves[i].table_entries = kTrcTableSize;
788*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.input_curves[i].table_16 = reinterpret_cast<uint8_t*>(trc_table.data());
789*c8dee2aaSAndroid Build Coastguard Worker // The output and matrix curves are the identity.
790*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.output_curves[i].parametric = SkNamedTransferFn::kLinear;
791*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.matrix_curves[i].parametric = SkNamedTransferFn::kLinear;
792*c8dee2aaSAndroid Build Coastguard Worker // Set the matrix to convert from the primaries to XYZD50.
793*c8dee2aaSAndroid Build Coastguard Worker for (size_t j = 0; j < 3; ++j) {
794*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.matrix.vals[i][j] = toXYZD50.vals[i][j];
795*c8dee2aaSAndroid Build Coastguard Worker }
796*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.matrix.vals[i][3] = 0.f;
797*c8dee2aaSAndroid Build Coastguard Worker }
798*c8dee2aaSAndroid Build Coastguard Worker profile.A2B.grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
799*c8dee2aaSAndroid Build Coastguard Worker
800*c8dee2aaSAndroid Build Coastguard Worker // Populate B2A as the identity.
801*c8dee2aaSAndroid Build Coastguard Worker profile.has_B2A = true;
802*c8dee2aaSAndroid Build Coastguard Worker profile.B2A.input_channels = kNumChannels;
803*c8dee2aaSAndroid Build Coastguard Worker for (size_t i = 0; i < 3; ++i) {
804*c8dee2aaSAndroid Build Coastguard Worker profile.B2A.input_curves[i].parametric = SkNamedTransferFn::kLinear;
805*c8dee2aaSAndroid Build Coastguard Worker }
806*c8dee2aaSAndroid Build Coastguard Worker }
807*c8dee2aaSAndroid Build Coastguard Worker
808*c8dee2aaSAndroid Build Coastguard Worker // Populate CICP.
809*c8dee2aaSAndroid Build Coastguard Worker if (skcms_TransferFunction_isHLGish(&fn) || skcms_TransferFunction_isPQish(&fn)) {
810*c8dee2aaSAndroid Build Coastguard Worker profile.has_CICP = true;
811*c8dee2aaSAndroid Build Coastguard Worker profile.CICP.color_primaries = get_cicp_primaries(toXYZD50);
812*c8dee2aaSAndroid Build Coastguard Worker profile.CICP.transfer_characteristics = get_cicp_trfn(fn);
813*c8dee2aaSAndroid Build Coastguard Worker profile.CICP.matrix_coefficients = 0;
814*c8dee2aaSAndroid Build Coastguard Worker profile.CICP.video_full_range_flag = 1;
815*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(profile.CICP.color_primaries);
816*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(profile.CICP.transfer_characteristics);
817*c8dee2aaSAndroid Build Coastguard Worker }
818*c8dee2aaSAndroid Build Coastguard Worker
819*c8dee2aaSAndroid Build Coastguard Worker std::string description = get_desc_string(fn, toXYZD50);
820*c8dee2aaSAndroid Build Coastguard Worker return SkWriteICCProfile(&profile, description.c_str());
821*c8dee2aaSAndroid Build Coastguard Worker }
822