From 147b47e086545e69669779a5a738465852b3125e Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 16 Jul 2020 14:29:19 +0000 Subject: [PATCH] Translators: Add an AVIF translator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This translator only supports still images for now, and supports both decoding and encoding. Encoding support has been tested only with aom, rav1e doesn’t build on Haiku yet, see https://github.com/haikuports/haikuports/pull/5534 for one of the missing dependencies. Change-Id: I716f4b862ed316b89b227bfed38072d72074201f Reviewed-on: https://review.haiku-os.org/c/haiku/+/3040 Reviewed-by: waddlesplash Reviewed-by: Jérôme Duval Tested-by: Commit checker robot --- Jamfile | 2 +- build/jam/BuildFeatures | 20 + build/jam/images/definitions/regular | 1 + src/add-ons/translators/Jamfile | 1 + .../translators/avif/AVIFTranslator.cpp | 532 ++++++++++++++++++ src/add-ons/translators/avif/AVIFTranslator.h | 84 +++ .../translators/avif/AVIFTranslator.rdef | 15 + src/add-ons/translators/avif/ConfigView.cpp | 255 +++++++++ src/add-ons/translators/avif/ConfigView.h | 38 ++ src/add-ons/translators/avif/Jamfile | 38 ++ src/add-ons/translators/avif/main.cpp | 31 + src/apps/aboutsystem/AboutSystem.cpp | 7 + src/data/mime_db/image/avif | 13 + src/data/package_infos/generic/haiku | 3 + 14 files changed, 1039 insertions(+), 1 deletion(-) create mode 100644 src/add-ons/translators/avif/AVIFTranslator.cpp create mode 100644 src/add-ons/translators/avif/AVIFTranslator.h create mode 100644 src/add-ons/translators/avif/AVIFTranslator.rdef create mode 100644 src/add-ons/translators/avif/ConfigView.cpp create mode 100644 src/add-ons/translators/avif/ConfigView.h create mode 100644 src/add-ons/translators/avif/Jamfile create mode 100644 src/add-ons/translators/avif/main.cpp create mode 100644 src/data/mime_db/image/avif diff --git a/Jamfile b/Jamfile index 72d2ccabbe..08a1565410 100644 --- a/Jamfile +++ b/Jamfile @@ -60,7 +60,7 @@ if $(HAIKU_PACKAGING_ARCHS[2]) { freetype icu libsolv zlib regular_image @{ - ffmpeg glu jasper jpeg libicns libpng16 libwebp mesa + ffmpeg glu jasper jpeg libavif libicns libpng16 libwebp mesa }@ ] ; if $(TARGET_PACKAGING_ARCH) != x86_gcc2 { diff --git a/build/jam/BuildFeatures b/build/jam/BuildFeatures index 509ae529c1..3162433dfc 100644 --- a/build/jam/BuildFeatures +++ b/build/jam/BuildFeatures @@ -650,6 +650,26 @@ if [ IsPackageAvailable libwebp_devel ] { } +# libavif +if [ IsPackageAvailable libavif_devel ] { + if $(HAIKU_PACKAGING_ARCH) = x86_64 { + ExtractBuildFeatureArchives libavif : + file: base libavif + runtime: lib + file: devel libavif_devel + depends: base + library: $(developLibDir)/libavif.so.12 + headers: $(developHeadersDir) $(developHeadersDir)/avif + ; + EnableBuildFeatures libavif ; + } else { + unavailableBuildFeatures += libavif ; + } +} else { + unavailableBuildFeatures += libavif ; +} + + # live555 if [ IsPackageAvailable live555_devel ] { ExtractBuildFeatureArchives live555 : diff --git a/build/jam/images/definitions/regular b/build/jam/images/definitions/regular index f76b2ef8a4..1cebb7a1a9 100644 --- a/build/jam/images/definitions/regular +++ b/build/jam/images/definitions/regular @@ -124,6 +124,7 @@ SYSTEM_ADD_ONS_ACCELERANTS += [ FFilterByBuildFeatures ] ; SYSTEM_ADD_ONS_TRANSLATORS += [ FFilterByBuildFeatures + AVIFTranslator@libavif BMPTranslator EXRTranslator@openexr GIFTranslator diff --git a/src/add-ons/translators/Jamfile b/src/add-ons/translators/Jamfile index 7c0c427b14..f6eaa1d7fd 100644 --- a/src/add-ons/translators/Jamfile +++ b/src/add-ons/translators/Jamfile @@ -1,5 +1,6 @@ SubDir HAIKU_TOP src add-ons translators ; +SubInclude HAIKU_TOP src add-ons translators avif ; SubInclude HAIKU_TOP src add-ons translators bmp ; SubInclude HAIKU_TOP src add-ons translators exr ; SubInclude HAIKU_TOP src add-ons translators gif ; diff --git a/src/add-ons/translators/avif/AVIFTranslator.cpp b/src/add-ons/translators/avif/AVIFTranslator.cpp new file mode 100644 index 0000000000..dc3b0121ab --- /dev/null +++ b/src/add-ons/translators/avif/AVIFTranslator.cpp @@ -0,0 +1,532 @@ +/* + * Copyright 2021, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Emmanuel Gil Peyrot + */ + + +#include "AVIFTranslator.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#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(buf); + data.size = static_cast(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( + 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(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(malloc(bitsSize)); + if (stream->Read(pixels, bitsSize) != bitsSize) + return B_IO_ERROR; + + uint8_t* luma = static_cast(malloc(bitsSize / 3)); + uint8_t* cb = static_cast(malloc(bitsSize / 3)); + uint8_t* cr = static_cast(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(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(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(); +} diff --git a/src/add-ons/translators/avif/AVIFTranslator.h b/src/add-ons/translators/avif/AVIFTranslator.h new file mode 100644 index 0000000000..8738509560 --- /dev/null +++ b/src/add-ons/translators/avif/AVIFTranslator.h @@ -0,0 +1,84 @@ +/* + * Copyright 2021, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Emmanuel Gil Peyrot + */ +#ifndef AVIF_TRANSLATOR_H +#define AVIF_TRANSLATOR_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "BaseTranslator.h" + +#define AVIF_TRANSLATOR_VERSION B_TRANSLATION_MAKE_VERSION(0,1,0) +#define AVIF_IMAGE_FORMAT 'AVIF' + +#define AVIF_SETTING_LOSSLESS "lossless" +#define AVIF_SETTING_PIXEL_FORMAT "pixfmt" +#define AVIF_SETTING_QUALITY "quality" +#define AVIF_SETTING_SPEED "speed" +#define AVIF_SETTING_TILES_HORIZONTAL "htiles" +#define AVIF_SETTING_TILES_VERTICAL "vtiles" + + +#define AVIF_IN_QUALITY 0.90 +#define AVIF_IN_CAPABILITY 0.90 + +#define AVIF_OUT_QUALITY 0.90 +#define AVIF_OUT_CAPABILITY 0.5 + +#define BITS_IN_QUALITY 0.8 +#define BITS_IN_CAPABILITY 0.6 + +#define BITS_OUT_QUALITY 0.5 +#define BITS_OUT_CAPABILITY 0.4 + + +struct AVIFPicture; + + +class AVIFTranslator : public BaseTranslator { +public: + AVIFTranslator(); + + virtual status_t DerivedIdentify(BPositionIO* stream, + const translation_format* format, + BMessage* settings, translator_info* info, + uint32 outType); + + virtual status_t DerivedTranslate(BPositionIO* stream, + const translator_info* info, + BMessage* settings, uint32 outType, + BPositionIO* target, int32 baseType); + + virtual BView* NewConfigView(TranslatorSettings* settings); + +protected: + virtual ~AVIFTranslator(); + // this is protected because the object is deleted by the + // Release() function instead of being deleted directly by + // the user + +private: + status_t _TranslateFromBits(BPositionIO* stream, + BMessage* settings, uint32 outType, + BPositionIO* target); + + status_t _TranslateFromAVIF(BPositionIO* stream, + BMessage* settings, uint32 outType, + BPositionIO* target); +}; + + +#endif // #ifndef AVIF_TRANSLATOR_H diff --git a/src/add-ons/translators/avif/AVIFTranslator.rdef b/src/add-ons/translators/avif/AVIFTranslator.rdef new file mode 100644 index 0000000000..f73144285f --- /dev/null +++ b/src/add-ons/translators/avif/AVIFTranslator.rdef @@ -0,0 +1,15 @@ +/* + * AVIFTranslator.rdef + */ + +resource app_signature "application/x-vnd.Haiku-AVIFTranslator"; + +resource app_version { + major = 0, + middle = 1, + minor = 0, + variety = 0, + internal = 0, + short_info = "0.1.0", + long_info = "Haiku AVIFTranslator Add-Ons." +}; diff --git a/src/add-ons/translators/avif/ConfigView.cpp b/src/add-ons/translators/avif/ConfigView.cpp new file mode 100644 index 0000000000..b04a80baa3 --- /dev/null +++ b/src/add-ons/translators/avif/ConfigView.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2021, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Emmanuel Gil Peyrot + */ + + +#include "ConfigView.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "avif/avif.h" + +#include "TranslatorSettings.h" +#include "AVIFTranslator.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "ConfigView" + + +static const uint32 kMsgLossless = 'losl'; +static const uint32 kMsgPixelFormat = 'pfmt'; +static const uint32 kMsgQuality = 'qlty'; +static const uint32 kMsgSpeed = 'sped'; +static const uint32 kMsgTilesHorizontal = 'tilh'; +static const uint32 kMsgTilesVertical = 'tilv'; + + +ConfigView::ConfigView(TranslatorSettings* settings) + : + BGroupView(B_TRANSLATE("AVIFTranslator Settings"), B_VERTICAL), + fSettings(settings) +{ + SetViewUIColor(B_PANEL_BACKGROUND_COLOR); + + BStringView* title = new BStringView("title", + B_TRANSLATE("AVIF image translator")); + title->SetFont(be_bold_font); + + char versionString[256]; + sprintf(versionString, "v%d.%d.%d, %s", + static_cast(B_TRANSLATION_MAJOR_VERSION(AVIF_TRANSLATOR_VERSION)), + static_cast(B_TRANSLATION_MINOR_VERSION(AVIF_TRANSLATOR_VERSION)), + static_cast(B_TRANSLATION_REVISION_VERSION( + AVIF_TRANSLATOR_VERSION)), + __DATE__); + + BStringView* version = new BStringView("version", versionString); + + BString copyrightsText; + BStringView *copyrightView = new BStringView("Copyright", + B_TRANSLATE(B_UTF8_COPYRIGHT "2021 Emmanuel Gil Peyrot")); + + BString libavifInfo = B_TRANSLATE( + "Based on libavif %version%"); + libavifInfo.ReplaceAll("%version%", avifVersion()); + + BStringView *copyright2View = new BStringView("Copyright2", + libavifInfo.String()); + BStringView *copyright3View = new BStringView("Copyright3", + B_TRANSLATE(B_UTF8_COPYRIGHT "2019 Joe Drago. All rights reserved.")); + + // output parameters + + fLosslessCheckBox = new BCheckBox("lossless", + B_TRANSLATE("Lossless"), new BMessage(kMsgLossless)); + bool lossless; + fSettings->SetGetBool(AVIF_SETTING_LOSSLESS, &lossless); + if (lossless) + fLosslessCheckBox->SetValue(B_CONTROL_ON); + + fPixelFormatMenu = new BPopUpMenu(B_TRANSLATE("Pixel format")); + + static const avifPixelFormat pixelFormats[4] = { + AVIF_PIXEL_FORMAT_YUV444, + AVIF_PIXEL_FORMAT_YUV420, + AVIF_PIXEL_FORMAT_YUV400, + AVIF_PIXEL_FORMAT_YUV422, + }; + for (size_t i = 0; i < 4; ++i) { + BMessage* msg = new BMessage(kMsgPixelFormat); + msg->AddInt32("value", pixelFormats[i]); + + BMenuItem* item = new BMenuItem( + avifPixelFormatToString(pixelFormats[i]), msg); + if (fSettings->SetGetInt32(AVIF_SETTING_PIXEL_FORMAT) == pixelFormats[i]) + item->SetMarked(true); + fPixelFormatMenu->AddItem(item); + } + + BMenuField* pixelFormatField = new BMenuField(B_TRANSLATE("Pixel format:"), + fPixelFormatMenu); + + rgb_color barColor = { 0, 0, 229, 255 }; + + fQualitySlider = new BSlider("quality", B_TRANSLATE("Output quality:"), + new BMessage(kMsgQuality), AVIF_QUANTIZER_BEST_QUALITY, + AVIF_QUANTIZER_WORST_QUALITY, B_HORIZONTAL, B_BLOCK_THUMB); + fQualitySlider->SetHashMarks(B_HASH_MARKS_BOTTOM); + fQualitySlider->SetHashMarkCount(8); + fQualitySlider->SetLimitLabels(B_TRANSLATE("Best"), B_TRANSLATE("Worst")); + fQualitySlider->UseFillColor(true, &barColor); + fQualitySlider->SetValue(fSettings->SetGetInt32(AVIF_SETTING_QUALITY)); + fQualitySlider->SetEnabled(!lossless); + + fSpeedSlider = new BSlider("speed", B_TRANSLATE("Compression speed:"), + new BMessage(kMsgSpeed), AVIF_SPEED_SLOWEST, + AVIF_SPEED_FASTEST, B_HORIZONTAL, B_BLOCK_THUMB); + fSpeedSlider->SetHashMarks(B_HASH_MARKS_BOTTOM); + fSpeedSlider->SetHashMarkCount(11); + fSpeedSlider->SetLimitLabels(B_TRANSLATE("Slow"), + B_TRANSLATE("Faster but worse quality")); + fSpeedSlider->UseFillColor(true, &barColor); + fSpeedSlider->SetValue(fSettings->SetGetInt32(AVIF_SETTING_SPEED)); + + fHTilesSlider = new BSlider("htiles", B_TRANSLATE("Horizontal tiles:"), + new BMessage(kMsgTilesHorizontal), 1, 6, B_HORIZONTAL, + B_BLOCK_THUMB); + fHTilesSlider->SetHashMarks(B_HASH_MARKS_BOTTOM); + fHTilesSlider->SetHashMarkCount(6); + fHTilesSlider->SetLimitLabels(B_TRANSLATE("1"), + B_TRANSLATE("2⁶")); + fHTilesSlider->UseFillColor(true, &barColor); + fHTilesSlider->SetValue( + fSettings->SetGetInt32(AVIF_SETTING_TILES_HORIZONTAL)); + + fVTilesSlider = new BSlider("vtiles", B_TRANSLATE("Vertical tiles:"), + new BMessage(kMsgTilesVertical), 1, 6, B_HORIZONTAL, + B_BLOCK_THUMB); + fVTilesSlider->SetHashMarks(B_HASH_MARKS_BOTTOM); + fVTilesSlider->SetHashMarkCount(6); + fVTilesSlider->SetLimitLabels(B_TRANSLATE("1"), + B_TRANSLATE("2⁶")); + fVTilesSlider->UseFillColor(true, &barColor); + fVTilesSlider->SetValue( + fSettings->SetGetInt32(AVIF_SETTING_TILES_VERTICAL)); + + // Build the layout + BLayoutBuilder::Group<>(this, B_VERTICAL, 0) + .SetInsets(B_USE_DEFAULT_SPACING) + .Add(title) + .Add(version) + .Add(copyrightView) + .AddGlue() + .Add(fLosslessCheckBox) + .AddGrid(B_USE_DEFAULT_SPACING, B_USE_SMALL_SPACING) + .Add(pixelFormatField->CreateLabelLayoutItem(), 0, 0) + .AddGroup(B_HORIZONTAL, 0.0f, 1, 0) + .Add(pixelFormatField->CreateMenuBarLayoutItem(), 0.0f) + .AddGlue() + .End() + .End() + .Add(fQualitySlider) + .Add(fSpeedSlider) + .Add(fHTilesSlider) + .Add(fVTilesSlider) + .AddGlue() + .Add(copyright2View) + .Add(copyright3View); + + BFont font; + GetFont(&font); + SetExplicitPreferredSize(BSize((font.Size() * 250) / 12, + (font.Size() * 350) / 12)); +} + + +ConfigView::~ConfigView() +{ + fSettings->Release(); +} + + +void +ConfigView::AttachedToWindow() +{ + BGroupView::AttachedToWindow(); + + fQualitySlider->SetTarget(this); + fSpeedSlider->SetTarget(this); + fHTilesSlider->SetTarget(this); + fVTilesSlider->SetTarget(this); + + if (Parent() == NULL && Window()->GetLayout() == NULL) { + Window()->SetLayout(new BGroupLayout(B_VERTICAL)); + Window()->ResizeTo(PreferredSize().Width(), + PreferredSize().Height()); + } +} + + +void +ConfigView::MessageReceived(BMessage* message) +{ + struct { + const char* name; + uint32 what; + TranSettingType type; + } maps[] = { + { AVIF_SETTING_LOSSLESS, kMsgLossless, TRAN_SETTING_BOOL }, + { AVIF_SETTING_PIXEL_FORMAT, kMsgPixelFormat, + TRAN_SETTING_INT32 }, + { AVIF_SETTING_QUALITY, kMsgQuality, TRAN_SETTING_INT32 }, + { AVIF_SETTING_SPEED, kMsgSpeed, TRAN_SETTING_INT32 }, + { AVIF_SETTING_TILES_HORIZONTAL, kMsgTilesHorizontal, + TRAN_SETTING_INT32 }, + { AVIF_SETTING_TILES_VERTICAL, kMsgTilesVertical, + TRAN_SETTING_INT32 }, + { NULL } + }; + + int i; + for (i = 0; maps[i].name != NULL; i++) { + if (maps[i].what == message->what) + break; + } + + if (maps[i].name == NULL) { + BGroupView::MessageReceived(message); + return; + } + + int32 value; + if (message->FindInt32("value", &value) == B_OK + || message->FindInt32("be:value", &value) == B_OK) { + switch(maps[i].type) { + case TRAN_SETTING_BOOL: + { + bool boolValue = value; + fSettings->SetGetBool(maps[i].name, &boolValue); + if (maps[i].what == kMsgLossless) + fSpeedSlider->SetEnabled(!boolValue); + break; + } + case TRAN_SETTING_INT32: + fSettings->SetGetInt32(maps[i].name, &value); + break; + } + fSettings->SaveSettings(); + } +} diff --git a/src/add-ons/translators/avif/ConfigView.h b/src/add-ons/translators/avif/ConfigView.h new file mode 100644 index 0000000000..f4ac5e1a14 --- /dev/null +++ b/src/add-ons/translators/avif/ConfigView.h @@ -0,0 +1,38 @@ +/* + * Copyright 2021, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Emmanuel Gil Peyrot + */ +#ifndef CONFIG_VIEW_H +#define CONFIG_VIEW_H + + +#include + +class BCheckBox; +class BPopUpMenu; +class BSlider; +class TranslatorSettings; + +class ConfigView : public BGroupView { +public: + ConfigView(TranslatorSettings* settings); + virtual ~ConfigView(); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *message); + +private: + TranslatorSettings* fSettings; + BPopUpMenu* fPixelFormatMenu; + BCheckBox* fLosslessCheckBox; + BSlider* fQualitySlider; + BSlider* fSpeedSlider; + BSlider* fHTilesSlider; + BSlider* fVTilesSlider; +}; + + +#endif /* CONFIG_VIEW_H */ diff --git a/src/add-ons/translators/avif/Jamfile b/src/add-ons/translators/avif/Jamfile new file mode 100644 index 0000000000..ff6315ca7d --- /dev/null +++ b/src/add-ons/translators/avif/Jamfile @@ -0,0 +1,38 @@ +SubDir HAIKU_TOP src add-ons translators avif ; + +SubDirHdrs [ FDirName $(SUBDIR) $(DOTDOT) shared ] ; + +AddResources AVIFTranslator : AVIFTranslator.rdef ; + +local architectureObject ; +for architectureObject in [ MultiArchSubDirSetup ] { + on $(architectureObject) { + UseBuildFeatureHeaders libavif ; + + Translator [ MultiArchDefaultGristFiles AVIFTranslator ] : + + main.cpp + + AVIFTranslator.cpp + ConfigView.cpp + + : + be translation [ MultiArchDefaultGristFiles libtranslatorsutils.a ] + [ BuildFeatureAttribute libavif : library ] + [ TargetLibsupc++ ] localestub + : true + ; + + Includes [ FGristFiles ConfigView.cpp AVIFTranslator.cpp ] + : [ BuildFeatureAttribute libavif : headers ] ; + } +} + +DoCatalogs AVIFTranslator : + x-vnd.Haiku-AVIFTranslator + : + main.cpp + ConfigView.cpp + AVIFTranslator.cpp +; + diff --git a/src/add-ons/translators/avif/main.cpp b/src/add-ons/translators/avif/main.cpp new file mode 100644 index 0000000000..403ae454a7 --- /dev/null +++ b/src/add-ons/translators/avif/main.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2021, Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Emmanuel Gil Peyrot + */ + + +#include +#include + +#include "TranslatorWindow.h" +#include "AVIFTranslator.h" + + +#undef B_TRANSLATION_CONTEXT +#define B_TRANSLATION_CONTEXT "main" + + +int +main() +{ + BApplication app("application/x-vnd.Haiku-AVIFTranslator"); + if (LaunchTranslatorWindow(new AVIFTranslator, + B_TRANSLATE("AVIF Settings")) != B_OK) + return 1; + + app.Run(); + return 0; +} diff --git a/src/apps/aboutsystem/AboutSystem.cpp b/src/apps/aboutsystem/AboutSystem.cpp index 1c18185b3f..93a740b6c0 100644 --- a/src/apps/aboutsystem/AboutSystem.cpp +++ b/src/apps/aboutsystem/AboutSystem.cpp @@ -1398,6 +1398,13 @@ AboutView::_CreateCreditsView() .SetLicense(kBSDThreeClause) .SetURL("http://www.webmproject.org/code/#libwebp_webp_image_library")); + // libavif + _AddPackageCredit(PackageCredit("libavif") + .SetCopyright(B_TRANSLATE(COPYRIGHT_STRING + "2019 Joe Drago. All rights reserved.")) + .SetLicense(kBSDThreeClause) + .SetURL("https://github.com/AOMediaCodec/libavif")); + // GTF _AddPackageCredit(PackageCredit("GTF") .SetCopyright(B_TRANSLATE("2001 by Andy Ritger based on the " diff --git a/src/data/mime_db/image/avif b/src/data/mime_db/image/avif new file mode 100644 index 0000000000..a6b78c8088 --- /dev/null +++ b/src/data/mime_db/image/avif @@ -0,0 +1,13 @@ + +resource(0, "BEOS:TYPE") #'MIMS' "application/x-vnd.Be-meta-mime"; + +resource(1, "META:TYPE") "image/avif"; + +resource(2, "META:SNIFF_RULE") "0.50 \(\"\\000\\000\\000 ftypavif\"\)"; + +resource(3, "META:S:DESC") #'MSDC' "AV1 Image File Format"; + +resource(4, "META:EXTENS") message(234) { + "extensions" = "avif", + "type" = "image/avif" +}; diff --git a/src/data/package_infos/generic/haiku b/src/data/package_infos/generic/haiku index 191d06ce99..dbea0cada7 100644 --- a/src/data/package_infos/generic/haiku +++ b/src/data/package_infos/generic/haiku @@ -130,6 +130,9 @@ requires { #ifdef HAIKU_BUILD_FEATURE_%HAIKU_PACKAGING_ARCH%_libpng_ENABLED lib:libpng16 #endif +#ifdef HAIKU_BUILD_FEATURE_%HAIKU_PACKAGING_ARCH%_libavif_ENABLED + lib:libavif >= 12 +#endif #ifdef HAIKU_BUILD_FEATURE_%HAIKU_PACKAGING_ARCH%_openssl_ENABLED lib:libssl lib:libcrypto