xref: /haiku/src/add-ons/translators/avif/AVIFTranslator.cpp (revision 3d4afef9cba2f328e238089d4609d00d4b1524f3)
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:
34 		FreeAllocation(void* buffer)
35 			:
36 			fBuffer(buffer)
37 		{
38 		}
39 
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 
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 
128 AVIFTranslator::~AVIFTranslator()
129 {
130 }
131 
132 
133 status_t
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
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*
189 AVIFTranslator::NewConfigView(TranslatorSettings* settings)
190 {
191 	return new ConfigView(settings);
192 }
193 
194 
195 status_t
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
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*
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