xref: /aosp_15_r20/external/skia/src/codec/SkCrabbyAvifCodec.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2024 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 "src/codec/SkCrabbyAvifCodec.h"
9 
10 #include "include/codec/SkAndroidCodec.h"
11 #include "include/codec/SkAvifDecoder.h"
12 #include "include/codec/SkCodec.h"
13 #include "include/codec/SkCodecAnimation.h"
14 #include "include/core/SkColorType.h"
15 #include "include/core/SkImageInfo.h"
16 #include "include/core/SkSize.h"
17 #include "include/core/SkStream.h"
18 #include "include/core/SkTypes.h"
19 #include "include/private/SkGainmapInfo.h"
20 #include "modules/skcms/skcms.h"
21 #include "src/core/SkStreamPriv.h"
22 
23 #include <cstdint>
24 #include <cstring>
25 #include <utility>
26 
27 #include "avif/avif.h"
28 #include "avif/libavif_compat.h"
29 
30 namespace {
31 
32 template <typename NumeratorType>
FractionToFloat(NumeratorType numerator,uint32_t denominator)33 float FractionToFloat(NumeratorType numerator, uint32_t denominator) {
34     // First cast to double and not float because uint32_t->float conversion can
35     // cause precision loss.
36     return static_cast<double>(numerator) / denominator;
37 }
38 
AltImageColorSpace(const crabbyavif::avifGainMap & gain_map,const crabbyavif::avifImage & image)39 sk_sp<SkColorSpace> AltImageColorSpace(const crabbyavif::avifGainMap& gain_map,
40                                        const crabbyavif::avifImage& image) {
41     sk_sp<SkColorSpace> color_space = nullptr;
42     if (!gain_map.altICC.size) {
43         return nullptr;
44     }
45     if (image.icc.size == gain_map.altICC.size &&
46         memcmp(gain_map.altICC.data, image.icc.data, gain_map.altICC.size) == 0) {
47         // Same ICC as the base image, no need to specify it.
48         return nullptr;
49     }
50     skcms_ICCProfile icc_profile;
51     if (!skcms_Parse(gain_map.altICC.data, gain_map.altICC.size, &icc_profile)) {
52         return nullptr;
53     }
54     return SkColorSpace::Make(icc_profile);
55 }
56 
PopulateGainmapInfo(const crabbyavif::avifGainMap & gain_map,const crabbyavif::avifImage & image,SkGainmapInfo * info)57 bool PopulateGainmapInfo(const crabbyavif::avifGainMap& gain_map,
58                          const crabbyavif::avifImage& image,
59                          SkGainmapInfo* info) {
60     if (gain_map.baseHdrHeadroom.d == 0 || gain_map.alternateHdrHeadroom.d == 0) {
61         return false;
62     }
63     const float base_headroom =
64             std::exp2(FractionToFloat(gain_map.baseHdrHeadroom.n, gain_map.baseHdrHeadroom.d));
65     const float alternate_headroom = std::exp2(
66             FractionToFloat(gain_map.alternateHdrHeadroom.n, gain_map.alternateHdrHeadroom.d));
67     const bool base_is_hdr = base_headroom > alternate_headroom;
68     info->fDisplayRatioSdr = base_is_hdr ? alternate_headroom : base_headroom;
69     info->fDisplayRatioHdr = base_is_hdr ? base_headroom : alternate_headroom;
70     info->fBaseImageType =
71             base_is_hdr ? SkGainmapInfo::BaseImageType::kHDR : SkGainmapInfo::BaseImageType::kSDR;
72     for (int i = 0; i < 3; ++i) {
73         if (gain_map.gainMapMin[i].d == 0 || gain_map.gainMapMax[i].d == 0 ||
74             gain_map.gainMapGamma[i].d == 0 || gain_map.baseOffset[i].d == 0 ||
75             gain_map.alternateOffset[i].d == 0 || gain_map.gainMapGamma[i].n == 0) {
76             return false;
77         }
78         const float min_log2 = FractionToFloat(gain_map.gainMapMin[i].n, gain_map.gainMapMin[i].d);
79         const float max_log2 = FractionToFloat(gain_map.gainMapMax[i].n, gain_map.gainMapMax[i].d);
80         info->fGainmapRatioMin[i] = std::exp2(min_log2);
81         info->fGainmapRatioMax[i] = std::exp2(max_log2);
82         // Numerator and denominator intentionally swapped to get 1.0/gamma.
83         info->fGainmapGamma[i] =
84                 FractionToFloat(gain_map.gainMapGamma[i].d, gain_map.gainMapGamma[i].n);
85         const float base_offset =
86                 FractionToFloat(gain_map.baseOffset[i].n, gain_map.baseOffset[i].d);
87         const float alternate_offset =
88                 FractionToFloat(gain_map.alternateOffset[i].n, gain_map.alternateOffset[i].d);
89         info->fEpsilonSdr[i] = base_is_hdr ? alternate_offset : base_offset;
90         info->fEpsilonHdr[i] = base_is_hdr ? base_offset : alternate_offset;
91     }
92     if (!gain_map.useBaseColorSpace) {
93         info->fGainmapMathColorSpace = AltImageColorSpace(gain_map, image);
94     }
95     return true;
96 }
97 
98 }  // namespace
99 
operator ()(crabbyavif::avifDecoder * decoder) const100 void AvifDecoderDeleter::operator()(crabbyavif::avifDecoder* decoder) const {
101     if (decoder != nullptr) {
102         crabbyavif::avifDecoderDestroy(decoder);
103     }
104 }
105 
IsAvif(const void * buffer,size_t bytesRead)106 bool SkCrabbyAvifCodec::IsAvif(const void* buffer, size_t bytesRead) {
107     crabbyavif::avifROData avifData = {static_cast<const uint8_t*>(buffer), bytesRead};
108     return crabbyavif::avifPeekCompatibleFileType(&avifData) == crabbyavif::CRABBY_AVIF_TRUE;
109 }
110 
MakeFromStream(std::unique_ptr<SkStream> stream,Result * result,bool gainmapOnly)111 std::unique_ptr<SkCodec> SkCrabbyAvifCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
112                                                            Result* result,
113                                                            bool gainmapOnly /*=false*/) {
114     SkASSERT(result);
115     if (!stream) {
116         *result = SkCodec::kInvalidInput;
117         return nullptr;
118     }
119 
120     // CrabbyAvif needs a contiguous data buffer.
121     sk_sp<SkData> data = nullptr;
122     if (stream->getMemoryBase()) {
123         // It is safe to make without copy because we'll hold onto the stream.
124         data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
125     } else {
126         data = SkCopyStreamToData(stream.get());
127         // If we are forced to copy the stream to a data, we can go ahead and
128         // delete the stream.
129         stream.reset(nullptr);
130     }
131     return SkCrabbyAvifCodec::MakeFromData(std::move(stream), std::move(data), result, gainmapOnly);
132 }
133 
MakeFromData(std::unique_ptr<SkStream> stream,sk_sp<SkData> data,Result * result,bool gainmapOnly)134 std::unique_ptr<SkCodec> SkCrabbyAvifCodec::MakeFromData(std::unique_ptr<SkStream> stream,
135                                                          sk_sp<SkData> data,
136                                                          Result* result,
137                                                          bool gainmapOnly /*=false*/) {
138     SkASSERT(result);
139 
140     AvifDecoder avifDecoder(crabbyavif::avifDecoderCreate());
141     if (avifDecoder == nullptr) {
142         *result = SkCodec::kInternalError;
143         return nullptr;
144     }
145 
146     // Ignore XMP and Exif to ensure that avifDecoderParse() isn't waiting for
147     // some tiny Exif payload hiding at the end of a file.
148     avifDecoder->ignoreXMP = crabbyavif::CRABBY_AVIF_TRUE;
149     avifDecoder->ignoreExif = crabbyavif::CRABBY_AVIF_TRUE;
150 
151     // Disable strict mode. This allows some AVIF files in the wild that are
152     // technically invalid according to the specification because they were
153     // created with older tools but can be decoded and rendered without any
154     // issues.
155     avifDecoder->strictFlags = crabbyavif::AVIF_STRICT_DISABLED;
156 
157     // TODO(vigneshv): Enable threading based on number of CPU cores available.
158     avifDecoder->maxThreads = 1;
159 
160     if (gainmapOnly) {
161         avifDecoder->imageContentToDecode = crabbyavif::AVIF_IMAGE_CONTENT_GAIN_MAP;
162     }
163 
164     crabbyavif::avifResult res =
165             crabbyavif::avifDecoderSetIOMemory(avifDecoder.get(), data->bytes(), data->size());
166     if (res != crabbyavif::AVIF_RESULT_OK) {
167         *result = SkCodec::kInternalError;
168         return nullptr;
169     }
170 
171     res = crabbyavif::avifDecoderParse(avifDecoder.get());
172     if (res != crabbyavif::AVIF_RESULT_OK) {
173         *result = SkCodec::kInvalidInput;
174         return nullptr;
175     }
176 
177     std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr;
178     // TODO(vigneshv): Get ICC Profile from the avif decoder.
179 
180     // CrabbyAvif uses MediaCodec, which always sets bitsPerComponent to 8.
181     const int bitsPerComponent = 8;
182     SkEncodedInfo::Color color;
183     SkEncodedInfo::Alpha alpha;
184     if (avifDecoder->alphaPresent && !gainmapOnly) {
185         color = SkEncodedInfo::kRGBA_Color;
186         alpha = SkEncodedInfo::kUnpremul_Alpha;
187     } else {
188         color = SkEncodedInfo::kRGB_Color;
189         alpha = SkEncodedInfo::kOpaque_Alpha;
190     }
191     if (gainmapOnly && !avifDecoder->image->gainMap) {
192         *result = SkCodec::kInvalidInput;
193         return nullptr;
194     }
195     crabbyavif::avifImage* image =
196             gainmapOnly ? avifDecoder->image->gainMap->image : avifDecoder->image;
197     auto width = image->width;
198     auto height = image->height;
199     if (image->transformFlags & crabbyavif::AVIF_TRANSFORM_CLAP) {
200         crabbyavif::avifCropRect rect;
201         if (crabbyavif::crabby_avifCropRectConvertCleanApertureBox(
202                     &rect, &image->clap, width, height, image->yuvFormat, nullptr)) {
203             width = rect.width;
204             height = rect.height;
205         }
206     }
207     SkEncodedInfo info = SkEncodedInfo::Make(
208             width, height, color, alpha, bitsPerComponent, std::move(profile), image->depth);
209     bool animation = avifDecoder->imageCount > 1;
210     *result = kSuccess;
211     return std::unique_ptr<SkCodec>(new SkCrabbyAvifCodec(std::move(info),
212                                                           std::move(stream),
213                                                           std::move(data),
214                                                           std::move(avifDecoder),
215                                                           kDefault_SkEncodedOrigin,
216                                                           animation,
217                                                           gainmapOnly));
218 }
219 
SkCrabbyAvifCodec(SkEncodedInfo && info,std::unique_ptr<SkStream> stream,sk_sp<SkData> data,AvifDecoder avifDecoder,SkEncodedOrigin origin,bool useAnimation,bool gainmapOnly)220 SkCrabbyAvifCodec::SkCrabbyAvifCodec(SkEncodedInfo&& info,
221                                      std::unique_ptr<SkStream> stream,
222                                      sk_sp<SkData> data,
223                                      AvifDecoder avifDecoder,
224                                      SkEncodedOrigin origin,
225                                      bool useAnimation,
226                                      bool gainmapOnly)
227         : SkScalingCodec(std::move(info), skcms_PixelFormat_RGBA_8888, std::move(stream), origin)
228         , fData(std::move(data))
229         , fAvifDecoder(std::move(avifDecoder))
230         , fUseAnimation(useAnimation)
231         , fGainmapOnly(gainmapOnly) {}
232 
onGetFrameCount()233 int SkCrabbyAvifCodec::onGetFrameCount() {
234     if (!fUseAnimation) {
235         return 1;
236     }
237 
238     if (fFrameHolder.size() == 0) {
239         if (fAvifDecoder->imageCount <= 1) {
240             fUseAnimation = false;
241             return 1;
242         }
243         fFrameHolder.reserve(fAvifDecoder->imageCount);
244         for (int i = 0; i < fAvifDecoder->imageCount; i++) {
245             Frame* frame = fFrameHolder.appendNewFrame(fAvifDecoder->alphaPresent ==
246                                                        crabbyavif::CRABBY_AVIF_TRUE);
247             frame->setXYWH(0, 0, fAvifDecoder->image->width, fAvifDecoder->image->height);
248             frame->setDisposalMethod(SkCodecAnimation::DisposalMethod::kKeep);
249             crabbyavif::avifImageTiming timing;
250             avifDecoderNthImageTiming(fAvifDecoder.get(), i, &timing);
251             frame->setDuration(timing.duration * 1000);
252             frame->setRequiredFrame(SkCodec::kNoFrame);
253             frame->setHasAlpha(fAvifDecoder->alphaPresent == crabbyavif::CRABBY_AVIF_TRUE);
254         }
255     }
256 
257     return fFrameHolder.size();
258 }
259 
onGetFrame(int i) const260 const SkFrame* SkCrabbyAvifCodec::FrameHolder::onGetFrame(int i) const {
261     return static_cast<const SkFrame*>(this->frame(i));
262 }
263 
appendNewFrame(bool hasAlpha)264 SkCrabbyAvifCodec::Frame* SkCrabbyAvifCodec::FrameHolder::appendNewFrame(bool hasAlpha) {
265     const int i = this->size();
266     fFrames.emplace_back(i,
267                          hasAlpha ? SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha);
268     return &fFrames[i];
269 }
270 
frame(int i) const271 const SkCrabbyAvifCodec::Frame* SkCrabbyAvifCodec::FrameHolder::frame(int i) const {
272     SkASSERT(i >= 0 && i < this->size());
273     return &fFrames[i];
274 }
275 
onGetFrameInfo(int i,FrameInfo * frameInfo) const276 bool SkCrabbyAvifCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {
277     if (i >= fFrameHolder.size()) {
278         return false;
279     }
280 
281     const Frame* frame = fFrameHolder.frame(i);
282     if (!frame) {
283         return false;
284     }
285 
286     if (frameInfo) {
287         frame->fillIn(frameInfo, true);
288     }
289 
290     return true;
291 }
292 
onGetRepetitionCount()293 int SkCrabbyAvifCodec::onGetRepetitionCount() { return kRepetitionCountInfinite; }
294 
conversionSupported(const SkImageInfo & dstInfo,bool srcIsOpaque,bool needsColorXform)295 bool SkCrabbyAvifCodec::conversionSupported(const SkImageInfo& dstInfo,
296                                             bool srcIsOpaque,
297                                             bool needsColorXform) {
298     return dstInfo.colorType() == kRGBA_8888_SkColorType ||
299            dstInfo.colorType() == kRGBA_1010102_SkColorType ||
300            dstInfo.colorType() == kRGBA_F16_SkColorType ||
301            dstInfo.colorType() == kRGB_565_SkColorType;
302 }
303 
onGetPixels(const SkImageInfo & dstInfo,void * dst,size_t dstRowBytes,const Options & options,int * rowsDecoded)304 SkCodec::Result SkCrabbyAvifCodec::onGetPixels(const SkImageInfo& dstInfo,
305                                                void* dst,
306                                                size_t dstRowBytes,
307                                                const Options& options,
308                                                int* rowsDecoded) {
309     if (options.fSubset) {
310         return kUnimplemented;
311     }
312 
313     switch (dstInfo.colorType()) {
314         case kRGBA_8888_SkColorType:
315         case kRGB_565_SkColorType:
316             fAvifDecoder->androidMediaCodecOutputColorFormat =
317                     crabbyavif::ANDROID_MEDIA_CODEC_OUTPUT_COLOR_FORMAT_YUV420_FLEXIBLE;
318             break;
319         case kRGBA_F16_SkColorType:
320         case kRGBA_1010102_SkColorType:
321             fAvifDecoder->androidMediaCodecOutputColorFormat =
322                     crabbyavif::ANDROID_MEDIA_CODEC_OUTPUT_COLOR_FORMAT_P010;
323             break;
324         default:
325             return kUnimplemented;
326     }
327 
328     crabbyavif::avifResult result =
329             crabbyavif::avifDecoderNthImage(fAvifDecoder.get(), options.fFrameIndex);
330     if (result != crabbyavif::AVIF_RESULT_OK) {
331         return kInvalidInput;
332     }
333     if (fGainmapOnly && !fAvifDecoder->image->gainMap) {
334         return kInvalidInput;
335     }
336     crabbyavif::avifImage* image =
337             fGainmapOnly ? fAvifDecoder->image->gainMap->image : fAvifDecoder->image;
338     using AvifImagePtr =
339             std::unique_ptr<crabbyavif::avifImage, decltype(&crabbyavif::crabby_avifImageDestroy)>;
340 
341     AvifImagePtr scaled_image{nullptr, crabbyavif::crabby_avifImageDestroy};
342     if (this->dimensions() != dstInfo.dimensions()) {
343         // |image| contains plane pointers which point to Android MediaCodec's buffers. Those
344         // buffers are read-only and hence we cannot scale in place. Make a copy of the image and
345         // scale the copied image.
346         scaled_image.reset(crabbyavif::crabby_avifImageCreateEmpty());
347         result = crabbyavif::crabby_avifImageCopy(
348             scaled_image.get(), image, crabbyavif::AVIF_PLANES_ALL);
349         if (result != crabbyavif::AVIF_RESULT_OK) {
350             return kInvalidInput;
351         }
352         image = scaled_image.get();
353         result = crabbyavif::avifImageScale(
354                 image, dstInfo.width(), dstInfo.height(), &fAvifDecoder->diag);
355         if (result != crabbyavif::AVIF_RESULT_OK) {
356             return kInvalidInput;
357         }
358     }
359 
360     // cropped_image is a view into the underlying image. It can be safely deleted once the pixels
361     // are converted into RGB (or when it goes out of scope in one of the error paths).
362     AvifImagePtr cropped_image{nullptr, crabbyavif::crabby_avifImageDestroy};
363     if (image->transformFlags & crabbyavif::AVIF_TRANSFORM_CLAP) {
364         crabbyavif::avifCropRect rect;
365         if (crabbyavif::crabby_avifCropRectConvertCleanApertureBox(
366                     &rect, &image->clap, image->width, image->height, image->yuvFormat, nullptr)) {
367             cropped_image.reset(crabbyavif::crabby_avifImageCreateEmpty());
368             result = crabbyavif::crabby_avifImageSetViewRect(cropped_image.get(), image, &rect);
369             if (result != crabbyavif::AVIF_RESULT_OK) {
370                 return kInvalidInput;
371             }
372             image = cropped_image.get();
373         }
374     }
375 
376     crabbyavif::avifRGBImage rgbImage;
377     crabbyavif::avifRGBImageSetDefaults(&rgbImage, image);
378 
379     switch (dstInfo.colorType()) {
380         case kRGBA_8888_SkColorType:
381             rgbImage.depth = 8;
382             break;
383         case kRGBA_F16_SkColorType:
384             rgbImage.depth = 16;
385             rgbImage.isFloat = crabbyavif::CRABBY_AVIF_TRUE;
386             break;
387         case kRGBA_1010102_SkColorType:
388             rgbImage.depth = 10;
389             rgbImage.format = crabbyavif::AVIF_RGB_FORMAT_RGBA1010102;
390             break;
391         case kRGB_565_SkColorType:
392             rgbImage.depth = 8;
393             rgbImage.format = crabbyavif::AVIF_RGB_FORMAT_RGB565;
394             break;
395         default:
396             // TODO(vigneshv): Check if more color types need to be supported.
397             // Currently android supports at least RGB565 and BGRA8888 which is
398             // not supported here.
399             return kUnimplemented;
400     }
401 
402     rgbImage.pixels = static_cast<uint8_t*>(dst);
403     rgbImage.rowBytes = dstRowBytes;
404     rgbImage.chromaUpsampling = crabbyavif::AVIF_CHROMA_UPSAMPLING_FASTEST;
405 
406     result = crabbyavif::avifImageYUVToRGB(image, &rgbImage);
407     if (result != crabbyavif::AVIF_RESULT_OK) {
408         return kInvalidInput;
409     }
410 
411     *rowsDecoded = image->height;
412     return kSuccess;
413 }
414 
onGetGainmapCodec(SkGainmapInfo * info,std::unique_ptr<SkCodec> * gainmapCodec)415 bool SkCrabbyAvifCodec::onGetGainmapCodec(SkGainmapInfo* info,
416                                           std::unique_ptr<SkCodec>* gainmapCodec) {
417     if (!gainmapCodec || !info || !fAvifDecoder->image || !fAvifDecoder->image->gainMap ||
418         !PopulateGainmapInfo(*fAvifDecoder->image->gainMap, *fAvifDecoder->image, info)) {
419         return false;
420     }
421     Result result;
422     *gainmapCodec = SkCrabbyAvifCodec::MakeFromData(
423             /*stream=*/nullptr, fData, &result, /*gainmapOnly=*/true);
424     return static_cast<bool>(*gainmapCodec);
425 }
426 
427 namespace SkAvifDecoder {
428 namespace CrabbyAvif {
429 
IsAvif(const void * data,size_t len)430 bool IsAvif(const void* data, size_t len) { return SkCrabbyAvifCodec::IsAvif(data, len); }
431 
Decode(std::unique_ptr<SkStream> stream,SkCodec::Result * outResult,SkCodecs::DecodeContext)432 std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream,
433                                 SkCodec::Result* outResult,
434                                 SkCodecs::DecodeContext) {
435     SkCodec::Result resultStorage;
436     if (!outResult) {
437         outResult = &resultStorage;
438     }
439     return SkCrabbyAvifCodec::MakeFromStream(std::move(stream), outResult);
440 }
441 
Decode(sk_sp<SkData> data,SkCodec::Result * outResult,SkCodecs::DecodeContext)442 std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data,
443                                 SkCodec::Result* outResult,
444                                 SkCodecs::DecodeContext) {
445     if (!data) {
446         if (outResult) {
447             *outResult = SkCodec::kInvalidInput;
448         }
449         return nullptr;
450     }
451     return Decode(SkMemoryStream::Make(std::move(data)), outResult, nullptr);
452 }
453 
454 }  // namespace CrabbyAvif
455 }  // namespace SkAvifDecoder
456