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