• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef USE_BIG_ENDIAN
18 #define USE_BIG_ENDIAN true
19 #endif
20 
21 #include <ultrahdr/icc.h>
22 #include <ultrahdr/gainmapmath.h>
23 #include <vector>
24 #include <utils/Log.h>
25 
26 #ifndef FLT_MAX
27 #define FLT_MAX 0x1.fffffep127f
28 #endif
29 
30 namespace android::ultrahdr {
Matrix3x3_apply(const Matrix3x3 * m,float * x)31 static void Matrix3x3_apply(const Matrix3x3* m, float* x) {
32     float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2];
33     float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2];
34     float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2];
35     x[0] = y0;
36     x[1] = y1;
37     x[2] = y2;
38 }
39 
Matrix3x3_invert(const Matrix3x3 * src,Matrix3x3 * dst)40 bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) {
41     double a00 = src->vals[0][0],
42            a01 = src->vals[1][0],
43            a02 = src->vals[2][0],
44            a10 = src->vals[0][1],
45            a11 = src->vals[1][1],
46            a12 = src->vals[2][1],
47            a20 = src->vals[0][2],
48            a21 = src->vals[1][2],
49            a22 = src->vals[2][2];
50 
51     double b0 = a00*a11 - a01*a10,
52            b1 = a00*a12 - a02*a10,
53            b2 = a01*a12 - a02*a11,
54            b3 = a20,
55            b4 = a21,
56            b5 = a22;
57 
58     double determinant = b0*b5
59                        - b1*b4
60                        + b2*b3;
61 
62     if (determinant == 0) {
63         return false;
64     }
65 
66     double invdet = 1.0 / determinant;
67     if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) {
68         return false;
69     }
70 
71     b0 *= invdet;
72     b1 *= invdet;
73     b2 *= invdet;
74     b3 *= invdet;
75     b4 *= invdet;
76     b5 *= invdet;
77 
78     dst->vals[0][0] = (float)( a11*b5 - a12*b4 );
79     dst->vals[1][0] = (float)( a02*b4 - a01*b5 );
80     dst->vals[2][0] = (float)(        +     b2 );
81     dst->vals[0][1] = (float)( a12*b3 - a10*b5 );
82     dst->vals[1][1] = (float)( a00*b5 - a02*b3 );
83     dst->vals[2][1] = (float)(        -     b1 );
84     dst->vals[0][2] = (float)( a10*b4 - a11*b3 );
85     dst->vals[1][2] = (float)( a01*b3 - a00*b4 );
86     dst->vals[2][2] = (float)(        +     b0 );
87 
88     for (int r = 0; r < 3; ++r)
89     for (int c = 0; c < 3; ++c) {
90         if (!isfinitef_(dst->vals[r][c])) {
91             return false;
92         }
93     }
94     return true;
95 }
96 
Matrix3x3_concat(const Matrix3x3 * A,const Matrix3x3 * B)97 static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) {
98     Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } };
99     for (int r = 0; r < 3; r++)
100         for (int c = 0; c < 3; c++) {
101             m.vals[r][c] = A->vals[r][0] * B->vals[0][c]
102                          + A->vals[r][1] * B->vals[1][c]
103                          + A->vals[r][2] * B->vals[2][c];
104         }
105     return m;
106 }
107 
float_XYZD50_to_grid16_lab(const float * xyz_float,uint8_t * grid16_lab)108 static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) {
109     float v[3] = {
110             xyz_float[0] / kD50_x,
111             xyz_float[1] / kD50_y,
112             xyz_float[2] / kD50_z,
113     };
114     for (size_t i = 0; i < 3; ++i) {
115         v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f);
116     }
117     const float L = v[1] * 116.0f - 16.0f;
118     const float a = (v[0] - v[1]) * 500.0f;
119     const float b = (v[1] - v[2]) * 200.0f;
120     const float Lab_unorm[3] = {
121             L * (1 / 100.f),
122             (a + 128.0f) * (1 / 255.0f),
123             (b + 128.0f) * (1 / 255.0f),
124     };
125     // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the
126     // table, but the spec appears to indicate that the value should be 0xFF00.
127     // https://crbug.com/skia/13807
128     for (size_t i = 0; i < 3; ++i) {
129         reinterpret_cast<uint16_t*>(grid16_lab)[i] =
130                 Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i]));
131     }
132 }
133 
get_desc_string(const ultrahdr_transfer_function tf,const ultrahdr_color_gamut gamut)134 std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf,
135                                        const ultrahdr_color_gamut gamut) {
136     std::string result;
137     switch (gamut) {
138         case ULTRAHDR_COLORGAMUT_BT709:
139             result += "sRGB";
140             break;
141         case ULTRAHDR_COLORGAMUT_P3:
142             result += "Display P3";
143             break;
144         case ULTRAHDR_COLORGAMUT_BT2100:
145             result += "Rec2020";
146             break;
147         default:
148             result += "Unknown";
149             break;
150     }
151     result += " Gamut with ";
152     switch (tf) {
153         case ULTRAHDR_TF_SRGB:
154             result += "sRGB";
155             break;
156         case ULTRAHDR_TF_LINEAR:
157             result += "Linear";
158             break;
159         case ULTRAHDR_TF_PQ:
160             result += "PQ";
161             break;
162         case ULTRAHDR_TF_HLG:
163             result += "HLG";
164             break;
165         default:
166             result += "Unknown";
167             break;
168     }
169     result += " Transfer";
170     return result;
171 }
172 
write_text_tag(const char * text)173 sp<DataStruct> IccHelper::write_text_tag(const char* text) {
174     uint32_t text_length = strlen(text);
175     uint32_t header[] = {
176             Endian_SwapBE32(kTAG_TextType),                         // Type signature
177             0,                                                      // Reserved
178             Endian_SwapBE32(1),                                     // Number of records
179             Endian_SwapBE32(12),                                    // Record size (must be 12)
180             Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')),    // English USA
181             Endian_SwapBE32(2 * text_length),                       // Length of string in bytes
182             Endian_SwapBE32(28),                                    // Offset of string
183     };
184 
185     uint32_t total_length = text_length * 2 + sizeof(header);
186     total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
187     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
188 
189     if (!dataStruct->write(header, sizeof(header))) {
190         ALOGE("write_text_tag(): error in writing data");
191         return dataStruct;
192     }
193 
194     for (size_t i = 0; i < text_length; i++) {
195         // Convert ASCII to big-endian UTF-16.
196         dataStruct->write8(0);
197         dataStruct->write8(text[i]);
198     }
199 
200     return dataStruct;
201 }
202 
write_xyz_tag(float x,float y,float z)203 sp<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
204     uint32_t data[] = {
205             Endian_SwapBE32(kXYZ_PCSSpace),
206             0,
207             static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
208             static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
209             static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
210     };
211     sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
212     dataStruct->write(&data, sizeof(data));
213     return dataStruct;
214 }
215 
write_trc_tag(const int table_entries,const void * table_16)216 sp<DataStruct> IccHelper::write_trc_tag(const int table_entries, const void* table_16) {
217     int total_length = 4 + 4 + 4 + table_entries * 2;
218     total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
219     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
220     dataStruct->write32(Endian_SwapBE32(kTAG_CurveType));     // Type
221     dataStruct->write32(0);                                     // Reserved
222     dataStruct->write32(Endian_SwapBE32(table_entries));  // Value count
223     for (size_t i = 0; i < table_entries; ++i) {
224         uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i];
225         dataStruct->write16(value);
226     }
227     return dataStruct;
228 }
229 
write_trc_tag_for_linear()230 sp<DataStruct> IccHelper::write_trc_tag_for_linear() {
231     int total_length = 16;
232     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
233     dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType));  // Type
234     dataStruct->write32(0);                                      // Reserved
235     dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
236     dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(1.0)));
237 
238     return dataStruct;
239 }
240 
compute_tone_map_gain(const ultrahdr_transfer_function tf,float L)241 float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
242     if (L <= 0.f) {
243         return 1.f;
244     }
245     if (tf == ULTRAHDR_TF_PQ) {
246         // The PQ transfer function will map to the range [0, 1]. Linearly scale
247         // it up to the range [0, 10,000/203]. We will then tone map that back
248         // down to [0, 1].
249         constexpr float kInputMaxLuminance = 10000 / 203.f;
250         constexpr float kOutputMaxLuminance = 1.0;
251         L *= kInputMaxLuminance;
252 
253         // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
254         constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
255         constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
256         return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
257     }
258     if (tf == ULTRAHDR_TF_HLG) {
259         // Let Lw be the brightness of the display in nits.
260         constexpr float Lw = 203.f;
261         const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
262         return std::pow(L, gamma - 1.f);
263     }
264     return 1.f;
265 }
266 
write_cicp_tag(uint32_t color_primaries,uint32_t transfer_characteristics)267 sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
268                                          uint32_t transfer_characteristics) {
269     int total_length = 12;  // 4 + 4 + 1 + 1 + 1 + 1
270     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
271     dataStruct->write32(Endian_SwapBE32(kTAG_cicp));    // Type signature
272     dataStruct->write32(0);                             // Reserved
273     dataStruct->write8(color_primaries);                // Color primaries
274     dataStruct->write8(transfer_characteristics);       // Transfer characteristics
275     dataStruct->write8(0);                              // RGB matrix
276     dataStruct->write8(1);                              // Full range
277     return dataStruct;
278 }
279 
compute_lut_entry(const Matrix3x3 & src_to_XYZD50,float rgb[3])280 void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) {
281     // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50.
282     Matrix3x3 src_to_rec2020;
283     const Matrix3x3 rec2020_to_XYZD50 = kRec2020;
284     {
285         Matrix3x3 XYZD50_to_rec2020;
286         Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020);
287         src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50);
288     }
289 
290     // Convert the source signal to linear.
291     for (size_t i = 0; i < kNumChannels; ++i) {
292         rgb[i] = pqOetf(rgb[i]);
293     }
294 
295     // Convert source gamut to Rec2020.
296     Matrix3x3_apply(&src_to_rec2020, rgb);
297 
298     // Compute the luminance of the signal.
299     float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}});
300 
301     // Compute the tone map gain based on the luminance.
302     float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L);
303 
304     // Apply the tone map gain.
305     for (size_t i = 0; i < kNumChannels; ++i) {
306         rgb[i] *= tone_map_gain;
307     }
308 
309     // Convert from Rec2020-linear to XYZD50.
310     Matrix3x3_apply(&rec2020_to_XYZD50, rgb);
311 }
312 
write_clut(const uint8_t * grid_points,const uint8_t * grid_16)313 sp<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, const uint8_t* grid_16) {
314     uint32_t value_count = kNumChannels;
315     for (uint32_t i = 0; i < kNumChannels; ++i) {
316         value_count *= grid_points[i];
317     }
318 
319     int total_length = 20 + 2 * value_count;
320     total_length = (((total_length + 2) >> 2) << 2);  // 4 aligned
321     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
322 
323     for (size_t i = 0; i < 16; ++i) {
324         dataStruct->write8(i < kNumChannels ? grid_points[i] : 0);  // Grid size
325     }
326     dataStruct->write8(2);  // Grid byte width (always 16-bit)
327     dataStruct->write8(0);  // Reserved
328     dataStruct->write8(0);  // Reserved
329     dataStruct->write8(0);  // Reserved
330 
331     for (uint32_t i = 0; i < value_count; ++i) {
332         uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i];
333         dataStruct->write16(value);
334     }
335 
336     return dataStruct;
337 }
338 
write_mAB_or_mBA_tag(uint32_t type,bool has_a_curves,const uint8_t * grid_points,const uint8_t * grid_16)339 sp<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type,
340                                                bool has_a_curves,
341                                                const uint8_t* grid_points,
342                                                const uint8_t* grid_16) {
343     const size_t b_curves_offset = 32;
344     sp<DataStruct> b_curves_data[kNumChannels];
345     sp<DataStruct> a_curves_data[kNumChannels];
346     size_t clut_offset = 0;
347     sp<DataStruct> clut;
348     size_t a_curves_offset = 0;
349 
350     // The "B" curve is required.
351     for (size_t i = 0; i < kNumChannels; ++i) {
352         b_curves_data[i] = write_trc_tag_for_linear();
353     }
354 
355     // The "A" curve and CLUT are optional.
356     if (has_a_curves) {
357         clut_offset = b_curves_offset;
358         for (size_t i = 0; i < kNumChannels; ++i) {
359             clut_offset += b_curves_data[i]->getLength();
360         }
361         clut = write_clut(grid_points, grid_16);
362 
363         a_curves_offset = clut_offset + clut->getLength();
364         for (size_t i = 0; i < kNumChannels; ++i) {
365             a_curves_data[i] = write_trc_tag_for_linear();
366         }
367     }
368 
369     int total_length = b_curves_offset;
370     for (size_t i = 0; i < kNumChannels; ++i) {
371         total_length += b_curves_data[i]->getLength();
372     }
373     if (has_a_curves) {
374         total_length += clut->getLength();
375         for (size_t i = 0; i < kNumChannels; ++i) {
376             total_length += a_curves_data[i]->getLength();
377         }
378     }
379     sp<DataStruct> dataStruct = sp<DataStruct>::make(total_length);
380     dataStruct->write32(Endian_SwapBE32(type));             // Type signature
381     dataStruct->write32(0);                                 // Reserved
382     dataStruct->write8(kNumChannels);                       // Input channels
383     dataStruct->write8(kNumChannels);                       // Output channels
384     dataStruct->write16(0);                                 // Reserved
385     dataStruct->write32(Endian_SwapBE32(b_curves_offset));  // B curve offset
386     dataStruct->write32(Endian_SwapBE32(0));                // Matrix offset (ignored)
387     dataStruct->write32(Endian_SwapBE32(0));                // M curve offset (ignored)
388     dataStruct->write32(Endian_SwapBE32(clut_offset));      // CLUT offset
389     dataStruct->write32(Endian_SwapBE32(a_curves_offset));  // A curve offset
390     for (size_t i = 0; i < kNumChannels; ++i) {
391         if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) {
392             return dataStruct;
393         }
394     }
395     if (has_a_curves) {
396         dataStruct->write(clut->getData(), clut->getLength());
397         for (size_t i = 0; i < kNumChannels; ++i) {
398             dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength());
399         }
400     }
401     return dataStruct;
402 }
403 
writeIccProfile(ultrahdr_transfer_function tf,ultrahdr_color_gamut gamut)404 sp<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
405                                           ultrahdr_color_gamut gamut) {
406     ICCHeader header;
407 
408     std::vector<std::pair<uint32_t, sp<DataStruct>>> tags;
409 
410     // Compute profile description tag
411     std::string desc = get_desc_string(tf, gamut);
412 
413     tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str()));
414 
415     Matrix3x3 toXYZD50;
416     switch (gamut) {
417         case ULTRAHDR_COLORGAMUT_BT709:
418             toXYZD50 = kSRGB;
419             break;
420         case ULTRAHDR_COLORGAMUT_P3:
421             toXYZD50 = kDisplayP3;
422             break;
423         case ULTRAHDR_COLORGAMUT_BT2100:
424             toXYZD50 = kRec2020;
425             break;
426         default:
427             // Should not fall here.
428             return nullptr;
429     }
430 
431     // Compute primaries.
432     {
433         tags.emplace_back(kTAG_rXYZ,
434                 write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0]));
435         tags.emplace_back(kTAG_gXYZ,
436                 write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1]));
437         tags.emplace_back(kTAG_bXYZ,
438                 write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2]));
439     }
440 
441     // Compute white point tag (must be D50)
442     tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z));
443 
444     // Compute transfer curves.
445     if (tf != ULTRAHDR_TF_PQ) {
446         if (tf == ULTRAHDR_TF_HLG) {
447             std::vector<uint8_t> trc_table;
448             trc_table.resize(kTrcTableSize * 2);
449             for (uint32_t i = 0; i < kTrcTableSize; ++i) {
450                 float x = i / (kTrcTableSize - 1.f);
451                 float y = hlgOetf(x);
452                 y *= compute_tone_map_gain(tf, y);
453                 float_to_table16(y, &trc_table[2 * i]);
454             }
455 
456             tags.emplace_back(kTAG_rTRC,
457                     write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
458             tags.emplace_back(kTAG_gTRC,
459                     write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
460             tags.emplace_back(kTAG_bTRC,
461                     write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
462         } else {
463             tags.emplace_back(kTAG_rTRC, write_trc_tag_for_linear());
464             tags.emplace_back(kTAG_gTRC, write_trc_tag_for_linear());
465             tags.emplace_back(kTAG_bTRC, write_trc_tag_for_linear());
466         }
467     }
468 
469     // Compute CICP.
470     if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) {
471         // The CICP tag is present in ICC 4.4, so update the header's version.
472         header.version = Endian_SwapBE32(0x04400000);
473 
474         uint32_t color_primaries = 0;
475         if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
476             color_primaries = kCICPPrimariesSRGB;
477         } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
478             color_primaries = kCICPPrimariesP3;
479         }
480 
481         uint32_t transfer_characteristics = 0;
482         if (tf == ULTRAHDR_TF_SRGB) {
483             transfer_characteristics = kCICPTrfnSRGB;
484         } else if (tf == ULTRAHDR_TF_LINEAR) {
485             transfer_characteristics = kCICPTrfnLinear;
486         } else if (tf == ULTRAHDR_TF_PQ) {
487             transfer_characteristics = kCICPTrfnPQ;
488         } else if (tf == ULTRAHDR_TF_HLG) {
489             transfer_characteristics = kCICPTrfnHLG;
490         }
491         tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
492     }
493 
494     // Compute A2B0.
495     if (tf == ULTRAHDR_TF_PQ) {
496         std::vector<uint8_t> a2b_grid;
497         a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2);
498         size_t a2b_grid_index = 0;
499         for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) {
500             for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) {
501                 for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) {
502                     float rgb[3] = {
503                             r_index / (kGridSize - 1.f),
504                             g_index / (kGridSize - 1.f),
505                             b_index / (kGridSize - 1.f),
506                     };
507                     compute_lut_entry(toXYZD50, rgb);
508                     float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]);
509                     a2b_grid_index += 6;
510                 }
511             }
512         }
513         const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data());
514 
515         uint8_t grid_points[kNumChannels];
516         for (size_t i = 0; i < kNumChannels; ++i) {
517             grid_points[i] = kGridSize;
518         }
519 
520         auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType,
521                                              /* has_a_curves */ true,
522                                              grid_points,
523                                              grid_16);
524         tags.emplace_back(kTAG_A2B0, std::move(a2b_data));
525     }
526 
527     // Compute B2A0.
528     if (tf == ULTRAHDR_TF_PQ) {
529         auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType,
530                                              /* has_a_curves */ false,
531                                              /* grid_points */ nullptr,
532                                              /* grid_16 */ nullptr);
533         tags.emplace_back(kTAG_B2A0, std::move(b2a_data));
534     }
535 
536     // Compute copyright tag
537     tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022"));
538 
539     // Compute the size of the profile.
540     size_t tag_data_size = 0;
541     for (const auto& tag : tags) {
542         tag_data_size += tag.second->getLength();
543     }
544     size_t tag_table_size = kICCTagTableEntrySize * tags.size();
545     size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size;
546 
547     sp<DataStruct> dataStruct = sp<DataStruct>::make(profile_size + kICCIdentifierSize);
548 
549     // Write identifier, chunk count, and chunk ID
550     if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) ||
551         !dataStruct->write8(1) || !dataStruct->write8(1)) {
552         ALOGE("writeIccProfile(): error in identifier");
553         return dataStruct;
554     }
555 
556     // Write the header.
557     header.data_color_space = Endian_SwapBE32(Signature_RGB);
558     header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ);
559     header.size = Endian_SwapBE32(profile_size);
560     header.tag_count = Endian_SwapBE32(tags.size());
561 
562     if (!dataStruct->write(&header, sizeof(header))) {
563         ALOGE("writeIccProfile(): error in header");
564         return dataStruct;
565     }
566 
567     // Write the tag table. Track the offset and size of the previous tag to
568     // compute each tag's offset. An empty SkData indicates that the previous
569     // tag is to be reused.
570     uint32_t last_tag_offset = sizeof(header) + tag_table_size;
571     uint32_t last_tag_size = 0;
572     for (const auto& tag : tags) {
573         last_tag_offset = last_tag_offset + last_tag_size;
574         last_tag_size = tag.second->getLength();
575         uint32_t tag_table_entry[3] = {
576                 Endian_SwapBE32(tag.first),
577                 Endian_SwapBE32(last_tag_offset),
578                 Endian_SwapBE32(last_tag_size),
579         };
580         if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) {
581             ALOGE("writeIccProfile(): error in writing tag table");
582             return dataStruct;
583         }
584     }
585 
586     // Write the tags.
587     for (const auto& tag : tags) {
588         if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) {
589             ALOGE("writeIccProfile(): error in writing tags");
590             return dataStruct;
591         }
592     }
593 
594     return dataStruct;
595 }
596 
tagsEqualToMatrix(const Matrix3x3 & matrix,const uint8_t * red_tag,const uint8_t * green_tag,const uint8_t * blue_tag)597 bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix,
598                                   const uint8_t* red_tag,
599                                   const uint8_t* green_tag,
600                                   const uint8_t* blue_tag) {
601     sp<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
602                                                 matrix.vals[2][0]);
603     sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
604                                                   matrix.vals[2][1]);
605     sp<DataStruct> blue_tag_test = write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2],
606                                                  matrix.vals[2][2]);
607     return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 &&
608            memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 &&
609            memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0;
610 }
611 
readIccColorGamut(void * icc_data,size_t icc_size)612 ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) {
613     // Each tag table entry consists of 3 fields of 4 bytes each.
614     static const size_t kTagTableEntrySize = 12;
615 
616     if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) {
617         return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
618     }
619 
620     if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) {
621         return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
622     }
623 
624     uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize;
625 
626     ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes);
627 
628     // Use 0 to indicate not found, since offsets are always relative to start
629     // of ICC data and therefore a tag offset of zero would never be valid.
630     size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0;
631     size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0;
632     for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) {
633         uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>(
634             icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize);
635         // first 4 bytes are the tag signature, next 4 bytes are the tag offset,
636         // last 4 bytes are the tag length in bytes.
637         if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) {
638             red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
639             red_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
640         } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) {
641             green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
642             green_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
643         } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) {
644             blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1));
645             blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2));
646         }
647     }
648 
649     if (red_primary_offset == 0 || red_primary_size != kColorantTagSize ||
650         kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size ||
651         green_primary_offset == 0 || green_primary_size != kColorantTagSize ||
652         kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size ||
653         blue_primary_offset == 0 || blue_primary_size != kColorantTagSize ||
654         kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) {
655         return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
656     }
657 
658     uint8_t* red_tag = icc_bytes + red_primary_offset;
659     uint8_t* green_tag = icc_bytes + green_primary_offset;
660     uint8_t* blue_tag = icc_bytes + blue_primary_offset;
661 
662     // Serialize tags as we do on encode and compare what we find to that to
663     // determine the gamut (since we don't have a need yet for full deserialize).
664     if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) {
665         return ULTRAHDR_COLORGAMUT_BT709;
666     } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) {
667         return ULTRAHDR_COLORGAMUT_P3;
668     } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) {
669         return ULTRAHDR_COLORGAMUT_BT2100;
670     }
671 
672     // Didn't find a match to one of the profiles we write; indicate the gamut
673     // is unspecified since we don't understand it.
674     return ULTRAHDR_COLORGAMUT_UNSPECIFIED;
675 }
676 
677 } // namespace android::ultrahdr
678