root/src/add-ons/translators/webp/WebPTranslator.cpp
/*
 * Copyright 2010-2011, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Philippe Houdoin
 */


#include "WebPTranslator.h"

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

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

#include "webp/encode.h"
#include "webp/decode.h"

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


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "WebPTranslator"


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[] = {
        {
                WEBP_IMAGE_FORMAT,
                B_TRANSLATOR_BITMAP,
                WEBP_IN_QUALITY,
                WEBP_IN_CAPABILITY,
                "image/webp",
                "WebP image"
        },
        {
                B_TRANSLATOR_BITMAP,
                B_TRANSLATOR_BITMAP,
                BITS_IN_QUALITY,
                BITS_IN_CAPABILITY,
                "image/x-be-bitmap",
                "Be Bitmap Format (WebPTranslator)"
        },
};

// The output formats that this translator knows how to write
static const translation_format sOutputFormats[] = {
        {
                WEBP_IMAGE_FORMAT,
                B_TRANSLATOR_BITMAP,
                WEBP_OUT_QUALITY,
                WEBP_OUT_CAPABILITY,
                "image/webp",
                "WebP image"
        },
        {
                B_TRANSLATOR_BITMAP,
                B_TRANSLATOR_BITMAP,
                BITS_OUT_QUALITY,
                BITS_OUT_CAPABILITY,
                "image/x-be-bitmap",
                "Be Bitmap Format (WebPTranslator)"
        },
};

// 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 },
        { WEBP_SETTING_QUALITY, TRAN_SETTING_INT32, 60 },
        { WEBP_SETTING_PRESET, TRAN_SETTING_INT32, 0 },
        { WEBP_SETTING_METHOD, TRAN_SETTING_INT32, 2 },
        { WEBP_SETTING_PREPROCESSING, TRAN_SETTING_BOOL, false },
};

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 -


WebPTranslator::WebPTranslator()
        :
        BaseTranslator(B_TRANSLATE("WebP images"),
        B_TRANSLATE("WebP image translator"),
        WEBP_TRANSLATOR_VERSION,
        sInputFormats, kNumInputFormats,
        sOutputFormats, kNumOutputFormats,
        "WebPTranslator_Settings", sDefaultSettings, kNumDefaultSettings,
        B_TRANSLATOR_BITMAP, WEBP_IMAGE_FORMAT)
{
}


WebPTranslator::~WebPTranslator()
{
}


status_t
WebPTranslator::DerivedIdentify(BPositionIO* stream,
        const translation_format* format, BMessage* settings,
        translator_info* info, uint32 outType)
{
        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 WebP format
        if (!WebPGetInfo((const uint8_t*)buf, size, NULL, NULL))
                return B_ILLEGAL_DATA;

        info->type = WEBP_IMAGE_FORMAT;
        info->group = B_TRANSLATOR_BITMAP;
        info->quality = WEBP_IN_QUALITY;
        info->capability = WEBP_IN_CAPABILITY;
        strlcpy(info->name, B_TRANSLATE("WebP image"), sizeof(info->name));
        strcpy(info->MIME, "image/webp");

        return B_OK;
}


status_t
WebPTranslator::DerivedTranslate(BPositionIO* stream,
        const translator_info* info, BMessage* ioExtension, uint32 outType,
        BPositionIO* target, int32 baseType)
{
        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 _TranslateFromWebP(stream, ioExtension, outType, target);
        else
                // if BaseTranslator dit not properly identify the data as
                // bits or not bits
                return B_NO_TRANSLATOR;
}


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


status_t
WebPTranslator::_TranslateFromBits(BPositionIO* stream, BMessage* ioExtension,
        uint32 outType, BPositionIO* target)
{
        if (!outType)
                outType = WEBP_IMAGE_FORMAT;
        if (outType != WEBP_IMAGE_FORMAT)
                return B_NO_TRANSLATOR;

        TranslatorBitmap bitsHeader;
        status_t status;

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

        if (bitsHeader.colors == B_CMAP8) {
                // TODO: support whatever colospace by intermediate colorspace conversion
                printf("Error! Colorspace not supported\n");
                return B_NO_TRANSLATOR;
        }

        int32 bitsBytesPerPixel = 0;
        switch (bitsHeader.colors) {
                case B_RGB32:
                case B_RGB32_BIG:
                case B_RGBA32:
                case B_RGBA32_BIG:
                case B_CMY32:
                case B_CMYA32:
                case B_CMYK32:
                        bitsBytesPerPixel = 4;
                        break;

                case B_RGB24:
                case B_RGB24_BIG:
                case B_CMY24:
                        bitsBytesPerPixel = 3;
                        break;

                case B_RGB16:
                case B_RGB16_BIG:
                case B_RGBA15:
                case B_RGBA15_BIG:
                case B_RGB15:
                case B_RGB15_BIG:
                        bitsBytesPerPixel = 2;
                        break;

                case B_CMAP8:
                case B_GRAY8:
                        bitsBytesPerPixel = 1;
                        break;

                default:
                        return B_ERROR;
        }

        if (bitsBytesPerPixel < 3) {
                // TODO support
                return B_NO_TRANSLATOR;
        }

        WebPPicture picture;
        WebPConfig config;

        if (!WebPPictureInit(&picture) || !WebPConfigInit(&config)) {
                printf("Error! Version mismatch!\n");
                return B_ERROR;
        }

        WebPPreset preset = (WebPPreset)fSettings->SetGetInt32(WEBP_SETTING_PRESET);
        config.quality = (float)fSettings->SetGetInt32(WEBP_SETTING_QUALITY);

        if (!WebPConfigPreset(&config, (WebPPreset)preset, config.quality)) {
                printf("Error! Could initialize configuration with preset.");
                return B_ERROR;
        }

        config.method = fSettings->SetGetInt32(WEBP_SETTING_METHOD);
        config.preprocessing = fSettings->SetGetBool(WEBP_SETTING_PREPROCESSING);

        if (!WebPValidateConfig(&config)) {
                printf("Error! Invalid configuration.\n");
                return B_ERROR;
        }

        picture.width = bitsHeader.bounds.IntegerWidth() + 1;
        picture.height = bitsHeader.bounds.IntegerHeight() + 1;

        int stride = bitsHeader.rowBytes;
        int bitsSize = picture.height * stride;
        uint8* bits = (uint8*)malloc(bitsSize);
        if (bits == NULL)
                return B_NO_MEMORY;

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

        if (!WebPPictureImportBGRA(&picture, bits, stride)) {
                printf("Error! WebPEncode() failed!\n");
                free(bits);
                return B_ERROR;
        }
        free(bits);

        picture.writer = _EncodedWriter;
        picture.custom_ptr = target;
        picture.stats = NULL;

        if (!WebPEncode(&config, &picture)) {
                printf("Error! WebPEncode() failed!\n");
                status = B_NO_TRANSLATOR;
        } else
                status = B_OK;

        WebPPictureFree(&picture);
        return status;
}


status_t
WebPTranslator::_TranslateFromWebP(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;
        }

        int width, height;
        uint8* out = WebPDecodeBGRA((const uint8*)streamData, streamSize, &width,
                &height);
        free(streamData);

        if (out == NULL)
                return B_ILLEGAL_DATA;

        FreeAllocation _(out);

        uint32 dataSize = width * 4 * height;

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

        // 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;

        uint32 dataLeft = dataSize;
        uint8* p = out;
        while (dataLeft) {
                bytesWritten = target->Write(p, 4);
                if (bytesWritten < B_OK)
                        return bytesWritten;

                if (bytesWritten != 4)
                        return B_IO_ERROR;

                p += 4;
                dataLeft -= 4;
        }

        return B_OK;
}


/* static */ int
WebPTranslator::_EncodedWriter(const uint8_t* data, size_t dataSize,
        const WebPPicture* const picture)
{
        BPositionIO* target = (BPositionIO*)picture->custom_ptr;
        return dataSize ? (target->Write(data, dataSize) == (ssize_t)dataSize) : 1;
}


//      #pragma mark -


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

        return new WebPTranslator();
}