1 /*
2 * Copyright 2021, Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Emmanuel Gil Peyrot
7 */
8
9
10 #include "AVIFTranslator.h"
11
12 #include <BufferIO.h>
13 #include <Catalog.h>
14 #include <Messenger.h>
15 #include <TranslatorRoster.h>
16
17 #include <assert.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include "avif/avif.h"
23
24 #include "ConfigView.h"
25 #include "TranslatorSettings.h"
26
27
28 #undef B_TRANSLATION_CONTEXT
29 #define B_TRANSLATION_CONTEXT "AVIFTranslator"
30
31
32 class FreeAllocation {
33 public:
FreeAllocation(void * buffer)34 FreeAllocation(void* buffer)
35 :
36 fBuffer(buffer)
37 {
38 }
39
~FreeAllocation()40 ~FreeAllocation()
41 {
42 free(fBuffer);
43 }
44
45 private:
46 void* fBuffer;
47 };
48
49
50 // The input formats that this translator knows how to read
51 static const translation_format sInputFormats[] = {
52 {
53 AVIF_IMAGE_FORMAT,
54 B_TRANSLATOR_BITMAP,
55 AVIF_IN_QUALITY,
56 AVIF_IN_CAPABILITY,
57 "image/avif",
58 "AV1 Image File Format"
59 },
60 {
61 B_TRANSLATOR_BITMAP,
62 B_TRANSLATOR_BITMAP,
63 BITS_IN_QUALITY,
64 BITS_IN_CAPABILITY,
65 "image/x-be-bitmap",
66 "Be Bitmap Format (AVIFTranslator)"
67 },
68 };
69
70
71 // The output formats that this translator knows how to write
72 static const translation_format sOutputFormats[] = {
73 {
74 AVIF_IMAGE_FORMAT,
75 B_TRANSLATOR_BITMAP,
76 AVIF_OUT_QUALITY,
77 AVIF_OUT_CAPABILITY,
78 "image/avif",
79 "AV1 Image File Format"
80 },
81 {
82 B_TRANSLATOR_BITMAP,
83 B_TRANSLATOR_BITMAP,
84 BITS_OUT_QUALITY,
85 BITS_OUT_CAPABILITY,
86 "image/x-be-bitmap",
87 "Be Bitmap Format (AVIFTranslator)"
88 },
89 };
90
91 // Default settings for the Translator
92 static const TranSetting sDefaultSettings[] = {
93 { B_TRANSLATOR_EXT_HEADER_ONLY, TRAN_SETTING_BOOL, false },
94 { B_TRANSLATOR_EXT_DATA_ONLY, TRAN_SETTING_BOOL, false },
95 { AVIF_SETTING_LOSSLESS, TRAN_SETTING_BOOL, false },
96 { AVIF_SETTING_PIXEL_FORMAT, TRAN_SETTING_INT32,
97 AVIF_PIXEL_FORMAT_YUV444 },
98 { AVIF_SETTING_QUALITY, TRAN_SETTING_INT32, 60 },
99 { AVIF_SETTING_SPEED, TRAN_SETTING_INT32, -1 },
100 { AVIF_SETTING_TILES_HORIZONTAL, TRAN_SETTING_INT32, 1 },
101 { AVIF_SETTING_TILES_VERTICAL, TRAN_SETTING_INT32, 1 },
102 };
103
104 const uint32 kNumInputFormats = sizeof(sInputFormats) /
105 sizeof(translation_format);
106 const uint32 kNumOutputFormats = sizeof(sOutputFormats) /
107 sizeof(translation_format);
108 const uint32 kNumDefaultSettings = sizeof(sDefaultSettings) /
109 sizeof(TranSetting);
110
111
112 // #pragma mark -
113
114
AVIFTranslator()115 AVIFTranslator::AVIFTranslator()
116 :
117 BaseTranslator(B_TRANSLATE("AVIF images"),
118 B_TRANSLATE("AVIF image translator"),
119 AVIF_TRANSLATOR_VERSION,
120 sInputFormats, kNumInputFormats,
121 sOutputFormats, kNumOutputFormats,
122 "AVIFTranslator_Settings", sDefaultSettings,
123 kNumDefaultSettings, B_TRANSLATOR_BITMAP, AVIF_IMAGE_FORMAT)
124 {
125 }
126
127
~AVIFTranslator()128 AVIFTranslator::~AVIFTranslator()
129 {
130 }
131
132
133 status_t
DerivedIdentify(BPositionIO * stream,const translation_format * format,BMessage * settings,translator_info * info,uint32 outType)134 AVIFTranslator::DerivedIdentify(BPositionIO* stream,
135 const translation_format* format, BMessage* settings,
136 translator_info* info, uint32 outType)
137 {
138 (void)format;
139 (void)settings;
140 if (!outType)
141 outType = B_TRANSLATOR_BITMAP;
142 if (outType != B_TRANSLATOR_BITMAP)
143 return B_NO_TRANSLATOR;
144
145 // Read header and first chunck bytes...
146 uint32 buf[64];
147 ssize_t size = sizeof(buf);
148 if (stream->Read(buf, size) != size)
149 return B_IO_ERROR;
150
151 // Check it's a valid AVIF format
152 avifROData data;
153 data.data = reinterpret_cast<const uint8_t*>(buf);
154 data.size = static_cast<size_t>(size);
155 if (!avifPeekCompatibleFileType(&data))
156 return B_ILLEGAL_DATA;
157
158 info->type = AVIF_IMAGE_FORMAT;
159 info->group = B_TRANSLATOR_BITMAP;
160 info->quality = AVIF_IN_QUALITY;
161 info->capability = AVIF_IN_CAPABILITY;
162 snprintf(info->name, sizeof(info->name), B_TRANSLATE("AVIF image"));
163 strcpy(info->MIME, "image/avif");
164
165 return B_OK;
166 }
167
168
169 status_t
DerivedTranslate(BPositionIO * stream,const translator_info * info,BMessage * ioExtension,uint32 outType,BPositionIO * target,int32 baseType)170 AVIFTranslator::DerivedTranslate(BPositionIO* stream,
171 const translator_info* info, BMessage* ioExtension, uint32 outType,
172 BPositionIO* target, int32 baseType)
173 {
174 (void)info;
175 if (baseType == 1)
176 // if stream is in bits format
177 return _TranslateFromBits(stream, ioExtension, outType, target);
178 else if (baseType == 0)
179 // if stream is NOT in bits format
180 return _TranslateFromAVIF(stream, ioExtension, outType, target);
181 else
182 // if BaseTranslator dit not properly identify the data as
183 // bits or not bits
184 return B_NO_TRANSLATOR;
185 }
186
187
188 BView*
NewConfigView(TranslatorSettings * settings)189 AVIFTranslator::NewConfigView(TranslatorSettings* settings)
190 {
191 return new ConfigView(settings);
192 }
193
194
195 status_t
_TranslateFromBits(BPositionIO * stream,BMessage * ioExtension,uint32 outType,BPositionIO * target)196 AVIFTranslator::_TranslateFromBits(BPositionIO* stream, BMessage* ioExtension,
197 uint32 outType, BPositionIO* target)
198 {
199 // FIXME: This codepath is completely untested for now, due to libavif
200 // being built without any encoder in haikuports.
201
202 (void)ioExtension;
203 if (!outType)
204 outType = AVIF_IMAGE_FORMAT;
205 if (outType != AVIF_IMAGE_FORMAT)
206 return B_NO_TRANSLATOR;
207
208 TranslatorBitmap bitsHeader;
209 status_t status;
210
211 status = identify_bits_header(stream, NULL, &bitsHeader);
212 if (status != B_OK)
213 return status;
214
215 avifPixelFormat format = static_cast<avifPixelFormat>(
216 fSettings->SetGetInt32(AVIF_SETTING_PIXEL_FORMAT));
217 int32 bytesPerPixel;
218 avifRGBFormat rgbFormat;
219 bool isRGB = true;
220 bool ignoreAlpha = false;
221 switch (bitsHeader.colors) {
222 case B_RGB32:
223 rgbFormat = AVIF_RGB_FORMAT_BGRA;
224 ignoreAlpha = true;
225 bytesPerPixel = 4;
226 break;
227
228 case B_RGB32_BIG:
229 rgbFormat = AVIF_RGB_FORMAT_ARGB;
230 ignoreAlpha = true;
231 bytesPerPixel = 4;
232 break;
233
234 case B_RGBA32:
235 rgbFormat = AVIF_RGB_FORMAT_BGRA;
236 bytesPerPixel = 4;
237 break;
238
239 case B_RGBA32_BIG:
240 rgbFormat = AVIF_RGB_FORMAT_ARGB;
241 bytesPerPixel = 4;
242 break;
243
244 case B_RGB24:
245 rgbFormat = AVIF_RGB_FORMAT_BGR;
246 bytesPerPixel = 3;
247 break;
248
249 case B_RGB24_BIG:
250 rgbFormat = AVIF_RGB_FORMAT_RGB;
251 bytesPerPixel = 3;
252 break;
253
254 case B_YCbCr444:
255 bytesPerPixel = 3;
256 isRGB = false;
257 break;
258
259 case B_GRAY8:
260 bytesPerPixel = 1;
261 isRGB = false;
262 break;
263
264 default:
265 printf("ERROR: Colorspace not supported: %d\n",
266 bitsHeader.colors);
267 return B_NO_TRANSLATOR;
268 }
269
270 int width = bitsHeader.bounds.IntegerWidth() + 1;
271 int height = bitsHeader.bounds.IntegerHeight() + 1;
272 int depth = 8;
273
274 avifImage* image = avifImageCreate(width, height, depth, format);
275 image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
276 image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
277 image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;
278
279 if (isRGB) {
280 image->yuvRange = AVIF_RANGE_FULL;
281
282 avifRGBImage rgb;
283 avifRGBImageSetDefaults(&rgb, image);
284 rgb.depth = depth;
285 rgb.format = rgbFormat;
286 rgb.ignoreAlpha = ignoreAlpha;
287 int bitsSize = height * bitsHeader.rowBytes;
288 rgb.pixels = static_cast<uint8_t*>(malloc(bitsSize));
289 if (rgb.pixels == NULL)
290 return B_NO_MEMORY;
291 rgb.rowBytes = bitsHeader.rowBytes;
292
293 if (stream->Read(rgb.pixels, bitsSize) != bitsSize) {
294 free(rgb.pixels);
295 return B_IO_ERROR;
296 }
297
298 avifResult conversionResult = avifImageRGBToYUV(image, &rgb);
299 free(rgb.pixels);
300 if (conversionResult != AVIF_RESULT_OK)
301 return B_ERROR;
302 } else if (bytesPerPixel == 3) {
303 // TODO: Investigate moving that to libavif instead, and do so
304 // for other Y'CbCr formats too.
305 //
306 // See also https://github.com/AOMediaCodec/libavif/pull/235
307 assert(bitsHeader.colors == B_YCbCr444);
308 int bitsSize = height * bitsHeader.rowBytes;
309 uint8_t* pixels = static_cast<uint8_t*>(malloc(bitsSize));
310 if (stream->Read(pixels, bitsSize) != bitsSize)
311 return B_IO_ERROR;
312
313 uint8_t* luma = static_cast<uint8_t*>(malloc(bitsSize / 3));
314 uint8_t* cb = static_cast<uint8_t*>(malloc(bitsSize / 3));
315 uint8_t* cr = static_cast<uint8_t*>(malloc(bitsSize / 3));
316
317 for (int i = 0; i < bitsSize / 3; ++i) {
318 luma[i] = pixels[3 * i + 0];
319 cb[i] = pixels[3 * i + 1];
320 cr[i] = pixels[3 * i + 2];
321 }
322
323 image->yuvPlanes[0] = luma;
324 image->yuvPlanes[1] = cb;
325 image->yuvPlanes[2] = cr;
326
327 image->yuvRowBytes[0] = bitsHeader.rowBytes / 3;
328 image->yuvRowBytes[1] = bitsHeader.rowBytes / 3;
329 image->yuvRowBytes[2] = bitsHeader.rowBytes / 3;
330
331 image->yuvRange = AVIF_RANGE_LIMITED;
332 } else {
333 assert(bitsHeader.colors == B_GRAY8);
334 int bitsSize = height * bitsHeader.rowBytes;
335 uint8_t* luma = static_cast<uint8_t*>(malloc(bitsSize));
336 if (stream->Read(luma, bitsSize) != bitsSize)
337 return B_IO_ERROR;
338
339 image->yuvPlanes[0] = luma;
340 image->yuvPlanes[1] = nullptr;
341 image->yuvPlanes[2] = nullptr;
342
343 image->yuvRowBytes[0] = bitsHeader.rowBytes;
344 image->yuvRowBytes[1] = 0;
345 image->yuvRowBytes[2] = 0;
346 }
347
348 avifRWData output = AVIF_DATA_EMPTY;
349 avifEncoder* encoder = avifEncoderCreate();
350
351 system_info info;
352 encoder->maxThreads = (get_system_info(&info) == B_OK) ?
353 info.cpu_count : 1;
354
355 if (fSettings->SetGetBool(AVIF_SETTING_LOSSLESS)) {
356 encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
357 encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
358 } else {
359 encoder->minQuantizer = encoder->maxQuantizer
360 = fSettings->SetGetInt32(AVIF_SETTING_QUALITY);
361 }
362 encoder->speed = fSettings->SetGetInt32(AVIF_SETTING_SPEED);
363 encoder->tileColsLog2
364 = fSettings->SetGetInt32(AVIF_SETTING_TILES_HORIZONTAL);
365 encoder->tileRowsLog2
366 = fSettings->SetGetInt32(AVIF_SETTING_TILES_VERTICAL);
367
368 avifResult encodeResult = avifEncoderWrite(encoder, image, &output);
369 avifImageDestroy(image);
370 avifEncoderDestroy(encoder);
371
372 if (encodeResult != AVIF_RESULT_OK) {
373 printf("ERROR: Failed to encode: %s\n",
374 avifResultToString(encodeResult));
375 avifRWDataFree(&output);
376 return B_ERROR;
377 }
378
379 // output contains a valid .avif file's contents
380 target->Write(output.data, output.size);
381 avifRWDataFree(&output);
382 return B_OK;
383 }
384
385
386 status_t
_TranslateFromAVIF(BPositionIO * stream,BMessage * ioExtension,uint32 outType,BPositionIO * target)387 AVIFTranslator::_TranslateFromAVIF(BPositionIO* stream, BMessage* ioExtension,
388 uint32 outType, BPositionIO* target)
389 {
390 if (!outType)
391 outType = B_TRANSLATOR_BITMAP;
392 if (outType != B_TRANSLATOR_BITMAP)
393 return B_NO_TRANSLATOR;
394
395 off_t streamLength = 0;
396 stream->GetSize(&streamLength);
397
398 off_t streamSize = stream->Seek(0, SEEK_END);
399 stream->Seek(0, SEEK_SET);
400
401 void* streamData = malloc(streamSize);
402 if (streamData == NULL)
403 return B_NO_MEMORY;
404
405 if (stream->Read(streamData, streamSize) != streamSize) {
406 free(streamData);
407 return B_IO_ERROR;
408 }
409
410 avifDecoder* decoder = avifDecoderCreate();
411 if (decoder == NULL) {
412 free(streamData);
413 return B_NO_MEMORY;
414 }
415
416 avifResult setIOMemoryResult = avifDecoderSetIOMemory(decoder,
417 reinterpret_cast<const uint8_t *>(streamData), streamSize);
418 if (setIOMemoryResult != AVIF_RESULT_OK) {
419 free(streamData);
420 return B_NO_MEMORY;
421 }
422
423 avifResult decodeResult = avifDecoderParse(decoder);
424 if (decodeResult != AVIF_RESULT_OK) {
425 free(streamData);
426 return B_ILLEGAL_DATA;
427 }
428
429 // We don’t support animations yet.
430 if (decoder->imageCount != 1) {
431 free(streamData);
432 return B_ILLEGAL_DATA;
433 }
434
435 avifResult nextImageResult = avifDecoderNextImage(decoder);
436 free(streamData);
437 if (nextImageResult != AVIF_RESULT_OK)
438 return B_ILLEGAL_DATA;
439
440 avifImage* image = decoder->image;
441 int width = image->width;
442 int height = image->height;
443 avifRGBFormat format;
444 uint8_t* pixels;
445 uint32_t rowBytes;
446 color_space colors;
447
448 bool convertToRGB = true;
449 if (image->alphaPlane) {
450 format = AVIF_RGB_FORMAT_BGRA;
451 colors = B_RGBA32;
452 } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
453 colors = B_GRAY8;
454 convertToRGB = false;
455 } else {
456 format = AVIF_RGB_FORMAT_BGR;
457 colors = B_RGB24;
458 }
459
460 if (convertToRGB) {
461 avifRGBImage rgb;
462 avifRGBImageSetDefaults(&rgb, image);
463 rgb.depth = 8;
464 rgb.format = format;
465
466 avifRGBImageAllocatePixels(&rgb);
467 avifResult conversionResult = avifImageYUVToRGB(image, &rgb);
468 if (conversionResult != AVIF_RESULT_OK)
469 return B_ILLEGAL_DATA;
470
471 pixels = rgb.pixels;
472 rowBytes = rgb.rowBytes;
473 } else {
474 // TODO: Add a downsampling (with dithering?) path here, or
475 // alternatively add support for higher bit depth to Haiku
476 // bitmaps, possibly with HDR too.
477 if (image->depth > 8)
478 return B_ILLEGAL_DATA;
479
480 // TODO: Add support for more than just the luma plane.
481 pixels = image->yuvPlanes[0];
482 rowBytes = image->yuvRowBytes[0];
483 }
484
485 uint32 dataSize = rowBytes * height;
486
487 TranslatorBitmap bitmapHeader;
488 bitmapHeader.magic = B_TRANSLATOR_BITMAP;
489 bitmapHeader.bounds.Set(0, 0, width - 1, height - 1);
490 bitmapHeader.rowBytes = rowBytes;
491 bitmapHeader.colors = colors;
492 bitmapHeader.dataSize = dataSize;
493
494 // write out Be's Bitmap header
495 swap_data(B_UINT32_TYPE, &bitmapHeader, sizeof(TranslatorBitmap),
496 B_SWAP_HOST_TO_BENDIAN);
497 ssize_t bytesWritten = target->Write(&bitmapHeader,
498 sizeof(TranslatorBitmap));
499 if (bytesWritten < B_OK)
500 return bytesWritten;
501
502 if ((size_t)bytesWritten != sizeof(TranslatorBitmap))
503 return B_IO_ERROR;
504
505 bool headerOnly = false;
506 if (ioExtension != NULL)
507 ioExtension->FindBool(B_TRANSLATOR_EXT_HEADER_ONLY,
508 &headerOnly);
509
510 if (headerOnly)
511 return B_OK;
512
513 bytesWritten = target->Write(pixels, dataSize);
514 if (bytesWritten < B_OK)
515 return bytesWritten;
516 return B_OK;
517 }
518
519
520 // #pragma mark -
521
522
523 BTranslator*
make_nth_translator(int32 n,image_id you,uint32 flags,...)524 make_nth_translator(int32 n, image_id you, uint32 flags, ...)
525 {
526 (void)you;
527 (void)flags;
528 if (n != 0)
529 return NULL;
530
531 return new AVIFTranslator();
532 }
533