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