root/src/add-ons/translators/avif/AVIFTranslator.cpp
/*
 * Copyright 2021, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Emmanuel Gil Peyrot
 */


#include "AVIFTranslator.h"

#include <BufferIO.h>
#include <Catalog.h>
#include <Messenger.h>
#include <TranslatorRoster.h>

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "avif/avif.h"

#include "ConfigView.h"
#include "TranslatorSettings.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AVIFTranslator"


class FreeAllocation {
        public:
                FreeAllocation(void* buffer)
                        :
                        fBuffer(buffer)
                {
                }

                ~FreeAllocation()
                {
                        free(fBuffer);
                }

        private:
                void*   fBuffer;
};


// The input formats that this translator knows how to read
static const translation_format sInputFormats[] = {
        {
                AVIF_IMAGE_FORMAT,
                B_TRANSLATOR_BITMAP,
                AVIF_IN_QUALITY,
                AVIF_IN_CAPABILITY,
                "image/avif",
                "AV1 Image File Format"
        },
        {
                B_TRANSLATOR_BITMAP,
                B_TRANSLATOR_BITMAP,
                BITS_IN_QUALITY,
                BITS_IN_CAPABILITY,
                "image/x-be-bitmap",
                "Be Bitmap Format (AVIFTranslator)"
        },
};


// The output formats that this translator knows how to write
static const translation_format sOutputFormats[] = {
        {
                AVIF_IMAGE_FORMAT,
                B_TRANSLATOR_BITMAP,
                AVIF_OUT_QUALITY,
                AVIF_OUT_CAPABILITY,
                "image/avif",
                "AV1 Image File Format"
        },
        {
                B_TRANSLATOR_BITMAP,
                B_TRANSLATOR_BITMAP,
                BITS_OUT_QUALITY,
                BITS_OUT_CAPABILITY,
                "image/x-be-bitmap",
                "Be Bitmap Format (AVIFTranslator)"
        },
};

// Default settings for the Translator
static const TranSetting sDefaultSettings[] = {
        { B_TRANSLATOR_EXT_HEADER_ONLY, TRAN_SETTING_BOOL, false },
        { B_TRANSLATOR_EXT_DATA_ONLY, TRAN_SETTING_BOOL, false },
        { AVIF_SETTING_LOSSLESS, TRAN_SETTING_BOOL, false },
        { AVIF_SETTING_PIXEL_FORMAT, TRAN_SETTING_INT32,
                AVIF_PIXEL_FORMAT_YUV444 },
        { AVIF_SETTING_QUALITY, TRAN_SETTING_INT32, 60 },
        { AVIF_SETTING_SPEED, TRAN_SETTING_INT32, -1 },
        { AVIF_SETTING_TILES_HORIZONTAL, TRAN_SETTING_INT32, 1 },
        { AVIF_SETTING_TILES_VERTICAL, TRAN_SETTING_INT32, 1 },
};

const uint32 kNumInputFormats = sizeof(sInputFormats) /
        sizeof(translation_format);
const uint32 kNumOutputFormats = sizeof(sOutputFormats) /
        sizeof(translation_format);
const uint32 kNumDefaultSettings = sizeof(sDefaultSettings) /
        sizeof(TranSetting);


//      #pragma mark -


AVIFTranslator::AVIFTranslator()
        :
        BaseTranslator(B_TRANSLATE("AVIF images"),
                B_TRANSLATE("AVIF image translator"),
                AVIF_TRANSLATOR_VERSION,
                sInputFormats, kNumInputFormats,
                sOutputFormats, kNumOutputFormats,
                "AVIFTranslator_Settings", sDefaultSettings,
                kNumDefaultSettings, B_TRANSLATOR_BITMAP, AVIF_IMAGE_FORMAT)
{
}


AVIFTranslator::~AVIFTranslator()
{
}


status_t
AVIFTranslator::DerivedIdentify(BPositionIO* stream,
        const translation_format* format, BMessage* settings,
        translator_info* info, uint32 outType)
{
        (void)format;
        (void)settings;
        if (!outType)
                outType = B_TRANSLATOR_BITMAP;
        if (outType != B_TRANSLATOR_BITMAP)
                return B_NO_TRANSLATOR;

        // Read header and first chunck bytes...
        uint32 buf[64];
        ssize_t size = sizeof(buf);
        if (stream->Read(buf, size) != size)
                return B_IO_ERROR;

        // Check it's a valid AVIF format
        avifROData data;
        data.data = reinterpret_cast<const uint8_t*>(buf);
        data.size = static_cast<size_t>(size);
        if (!avifPeekCompatibleFileType(&data))
                return B_ILLEGAL_DATA;

        info->type = AVIF_IMAGE_FORMAT;
        info->group = B_TRANSLATOR_BITMAP;
        info->quality = AVIF_IN_QUALITY;
        info->capability = AVIF_IN_CAPABILITY;
        snprintf(info->name, sizeof(info->name), B_TRANSLATE("AVIF image"));
        strcpy(info->MIME, "image/avif");

        return B_OK;
}


status_t
AVIFTranslator::DerivedTranslate(BPositionIO* stream,
        const translator_info* info, BMessage* ioExtension, uint32 outType,
        BPositionIO* target, int32 baseType)
{
        (void)info;
        if (baseType == 1)
                // if stream is in bits format
                return _TranslateFromBits(stream, ioExtension, outType, target);
        else if (baseType == 0)
                // if stream is NOT in bits format
                return _TranslateFromAVIF(stream, ioExtension, outType, target);
        else
                // if BaseTranslator dit not properly identify the data as
                // bits or not bits
                return B_NO_TRANSLATOR;
}


BView*
AVIFTranslator::NewConfigView(TranslatorSettings* settings)
{
        return new ConfigView(settings);
}


status_t
AVIFTranslator::_TranslateFromBits(BPositionIO* stream, BMessage* ioExtension,
        uint32 outType, BPositionIO* target)
{
        // FIXME: This codepath is completely untested for now, due to libavif
        // being built without any encoder in haikuports.

        (void)ioExtension;
        if (!outType)
                outType = AVIF_IMAGE_FORMAT;
        if (outType != AVIF_IMAGE_FORMAT)
                return B_NO_TRANSLATOR;

        TranslatorBitmap bitsHeader;
        status_t status;

        status = identify_bits_header(stream, NULL, &bitsHeader);
        if (status != B_OK)
                return status;

        avifPixelFormat format = static_cast<avifPixelFormat>(
                fSettings->SetGetInt32(AVIF_SETTING_PIXEL_FORMAT));
        int32 bytesPerPixel;
        avifRGBFormat rgbFormat;
        bool isRGB = true;
        bool ignoreAlpha = false;
        switch (bitsHeader.colors) {
                case B_RGB32:
                        rgbFormat = AVIF_RGB_FORMAT_BGRA;
                        ignoreAlpha = true;
                        bytesPerPixel = 4;
                        break;

                case B_RGB32_BIG:
                        rgbFormat = AVIF_RGB_FORMAT_ARGB;
                        ignoreAlpha = true;
                        bytesPerPixel = 4;
                        break;

                case B_RGBA32:
                        rgbFormat = AVIF_RGB_FORMAT_BGRA;
                        bytesPerPixel = 4;
                        break;

                case B_RGBA32_BIG:
                        rgbFormat = AVIF_RGB_FORMAT_ARGB;
                        bytesPerPixel = 4;
                        break;

                case B_RGB24:
                        rgbFormat = AVIF_RGB_FORMAT_BGR;
                        bytesPerPixel = 3;
                        break;

                case B_RGB24_BIG:
                        rgbFormat = AVIF_RGB_FORMAT_RGB;
                        bytesPerPixel = 3;
                        break;

                case B_YCbCr444:
                        bytesPerPixel = 3;
                        isRGB = false;
                        break;

                case B_GRAY8:
                        bytesPerPixel = 1;
                        isRGB = false;
                        break;

                default:
                        printf("ERROR: Colorspace not supported: %d\n",
                                bitsHeader.colors);
                        return B_NO_TRANSLATOR;
        }

        int width = bitsHeader.bounds.IntegerWidth() + 1;
        int height = bitsHeader.bounds.IntegerHeight() + 1;
        int depth = 8;

        avifImage* image = avifImageCreate(width, height, depth, format);
        image->colorPrimaries = AVIF_COLOR_PRIMARIES_BT709;
        image->transferCharacteristics = AVIF_TRANSFER_CHARACTERISTICS_SRGB;
        image->matrixCoefficients = AVIF_MATRIX_COEFFICIENTS_IDENTITY;

        if (isRGB) {
                image->yuvRange = AVIF_RANGE_FULL;

                avifRGBImage rgb;
                avifRGBImageSetDefaults(&rgb, image);
                rgb.depth = depth;
                rgb.format = rgbFormat;
                rgb.ignoreAlpha = ignoreAlpha;
                int bitsSize = height * bitsHeader.rowBytes;
                rgb.pixels = static_cast<uint8_t*>(malloc(bitsSize));
                if (rgb.pixels == NULL)
                        return B_NO_MEMORY;
                rgb.rowBytes = bitsHeader.rowBytes;

                if (stream->Read(rgb.pixels, bitsSize) != bitsSize) {
                        free(rgb.pixels);
                        return B_IO_ERROR;
                }

                avifResult conversionResult = avifImageRGBToYUV(image, &rgb);
                free(rgb.pixels);
                if (conversionResult != AVIF_RESULT_OK)
                        return B_ERROR;
        } else if (bytesPerPixel == 3) {
                // TODO: Investigate moving that to libavif instead, and do so
                // for other Y'CbCr formats too.
                //
                // See also https://github.com/AOMediaCodec/libavif/pull/235
                assert(bitsHeader.colors == B_YCbCr444);
                int bitsSize = height * bitsHeader.rowBytes;
                uint8_t* pixels = static_cast<uint8_t*>(malloc(bitsSize));
                if (stream->Read(pixels, bitsSize) != bitsSize)
                        return B_IO_ERROR;

                uint8_t* luma = static_cast<uint8_t*>(malloc(bitsSize / 3));
                uint8_t* cb = static_cast<uint8_t*>(malloc(bitsSize / 3));
                uint8_t* cr = static_cast<uint8_t*>(malloc(bitsSize / 3));

                for (int i = 0; i < bitsSize / 3; ++i) {
                        luma[i] = pixels[3 * i + 0];
                        cb[i] = pixels[3 * i + 1];
                        cr[i] = pixels[3 * i + 2];
                }

                image->yuvPlanes[0] = luma;
                image->yuvPlanes[1] = cb;
                image->yuvPlanes[2] = cr;

                image->yuvRowBytes[0] = bitsHeader.rowBytes / 3;
                image->yuvRowBytes[1] = bitsHeader.rowBytes / 3;
                image->yuvRowBytes[2] = bitsHeader.rowBytes / 3;

                image->yuvRange = AVIF_RANGE_LIMITED;
        } else {
                assert(bitsHeader.colors == B_GRAY8);
                int bitsSize = height * bitsHeader.rowBytes;
                uint8_t* luma = static_cast<uint8_t*>(malloc(bitsSize));
                if (stream->Read(luma, bitsSize) != bitsSize)
                        return B_IO_ERROR;

                image->yuvPlanes[0] = luma;
                image->yuvPlanes[1] = nullptr;
                image->yuvPlanes[2] = nullptr;

                image->yuvRowBytes[0] = bitsHeader.rowBytes;
                image->yuvRowBytes[1] = 0;
                image->yuvRowBytes[2] = 0;
        }

        avifRWData output = AVIF_DATA_EMPTY;
        avifEncoder* encoder = avifEncoderCreate();

        system_info info;
        encoder->maxThreads = (get_system_info(&info) == B_OK) ?
                info.cpu_count : 1;

        if (fSettings->SetGetBool(AVIF_SETTING_LOSSLESS)) {
                encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS;
                encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS;
        } else {
                encoder->minQuantizer = encoder->maxQuantizer
                        = fSettings->SetGetInt32(AVIF_SETTING_QUALITY);
        }
        encoder->speed = fSettings->SetGetInt32(AVIF_SETTING_SPEED);
        encoder->tileColsLog2
                = fSettings->SetGetInt32(AVIF_SETTING_TILES_HORIZONTAL);
        encoder->tileRowsLog2
                = fSettings->SetGetInt32(AVIF_SETTING_TILES_VERTICAL);

        avifResult encodeResult = avifEncoderWrite(encoder, image, &output);
        avifImageDestroy(image);
        avifEncoderDestroy(encoder);

        if (encodeResult != AVIF_RESULT_OK) {
                printf("ERROR: Failed to encode: %s\n",
                        avifResultToString(encodeResult));
                avifRWDataFree(&output);
                return B_ERROR;
        }

        // output contains a valid .avif file's contents
        target->Write(output.data, output.size);
        avifRWDataFree(&output);
        return B_OK;
}


status_t
AVIFTranslator::_TranslateFromAVIF(BPositionIO* stream, BMessage* ioExtension,
        uint32 outType, BPositionIO* target)
{
        if (!outType)
                outType = B_TRANSLATOR_BITMAP;
        if (outType != B_TRANSLATOR_BITMAP)
                return B_NO_TRANSLATOR;

        off_t streamLength = 0;
        stream->GetSize(&streamLength);

        off_t streamSize = stream->Seek(0, SEEK_END);
        stream->Seek(0, SEEK_SET);

        void* streamData = malloc(streamSize);
        if (streamData == NULL)
                return B_NO_MEMORY;

        if (stream->Read(streamData, streamSize) != streamSize) {
                free(streamData);
                return B_IO_ERROR;
        }

        avifDecoder* decoder = avifDecoderCreate();
        if (decoder == NULL) {
                free(streamData);
                return B_NO_MEMORY;
        }

        avifResult setIOMemoryResult = avifDecoderSetIOMemory(decoder,
                reinterpret_cast<const uint8_t *>(streamData), streamSize);
        if (setIOMemoryResult != AVIF_RESULT_OK) {
                free(streamData);
                return B_NO_MEMORY;
        }

        avifResult decodeResult = avifDecoderParse(decoder);
        if (decodeResult != AVIF_RESULT_OK) {
                free(streamData);
                return B_ILLEGAL_DATA;
        }

        // We don’t support animations yet.
        if (decoder->imageCount != 1) {
                free(streamData);
                return B_ILLEGAL_DATA;
        }

        avifResult nextImageResult = avifDecoderNextImage(decoder);
        free(streamData);
        if (nextImageResult != AVIF_RESULT_OK)
                return B_ILLEGAL_DATA;

        avifImage* image = decoder->image;
        int width = image->width;
        int height = image->height;
        avifRGBFormat format;
        uint8_t* pixels;
        uint32_t rowBytes;
        color_space colors;

        bool convertToRGB = true;
        if (image->alphaPlane) {
                format = AVIF_RGB_FORMAT_BGRA;
                colors = B_RGBA32;
        } else if (image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) {
                colors = B_GRAY8;
                convertToRGB = false;
        } else {
                format = AVIF_RGB_FORMAT_BGR;
                colors = B_RGB24;
        }

        if (convertToRGB) {
                avifRGBImage rgb;
                avifRGBImageSetDefaults(&rgb, image);
                rgb.depth = 8;
                rgb.format = format;

                avifRGBImageAllocatePixels(&rgb);
                avifResult conversionResult = avifImageYUVToRGB(image, &rgb);
                if (conversionResult != AVIF_RESULT_OK)
                        return B_ILLEGAL_DATA;

                pixels = rgb.pixels;
                rowBytes = rgb.rowBytes;
        } else {
                // TODO: Add a downsampling (with dithering?) path here, or
                // alternatively add support for higher bit depth to Haiku
                // bitmaps, possibly with HDR too.
                if (image->depth > 8)
                        return B_ILLEGAL_DATA;

                // TODO: Add support for more than just the luma plane.
                pixels = image->yuvPlanes[0];
                rowBytes = image->yuvRowBytes[0];
        }

        uint32 dataSize = rowBytes * height;

        TranslatorBitmap bitmapHeader;
        bitmapHeader.magic = B_TRANSLATOR_BITMAP;
        bitmapHeader.bounds.Set(0, 0, width - 1, height - 1);
        bitmapHeader.rowBytes = rowBytes;
        bitmapHeader.colors = colors;
        bitmapHeader.dataSize = dataSize;

        // write out Be's Bitmap header
        swap_data(B_UINT32_TYPE, &bitmapHeader, sizeof(TranslatorBitmap),
                B_SWAP_HOST_TO_BENDIAN);
        ssize_t bytesWritten = target->Write(&bitmapHeader,
                sizeof(TranslatorBitmap));
        if (bytesWritten < B_OK)
                return bytesWritten;

        if ((size_t)bytesWritten != sizeof(TranslatorBitmap))
                return B_IO_ERROR;

        bool headerOnly = false;
        if (ioExtension != NULL)
                ioExtension->FindBool(B_TRANSLATOR_EXT_HEADER_ONLY,
                        &headerOnly);

        if (headerOnly)
                return B_OK;

        bytesWritten = target->Write(pixels, dataSize);
        if (bytesWritten < B_OK)
                return bytesWritten;
        return B_OK;
}


//      #pragma mark -


BTranslator*
make_nth_translator(int32 n, image_id you, uint32 flags, ...)
{
        (void)you;
        (void)flags;
        if (n != 0)
                return NULL;

        return new AVIFTranslator();
}