1/*
2 * Copyright 2015 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8#include "include/codec/SkAndroidCodec.h"
9
10#include "include/codec/SkCodec.h"
11#include "include/codec/SkEncodedImageFormat.h"
12#include "include/core/SkAlphaType.h"
13#include "include/core/SkColor.h"
14#include "include/core/SkColorType.h"
15#include "include/core/SkData.h"
16#include "include/core/SkRect.h"
17#include "include/core/SkStream.h"
18#include "include/private/SkGainmapInfo.h"
19#include "include/private/base/SkFloatingPoint.h"
20#include "modules/skcms/skcms.h"
21#include "src/codec/SkAndroidCodecAdapter.h"
22#include "src/codec/SkCodecPriv.h"
23#include "src/codec/SkSampledCodec.h"
24
25#include <algorithm>
26#include <cstdint>
27#include <functional>
28#include <utility>
29
30class SkPngChunkReader;
31
32static bool is_valid_sample_size(int sampleSize) {
33 // FIXME: As Leon has mentioned elsewhere, surely there is also a maximum sampleSize?
34 return sampleSize > 0;
35}
36
37static bool cicp_get_primaries(uint8_t primaries, skcms_Matrix3x3* sk_primaries) {
38 // Rec. ITU-T H.273, Table 2.
39 switch (primaries) {
40 case 0:
41 // Reserved.
42 break;
43 case 1:
44 *sk_primaries = SkNamedGamut::kSRGB;
45 return true;
46 case 2:
47 // Unspecified.
48 break;
49 case 3:
50 // Reserved.
51 break;
52 case 4:
53 return skcms_PrimariesToXYZD50(
54 rx: 0.67f, ry: 0.33f, gx: 0.21f, gy: 0.71f, bx: 0.14f, by: 0.08f, wx: 0.31f, wy: 0.316f, toXYZD50: sk_primaries);
55 case 5:
56 return skcms_PrimariesToXYZD50(
57 rx: 0.64f, ry: 0.33f, gx: 0.29f, gy: 0.60f, bx: 0.15f, by: 0.06f, wx: 0.3127f, wy: 0.3290f, toXYZD50: sk_primaries);
58 case 6:
59 return skcms_PrimariesToXYZD50(
60 rx: 0.630f, ry: 0.340f, gx: 0.310f, gy: 0.595f, bx: 0.155f, by: 0.070f, wx: 0.3127f, wy: 0.3290f, toXYZD50: sk_primaries);
61 case 7:
62 return skcms_PrimariesToXYZD50(
63 rx: 0.630f, ry: 0.340f, gx: 0.310f, gy: 0.595f, bx: 0.155f, by: 0.070f, wx: 0.3127f, wy: 0.3290f, toXYZD50: sk_primaries);
64 case 8:
65 return skcms_PrimariesToXYZD50(
66 rx: 0.681f, ry: 0.319f, gx: 0.243f, gy: 0.692f, bx: 0.145f, by: 0.049f, wx: 0.310f, wy: 0.316f, toXYZD50: sk_primaries);
67 case 9:
68 *sk_primaries = SkNamedGamut::kRec2020;
69 return true;
70 case 10:
71 return skcms_PrimariesToXYZD50(
72 rx: 1.f, ry: 0.f, gx: 0.f, gy: 1.f, bx: 0.f, by: 0.f, wx: 1.f / 3.f, wy: 1.f / 3.f, toXYZD50: sk_primaries);
73 case 11:
74 return skcms_PrimariesToXYZD50(
75 rx: 0.680f, ry: 0.320f, gx: 0.265f, gy: 0.690f, bx: 0.150f, by: 0.060f, wx: 0.314f, wy: 0.351f, toXYZD50: sk_primaries);
76 case 12:
77 return skcms_PrimariesToXYZD50(
78 rx: 0.680f, ry: 0.320f, gx: 0.265f, gy: 0.690f, bx: 0.150f, by: 0.060f, wx: 0.3127f, wy: 0.3290f, toXYZD50: sk_primaries);
79 case 22:
80 return skcms_PrimariesToXYZD50(
81 rx: 0.630f, ry: 0.340f, gx: 0.295f, gy: 0.605f, bx: 0.155f, by: 0.077f, wx: 0.3127f, wy: 0.3290f, toXYZD50: sk_primaries);
82 default:
83 // Reserved.
84 break;
85 }
86 *sk_primaries = SkNamedGamut::kSRGB;
87 return false;
88}
89
90static bool cicp_get_transfer_fn(uint8_t transfer_characteristics, skcms_TransferFunction* trfn) {
91 // Rec. ITU-T H.273, Table 3.
92 switch (transfer_characteristics) {
93 case 0:
94 // Reserved.
95 break;
96 case 1:
97 *trfn = SkNamedTransferFn::kRec2020;
98 return true;
99 case 2:
100 // Unspecified.
101 break;
102 case 3:
103 // Reserved.
104 break;
105 case 4:
106 *trfn = {.g: 2.2f, .a: 1.0f, .b: 0.0f, .c: 0.0f, .d: 0.0f, .e: 0.0f, .f: 0.0f};
107 return true;
108 case 5:
109 *trfn = {.g: 2.8f, .a: 1.0f, .b: 0.0f, .c: 0.0f, .d: 0.0f, .e: 0.0f, .f: 0.0f};
110 return true;
111 case 6:
112 *trfn = SkNamedTransferFn::kRec2020;
113 return true;
114 case 7:
115 *trfn = {.g: 2.222222222222f,
116 .a: 0.899626676224f,
117 .b: 0.100373323776f,
118 .c: 0.25f,
119 .d: 0.091286342118f,
120 .e: 0.f,
121 .f: 0.f};
122 return true;
123 case 8:
124 *trfn = SkNamedTransferFn::kLinear;
125 return true;
126 case 9:
127 // Logarithmic transfer characteristic (100:1 range).
128 // Not supported by skcms
129 break;
130 case 10:
131 // Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range).
132 // Not supported by skcms
133 break;
134 case 11:
135 *trfn = SkNamedTransferFn::kSRGB;
136 break;
137 case 12:
138 // Rec. ITU-R BT.1361-0 extended colour gamut system (historical).
139 // Same as kRec709 on positive values, differs on negative values.
140 // Not supported by skcms
141 break;
142 case 13:
143 *trfn = SkNamedTransferFn::kSRGB;
144 return true;
145 case 14:
146 *trfn = SkNamedTransferFn::kRec2020;
147 return true;
148 case 15:
149 *trfn = SkNamedTransferFn::kRec2020;
150 return true;
151 case 16:
152 // Android expects PQ to match 203 nits to SDR white
153 *trfn = {.g: -2.f,
154 .a: -1.55522297832f,
155 .b: 1.86045365631f,
156 .c: 32 / 2523.0f,
157 .d: 2413 / 128.0f,
158 .e: -2392 / 128.0f,
159 .f: 8192 / 1305.0f};
160 return true;
161 case 17:
162 *trfn = {.g: 2.6f, .a: 1.034080527699f, .b: 0.f, .c: 0.f, .d: 0.f, .e: 0.f, .f: 0.f};
163 return true;
164 case 18:
165 // Android expects HLG to match 203 nits to SDR white
166 if (skcms_TransferFunction_makeScaledHLGish(trfn,
167 K: 0.314509843f,
168 R: 2.f,
169 G: 2.f,
170 a: 1.f / 0.17883277f,
171 b: 0.28466892f,
172 c: 0.55991073f)) {
173 return true;
174 }
175 break;
176 default:
177 // 19-255 Reserved.
178 break;
179 }
180
181 *trfn = SkNamedTransferFn::kSRGB;
182 return false;
183}
184
185static sk_sp<SkColorSpace> cicp_get_sk_color_space(uint8_t color_primaries,
186 uint8_t transfer_characteristics,
187 uint8_t matrix_coefficients,
188 uint8_t full_range_flag) {
189 if (matrix_coefficients != 0) return nullptr;
190
191 if (full_range_flag != 1) return nullptr;
192
193 skcms_TransferFunction trfn;
194 if (!cicp_get_transfer_fn(transfer_characteristics, trfn: &trfn)) return nullptr;
195
196 skcms_Matrix3x3 primaries;
197 if (!cicp_get_primaries(primaries: color_primaries, sk_primaries: &primaries)) return nullptr;
198
199 return SkColorSpace::MakeRGB(transferFn: trfn, toXYZ: primaries);
200}
201
202SkAndroidCodec::SkAndroidCodec(SkCodec* codec)
203 : fInfo(codec->getInfo())
204 , fCodec(codec)
205{}
206
207SkAndroidCodec::~SkAndroidCodec() {}
208
209std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
210 SkPngChunkReader* chunkReader) {
211 auto codec = SkCodec::MakeFromStream(std::move(stream), nullptr, chunkReader);
212 return MakeFromCodec(std::move(codec));
213}
214
215std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromCodec(std::unique_ptr<SkCodec> codec) {
216 if (nullptr == codec) {
217 return nullptr;
218 }
219
220 const SkEncodedImageFormat format = codec->getEncodedFormat();
221 if (format == SkEncodedImageFormat::kAVIF) {
222 if (SkCodecs::HasDecoder(id: "avif")) {
223 // If a dedicated AVIF decoder has been registered, SkAvifCodec can
224 // handle scaling internally.
225 return std::make_unique<SkAndroidCodecAdapter>(args: codec.release());
226 }
227 // This will fallback to SkHeifCodec, which needs sampling.
228 return std::make_unique<SkSampledCodec>(args: codec.release());
229 }
230
231 switch (format) {
232 case SkEncodedImageFormat::kPNG:
233 case SkEncodedImageFormat::kICO:
234 case SkEncodedImageFormat::kJPEG:
235 case SkEncodedImageFormat::kBMP:
236 case SkEncodedImageFormat::kWBMP:
237 case SkEncodedImageFormat::kHEIF:
238 return std::make_unique<SkSampledCodec>(args: codec.release());
239 case SkEncodedImageFormat::kGIF:
240 case SkEncodedImageFormat::kWEBP:
241 case SkEncodedImageFormat::kDNG:
242 return std::make_unique<SkAndroidCodecAdapter>(args: codec.release());
243 case SkEncodedImageFormat::kAVIF: // Handled above
244 case SkEncodedImageFormat::kPKM:
245 case SkEncodedImageFormat::kKTX:
246 case SkEncodedImageFormat::kASTC:
247 case SkEncodedImageFormat::kJPEGXL:
248 return nullptr;
249 }
250 SkUNREACHABLE;
251}
252
253std::unique_ptr<SkAndroidCodec> SkAndroidCodec::MakeFromData(sk_sp<SkData> data,
254 SkPngChunkReader* chunkReader) {
255 if (!data) {
256 return nullptr;
257 }
258
259 return MakeFromStream(stream: SkMemoryStream::Make(data: std::move(data)), chunkReader);
260}
261
262SkColorType SkAndroidCodec::computeOutputColorType(SkColorType requestedColorType) {
263 bool highPrecision = fCodec->getEncodedInfo().bitsPerComponent() > 8;
264 uint8_t colorDepth = fCodec->getEncodedInfo().getColorDepth();
265 switch (requestedColorType) {
266 case kARGB_4444_SkColorType:
267 return kN32_SkColorType;
268 case kN32_SkColorType:
269 break;
270 case kAlpha_8_SkColorType:
271 // Fall through to kGray_8. Before kGray_8_SkColorType existed,
272 // we allowed clients to request kAlpha_8 when they wanted a
273 // grayscale decode.
274 case kGray_8_SkColorType:
275 if (kGray_8_SkColorType == this->getInfo().colorType()) {
276 return kGray_8_SkColorType;
277 }
278 break;
279 case kRGB_565_SkColorType:
280 if (kOpaque_SkAlphaType == this->getInfo().alphaType()) {
281 return kRGB_565_SkColorType;
282 }
283 break;
284 case kRGBA_1010102_SkColorType:
285 if (colorDepth == 10) {
286 return kRGBA_1010102_SkColorType;
287 }
288 break;
289 case kRGBA_F16_SkColorType:
290 return kRGBA_F16_SkColorType;
291 default:
292 break;
293 }
294
295 // F16 is the Android default for high precision images.
296 return highPrecision ? kRGBA_F16_SkColorType :
297 (colorDepth == 10 ? kRGBA_1010102_SkColorType : kN32_SkColorType);
298}
299
300SkAlphaType SkAndroidCodec::computeOutputAlphaType(bool requestedUnpremul) {
301 if (kOpaque_SkAlphaType == this->getInfo().alphaType()) {
302 return kOpaque_SkAlphaType;
303 }
304 return requestedUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType;
305}
306
307sk_sp<SkColorSpace> SkAndroidCodec::computeOutputColorSpace(SkColorType outputColorType,
308 sk_sp<SkColorSpace> prefColorSpace) {
309 switch (outputColorType) {
310 case kRGBA_F16_SkColorType:
311 case kRGB_565_SkColorType:
312 case kRGBA_8888_SkColorType:
313 case kBGRA_8888_SkColorType:
314 case kRGBA_1010102_SkColorType: {
315 // If |prefColorSpace| is supplied, choose it.
316 if (prefColorSpace) {
317 return prefColorSpace;
318 }
319
320 const skcms_ICCProfile* encodedProfile = fCodec->getEncodedInfo().profile();
321 if (encodedProfile) {
322 // Prefer CICP information if it exists.
323 if (encodedProfile->has_CICP) {
324 const auto cicpColorSpace =
325 cicp_get_sk_color_space(color_primaries: encodedProfile->CICP.color_primaries,
326 transfer_characteristics: encodedProfile->CICP.transfer_characteristics,
327 matrix_coefficients: encodedProfile->CICP.matrix_coefficients,
328 full_range_flag: encodedProfile->CICP.video_full_range_flag);
329 if (cicpColorSpace) {
330 return cicpColorSpace;
331 }
332 }
333 if (auto encodedSpace = SkColorSpace::Make(*encodedProfile)) {
334 // Leave the pixels in the encoded color space. Color space conversion
335 // will be handled after decode time.
336 return encodedSpace;
337 }
338
339 if (encodedProfile->has_toXYZD50) {
340 return SkColorSpace::MakeRGB(transferFn: SkNamedTransferFn::kSRGB,
341 toXYZ: encodedProfile->toXYZD50);
342 }
343 }
344
345 return SkColorSpace::MakeSRGB();
346 }
347 default:
348 // Color correction not supported for kGray.
349 return nullptr;
350 }
351}
352
353static bool supports_any_down_scale(const SkCodec* codec) {
354 return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
355}
356
357// There are a variety of ways two SkISizes could be compared. This method
358// returns true if either dimensions of a is < that of b.
359// computeSampleSize also uses the opposite, which means that both
360// dimensions of a >= b.
361static inline bool smaller_than(const SkISize& a, const SkISize& b) {
362 return a.width() < b.width() || a.height() < b.height();
363}
364
365// Both dimensions of a > that of b.
366static inline bool strictly_bigger_than(const SkISize& a, const SkISize& b) {
367 return a.width() > b.width() && a.height() > b.height();
368}
369
370int SkAndroidCodec::computeSampleSize(SkISize* desiredSize) const {
371 SkASSERT(desiredSize);
372
373 const auto origDims = fCodec->dimensions();
374 if (!desiredSize || *desiredSize == origDims) {
375 return 1;
376 }
377
378 if (smaller_than(a: origDims, b: *desiredSize)) {
379 *desiredSize = origDims;
380 return 1;
381 }
382
383 // Handle bad input:
384 if (desiredSize->width() < 1 || desiredSize->height() < 1) {
385 *desiredSize = SkISize::Make(w: std::max(a: 1, b: desiredSize->width()),
386 h: std::max(a: 1, b: desiredSize->height()));
387 }
388
389 if (supports_any_down_scale(codec: fCodec.get())) {
390 return 1;
391 }
392
393 int sampleX = origDims.width() / desiredSize->width();
394 int sampleY = origDims.height() / desiredSize->height();
395 int sampleSize = std::min(a: sampleX, b: sampleY);
396 auto computedSize = this->getSampledDimensions(sampleSize);
397 if (computedSize == *desiredSize) {
398 return sampleSize;
399 }
400
401 if (computedSize == origDims || sampleSize == 1) {
402 // Cannot downscale
403 *desiredSize = computedSize;
404 return 1;
405 }
406
407 if (strictly_bigger_than(a: computedSize, b: *desiredSize)) {
408 // See if there is a tighter fit.
409 while (true) {
410 auto smaller = this->getSampledDimensions(sampleSize: sampleSize + 1);
411 if (smaller == *desiredSize) {
412 return sampleSize + 1;
413 }
414 if (smaller == computedSize || smaller_than(a: smaller, b: *desiredSize)) {
415 // Cannot get any smaller without being smaller than desired.
416 *desiredSize = computedSize;
417 return sampleSize;
418 }
419
420 sampleSize++;
421 computedSize = smaller;
422 }
423
424 SkASSERT(false);
425 }
426
427 if (!smaller_than(a: computedSize, b: *desiredSize)) {
428 // This means one of the computed dimensions is equal to desired, and
429 // the other is bigger. This is as close as we can get.
430 *desiredSize = computedSize;
431 return sampleSize;
432 }
433
434 // computedSize is too small. Make it larger.
435 while (sampleSize > 2) {
436 auto bigger = this->getSampledDimensions(sampleSize: sampleSize - 1);
437 if (bigger == *desiredSize || !smaller_than(a: bigger, b: *desiredSize)) {
438 *desiredSize = bigger;
439 return sampleSize - 1;
440 }
441 sampleSize--;
442 }
443
444 *desiredSize = origDims;
445 return 1;
446}
447
448SkISize SkAndroidCodec::getSampledDimensions(int sampleSize) const {
449 if (!is_valid_sample_size(sampleSize)) {
450 return {.fWidth: 0, .fHeight: 0};
451 }
452
453 // Fast path for when we are not scaling.
454 if (1 == sampleSize) {
455 return fCodec->dimensions();
456 }
457
458 return this->onGetSampledDimensions(sampleSize);
459}
460
461bool SkAndroidCodec::getSupportedSubset(SkIRect* desiredSubset) const {
462 if (!desiredSubset || !is_valid_subset(subset: *desiredSubset, imageDims: fCodec->dimensions())) {
463 return false;
464 }
465
466 return this->onGetSupportedSubset(desiredSubset);
467}
468
469SkISize SkAndroidCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const {
470 if (!is_valid_sample_size(sampleSize)) {
471 return {.fWidth: 0, .fHeight: 0};
472 }
473
474 // We require that the input subset is a subset that is supported by SkAndroidCodec.
475 // We test this by calling getSupportedSubset() and verifying that no modifications
476 // are made to the subset.
477 SkIRect copySubset = subset;
478 if (!this->getSupportedSubset(desiredSubset: &copySubset) || copySubset != subset) {
479 return {.fWidth: 0, .fHeight: 0};
480 }
481
482 // If the subset is the entire image, for consistency, use getSampledDimensions().
483 if (fCodec->dimensions() == subset.size()) {
484 return this->getSampledDimensions(sampleSize);
485 }
486
487 // This should perhaps call a virtual function, but currently both of our subclasses
488 // want the same implementation.
489 return {.fWidth: get_scaled_dimension(srcDimension: subset.width(), sampleSize),
490 .fHeight: get_scaled_dimension(srcDimension: subset.height(), sampleSize)};
491}
492
493SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& requestInfo,
494 void* requestPixels, size_t requestRowBytes, const AndroidOptions* options) {
495 if (!requestPixels) {
496 return SkCodec::kInvalidParameters;
497 }
498 if (requestRowBytes < requestInfo.minRowBytes()) {
499 return SkCodec::kInvalidParameters;
500 }
501
502 AndroidOptions defaultOptions;
503 if (!options) {
504 options = &defaultOptions;
505 } else {
506 if (options->fSubset) {
507 if (!is_valid_subset(subset: *options->fSubset, imageDims: fCodec->dimensions())) {
508 return SkCodec::kInvalidParameters;
509 }
510
511 if (SkIRect::MakeSize(size: fCodec->dimensions()) == *options->fSubset) {
512 // The caller wants the whole thing, rather than a subset. Modify
513 // the AndroidOptions passed to onGetAndroidPixels to not specify
514 // a subset.
515 defaultOptions = *options;
516 defaultOptions.fSubset = nullptr;
517 options = &defaultOptions;
518 }
519 }
520 }
521
522 // We may need to have handleFrameIndex recursively call this method
523 // to resolve one frame depending on another. The recursion stops
524 // when we find a frame which does not require an earlier frame
525 // e.g. frame->getRequiredFrame() returns kNoFrame
526 auto getPixelsFn = [&](const SkImageInfo& info, void* pixels, size_t rowBytes,
527 const SkCodec::Options& opts, int requiredFrame
528 ) -> SkCodec::Result {
529 SkAndroidCodec::AndroidOptions prevFrameOptions(
530 reinterpret_cast<const SkAndroidCodec::AndroidOptions&>(opts));
531 prevFrameOptions.fFrameIndex = requiredFrame;
532 return this->getAndroidPixels(requestInfo: info, requestPixels: pixels, requestRowBytes: rowBytes, options: &prevFrameOptions);
533 };
534 if (auto result = fCodec->handleFrameIndex(requestInfo, pixels: requestPixels, rowBytes: requestRowBytes,
535 *options, getPixelsFn); result != SkCodec::kSuccess) {
536 return result;
537 }
538
539 return this->onGetAndroidPixels(info: requestInfo, pixels: requestPixels, rowBytes: requestRowBytes, options: *options);
540}
541
542SkCodec::Result SkAndroidCodec::getAndroidPixels(const SkImageInfo& info, void* pixels,
543 size_t rowBytes) {
544 return this->getAndroidPixels(requestInfo: info, requestPixels: pixels, requestRowBytes: rowBytes, options: nullptr);
545}
546
547bool SkAndroidCodec::getAndroidGainmap(SkGainmapInfo* info,
548 std::unique_ptr<SkStream>* outGainmapImageStream) {
549 if (!fCodec->onGetGainmapInfo(info, outGainmapImageStream)) {
550 return false;
551 }
552 // Convert old parameter names to new parameter names.
553 // TODO(ccameron): Remove these parameters.
554 info->fLogRatioMin.fR = sk_float_log(x: info->fGainmapRatioMin.fR);
555 info->fLogRatioMin.fG = sk_float_log(x: info->fGainmapRatioMin.fG);
556 info->fLogRatioMin.fB = sk_float_log(x: info->fGainmapRatioMin.fB);
557 info->fLogRatioMax.fR = sk_float_log(x: info->fGainmapRatioMax.fR);
558 info->fLogRatioMax.fG = sk_float_log(x: info->fGainmapRatioMax.fG);
559 info->fLogRatioMax.fB = sk_float_log(x: info->fGainmapRatioMax.fB);
560 info->fHdrRatioMin = info->fDisplayRatioSdr;
561 info->fHdrRatioMax = info->fDisplayRatioHdr;
562 return true;
563}
564

source code of flutter_engine/third_party/skia/src/codec/SkAndroidCodec.cpp