xref: /aosp_15_r20/external/skia/src/codec/SkGainmapInfo.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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