1 /*
2 * Copyright 2024 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/private/SkGainmapInfo.h"
9
10 #include "include/core/SkColor.h"
11 #include "include/core/SkData.h"
12 #include "include/core/SkRefCnt.h"
13 #include "include/core/SkStream.h"
14 #include "src/base/SkEndian.h"
15 #include "src/codec/SkCodecPriv.h"
16 #include "src/core/SkStreamPriv.h"
17
18 #include <cmath>
19 #include <cstdint>
20 #include <memory>
21
22 namespace {
23 constexpr uint8_t kIsMultiChannelMask = (1u << 7);
24 constexpr uint8_t kUseBaseColourSpaceMask = (1u << 6);
25 } // namespace
26
write_rational_be(SkDynamicMemoryWStream & s,float x)27 static void write_rational_be(SkDynamicMemoryWStream& s, float x) {
28 // TODO(b/338342146): Select denominator to get maximum precision and robustness.
29 uint32_t denominator = 0x10000000;
30 if (std::abs(x) > 1.f) {
31 denominator = 0x1000;
32 }
33 int32_t numerator = static_cast<int32_t>(std::llround(static_cast<double>(x) * denominator));
34 SkWStreamWriteS32BE(&s, numerator);
35 SkWStreamWriteU32BE(&s, denominator);
36 }
37
write_positive_rational_be(SkDynamicMemoryWStream & s,float x)38 static void write_positive_rational_be(SkDynamicMemoryWStream& s, float x) {
39 // TODO(b/338342146): Select denominator to get maximum precision and robustness.
40 uint32_t denominator = 0x10000000;
41 if (x > 1.f) {
42 denominator = 0x1000;
43 }
44 uint32_t numerator = static_cast<uint32_t>(std::llround(static_cast<double>(x) * denominator));
45 SkWStreamWriteU32BE(&s, numerator);
46 SkWStreamWriteU32BE(&s, denominator);
47 }
48
read_u16_be(SkStream * s,uint16_t * value)49 static bool read_u16_be(SkStream* s, uint16_t* value) {
50 if (!s->readU16(value)) {
51 return false;
52 }
53 *value = SkEndian_SwapBE16(*value);
54 return true;
55 }
56
read_u32_be(SkStream * s,uint32_t * value)57 static bool read_u32_be(SkStream* s, uint32_t* value) {
58 if (!s->readU32(value)) {
59 return false;
60 }
61 *value = SkEndian_SwapBE32(*value);
62 return true;
63 }
64
read_s32_be(SkStream * s,int32_t * value)65 static bool read_s32_be(SkStream* s, int32_t* value) {
66 if (!s->readS32(value)) {
67 return false;
68 }
69 *value = SkEndian_SwapBE32(*value);
70 return true;
71 }
72
read_rational_be(SkStream * s,float * value)73 static bool read_rational_be(SkStream* s, float* value) {
74 int32_t numerator = 0;
75 uint32_t denominator = 0;
76 if (!read_s32_be(s, &numerator)) {
77 return false;
78 }
79 if (!read_u32_be(s, &denominator)) {
80 return false;
81 }
82 *value = static_cast<float>(static_cast<double>(numerator) / static_cast<double>(denominator));
83 return true;
84 }
85
read_positive_rational_be(SkStream * s,float * value)86 static bool read_positive_rational_be(SkStream* s, float* value) {
87 uint32_t numerator = 0;
88 uint32_t denominator = 0;
89 if (!read_u32_be(s, &numerator)) {
90 return false;
91 }
92 if (!read_u32_be(s, &denominator)) {
93 return false;
94 }
95 *value = static_cast<float>(static_cast<double>(numerator) / static_cast<double>(denominator));
96 return true;
97 }
98
read_iso_gainmap_version(SkStream * s)99 static bool read_iso_gainmap_version(SkStream* s) {
100 // Ensure minimum version is 0.
101 uint16_t minimum_version = 0;
102 if (!read_u16_be(s, &minimum_version)) {
103 SkCodecPrintf("Failed to read ISO 21496-1 minimum version.\n");
104 return false;
105 }
106 if (minimum_version != 0) {
107 SkCodecPrintf("Unsupported ISO 21496-1 minimum version.\n");
108 return false;
109 }
110
111 // Ensure writer version is present. No value is invalid.
112 uint16_t writer_version = 0;
113 if (!read_u16_be(s, &writer_version)) {
114 SkCodecPrintf("Failed to read ISO 21496-1 version.\n");
115 return false;
116 }
117
118 return true;
119 }
120
read_iso_gainmap_info(SkStream * s,SkGainmapInfo & info)121 static bool read_iso_gainmap_info(SkStream* s, SkGainmapInfo& info) {
122 if (!read_iso_gainmap_version(s)) {
123 SkCodecPrintf("Failed to read ISO 21496-1 version.\n");
124 return false;
125 }
126
127 uint8_t flags = 0;
128 if (!s->readU8(&flags)) {
129 SkCodecPrintf("Failed to read ISO 21496-1 flags.\n");
130 return false;
131 }
132 bool isMultiChannel = (flags & kIsMultiChannelMask) != 0;
133 bool useBaseColourSpace = (flags & kUseBaseColourSpaceMask) != 0;
134
135 float baseHdrHeadroom = 0.f;
136 if (!read_positive_rational_be(s, &baseHdrHeadroom)) {
137 SkCodecPrintf("Failed to read ISO 21496-1 base HDR headroom.\n");
138 return false;
139 }
140 float altrHdrHeadroom = 0.f;
141 if (!read_positive_rational_be(s, &altrHdrHeadroom)) {
142 SkCodecPrintf("Failed to read ISO 21496-1 altr HDR headroom.\n");
143 return false;
144 }
145
146 float gainMapMin[3] = {0.f};
147 float gainMapMax[3] = {0.f};
148 float gamma[3] = {0.f};
149 float baseOffset[3] = {0.f};
150 float altrOffset[3] = {0.f};
151
152 int channelCount = isMultiChannel ? 3 : 1;
153 for (int i = 0; i < channelCount; ++i) {
154 if (!read_rational_be(s, gainMapMin + i)) {
155 SkCodecPrintf("Failed to read ISO 21496-1 gainmap minimum.\n");
156 return false;
157 }
158 if (!read_rational_be(s, gainMapMax + i)) {
159 SkCodecPrintf("Failed to read ISO 21496-1 gainmap maximum.\n");
160 return false;
161 }
162 if (!read_positive_rational_be(s, gamma + i)) {
163 SkCodecPrintf("Failed to read ISO 21496-1 gamma.\n");
164 return false;
165 }
166 if (!read_rational_be(s, baseOffset + i)) {
167 SkCodecPrintf("Failed to read ISO 21496-1 base offset.\n");
168 return false;
169 }
170 if (!read_rational_be(s, altrOffset + i)) {
171 SkCodecPrintf("Failed to read ISO 21496-1 altr offset.\n");
172 return false;
173 }
174 }
175
176 info = SkGainmapInfo();
177 if (!useBaseColourSpace) {
178 info.fGainmapMathColorSpace = SkColorSpace::MakeSRGB();
179 }
180 if (baseHdrHeadroom < altrHdrHeadroom) {
181 info.fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
182 info.fDisplayRatioSdr = std::exp2(baseHdrHeadroom);
183 info.fDisplayRatioHdr = std::exp2(altrHdrHeadroom);
184 } else {
185 info.fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
186 info.fDisplayRatioHdr = std::exp2(baseHdrHeadroom);
187 info.fDisplayRatioSdr = std::exp2(altrHdrHeadroom);
188 }
189 for (int i = 0; i < 3; ++i) {
190 int j = i >= channelCount ? 0 : i;
191 info.fGainmapRatioMin[i] = std::exp2(gainMapMin[j]);
192 info.fGainmapRatioMax[i] = std::exp2(gainMapMax[j]);
193 info.fGainmapGamma[i] = 1.f / gamma[j];
194 switch (info.fBaseImageType) {
195 case SkGainmapInfo::BaseImageType::kSDR:
196 info.fEpsilonSdr[i] = baseOffset[j];
197 info.fEpsilonHdr[i] = altrOffset[j];
198 break;
199 case SkGainmapInfo::BaseImageType::kHDR:
200 info.fEpsilonHdr[i] = baseOffset[j];
201 info.fEpsilonSdr[i] = altrOffset[j];
202 break;
203 }
204 }
205 return true;
206 }
207
isUltraHDRv1Compatible() const208 bool SkGainmapInfo::isUltraHDRv1Compatible() const {
209 // UltraHDR v1 supports having the base image be HDR in theory, but it is largely
210 // untested.
211 if (fBaseImageType == BaseImageType::kHDR) {
212 return false;
213 }
214 // UltraHDR v1 doesn't support a non-base gainmap math color space.
215 if (fGainmapMathColorSpace) {
216 return false;
217 }
218 return true;
219 }
220
ParseVersion(const SkData * data)221 bool SkGainmapInfo::ParseVersion(const SkData* data) {
222 if (!data) {
223 return false;
224 }
225 auto s = SkMemoryStream::MakeDirect(data->data(), data->size());
226 return read_iso_gainmap_version(s.get());
227 }
228
Parse(const SkData * data,SkGainmapInfo & info)229 bool SkGainmapInfo::Parse(const SkData* data, SkGainmapInfo& info) {
230 if (!data) {
231 return false;
232 }
233 auto s = SkMemoryStream::MakeDirect(data->data(), data->size());
234 return read_iso_gainmap_info(s.get(), info);
235 }
236
SerializeVersion()237 sk_sp<SkData> SkGainmapInfo::SerializeVersion() {
238 SkDynamicMemoryWStream s;
239 SkWStreamWriteU16BE(&s, 0); // Minimum reader version
240 SkWStreamWriteU16BE(&s, 0); // Writer version
241 return s.detachAsData();
242 }
243
is_single_channel(SkColor4f c)244 static bool is_single_channel(SkColor4f c) { return c.fR == c.fG && c.fG == c.fB; };
245
serialize() const246 sk_sp<SkData> SkGainmapInfo::serialize() const {
247 SkDynamicMemoryWStream s;
248 // Version.
249 SkWStreamWriteU16BE(&s, 0); // Minimum reader version
250 SkWStreamWriteU16BE(&s, 0); // Writer version
251
252 // Flags.
253 bool all_single_channel = is_single_channel(fGainmapRatioMin) &&
254 is_single_channel(fGainmapRatioMax) &&
255 is_single_channel(fGainmapGamma) && is_single_channel(fEpsilonSdr) &&
256 is_single_channel(fEpsilonHdr);
257 uint8_t flags = 0;
258 if (!fGainmapMathColorSpace) {
259 flags |= kUseBaseColourSpaceMask;
260 }
261 if (!all_single_channel) {
262 flags |= kIsMultiChannelMask;
263 }
264 s.write8(flags);
265
266 // Base and altr headroom.
267 switch (fBaseImageType) {
268 case SkGainmapInfo::BaseImageType::kSDR:
269 write_positive_rational_be(s, std::log2(fDisplayRatioSdr));
270 write_positive_rational_be(s, std::log2(fDisplayRatioHdr));
271 break;
272 case SkGainmapInfo::BaseImageType::kHDR:
273 write_positive_rational_be(s, std::log2(fDisplayRatioHdr));
274 write_positive_rational_be(s, std::log2(fDisplayRatioSdr));
275 break;
276 }
277
278 // Per-channel information.
279 for (int i = 0; i < (all_single_channel ? 1 : 3); ++i) {
280 write_rational_be(s, std::log2(fGainmapRatioMin[i]));
281 write_rational_be(s, std::log2(fGainmapRatioMax[i]));
282 write_positive_rational_be(s, 1.f / fGainmapGamma[i]);
283 switch (fBaseImageType) {
284 case SkGainmapInfo::BaseImageType::kSDR:
285 write_rational_be(s, fEpsilonSdr[i]);
286 write_rational_be(s, fEpsilonHdr[i]);
287 break;
288 case SkGainmapInfo::BaseImageType::kHDR:
289 write_rational_be(s, fEpsilonHdr[i]);
290 write_rational_be(s, fEpsilonSdr[i]);
291 break;
292 }
293 }
294 return s.detachAsData();
295 }
296