diff --git a/meson_options.txt b/meson_options.txt index c6f749e1f7..c891630f04 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -189,8 +189,8 @@ option('unmount-path', option('evas-loaders-disabler', type : 'array', description : 'List of modular image/vector loaders to disable in efl', - choices : ['gst', 'pdf', 'ps', 'raw', 'svg', 'rsvg', 'xcf', 'bmp', 'dds', 'eet', 'generic', 'gif', 'ico', 'jp2k', 'jpeg', 'pmaps', 'png', 'psd', 'tga', 'tgv', 'tiff', 'wbmp', 'webp', 'xpm', 'json'], - value : ['json'] + choices : ['gst', 'pdf', 'ps', 'raw', 'svg', 'rsvg', 'xcf', 'bmp', 'dds', 'eet', 'generic', 'gif', 'ico', 'jp2k', 'jpeg', 'pmaps', 'png', 'psd', 'tga', 'tgv', 'tiff', 'wbmp', 'webp', 'xpm', 'json', 'avif'], + value : ['json', 'avif'] ) option('ecore-imf-loaders-disabler', diff --git a/po/POTFILES.in b/po/POTFILES.in index d6879babc1..fa4061f352 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -495,12 +495,14 @@ src/modules/evas/image_loaders/pmaps/evas_image_load_pmaps.c src/modules/evas/image_loaders/generic/evas_image_load_generic.c src/modules/evas/image_loaders/gif/evas_image_load_gif.c src/modules/evas/image_loaders/tgv/evas_image_load_tgv.c +src/modules/evas/image_loaders/avif/evas_image_load_avif.c src/modules/evas/image_savers/jpeg/evas_image_save_jpeg.c src/modules/evas/image_savers/png/evas_image_save_png.c src/modules/evas/image_savers/tiff/evas_image_save_tiff.c src/modules/evas/image_savers/eet/evas_image_save_eet.c src/modules/evas/image_savers/webp/evas_image_save_webp.c src/modules/evas/image_savers/tgv/evas_image_save_tgv.c +src/modules/evas/image_savers/avif/evas_image_save_avif.c src/modules/evas/vg_loaders/json/evas_vg_load_json.c src/modules/evas/vg_loaders/eet/evas_vg_load_eet.c src/modules/evas/vg_loaders/svg/evas_vg_load_svg.c diff --git a/src/lib/evas/common/evas_image_load.c b/src/lib/evas/common/evas_image_load.c index 1d28253c7e..96caa1627e 100644 --- a/src/lib/evas/common/evas_image_load.c +++ b/src/lib/evas/common/evas_image_load.c @@ -66,6 +66,9 @@ static const struct ext_loader_s loaders[] = MATCHING(".dds", "dds"), + MATCHING(".avif", "avif"), + MATCHING(".avifs", "avif"), + /* xcf - gefenric */ MATCHING(".xcf", "generic"), MATCHING(".xcf.gz", "generic"), @@ -93,6 +96,7 @@ static const struct ext_loader_s loaders[] = MATCHING(".dcr", "generic"), MATCHING(".dng", "generic"), MATCHING(".erf", "generic"), + MATCHING(".fff", "generic"), MATCHING(".k25", "generic"), MATCHING(".kdc", "generic"), MATCHING(".mrw", "generic"), @@ -163,7 +167,7 @@ static const struct ext_loader_s loaders[] = static const char *loaders_name[] = { /* in order of most likely needed */ "png", "jpeg", "eet", "xpm", "tiff", "gif", "svg", "webp", "pmaps", - "bmp", "tga", "wbmp", "ico", "psd", "jp2k", "dds", "generic" + "bmp", "tga", "wbmp", "ico", "psd", "jp2k", "dds", "avif", "generic" }; struct evas_image_foreach_loader_data diff --git a/src/lib/evas/common/evas_image_save.c b/src/lib/evas/common/evas_image_save.c index 9a6a7621c1..45718c5b9f 100644 --- a/src/lib/evas/common/evas_image_save.c +++ b/src/lib/evas/common/evas_image_save.c @@ -33,6 +33,8 @@ evas_common_save_image_to_file(RGBA_Image *im, const char *file, const char *key saver = "webp"; if (!strcasecmp(p, "tgv")) saver = "tgv"; + if (!strcasecmp(p, "avif")) + saver = "avif"; } if (saver) diff --git a/src/lib/evas/file/evas_module.c b/src/lib/evas/file/evas_module.c index a1e8b623af..83f1d804df 100644 --- a/src/lib/evas/file/evas_module.c +++ b/src/lib/evas/file/evas_module.c @@ -196,6 +196,7 @@ EVAS_EINA_STATIC_MODULE_DEFINE(vg_loader, json); #endif #if !EVAS_MODULE_NO_IMAGE_LOADERS +EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, avif); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, bmp); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, dds); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, eet); @@ -222,6 +223,7 @@ EVAS_EINA_STATIC_MODULE_DEFINE(vg_saver, svg); #endif #if !EVAS_MODULE_NO_IMAGE_SAVERS +EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, avif); EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, eet); EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, jpeg); EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, png); @@ -287,6 +289,9 @@ static const struct { #endif #endif #if !EVAS_MODULE_NO_IMAGE_LOADERS +#ifdef EVAS_STATIC_BUILD_AVIF + EVAS_EINA_STATIC_MODULE_USE(image_loader, avif), +#endif #ifdef EVAS_STATIC_BUILD_BMP EVAS_EINA_STATIC_MODULE_USE(image_loader, bmp), #endif @@ -351,6 +356,9 @@ static const struct { #endif #endif #if !EVAS_MODULE_NO_IMAGE_SAVERS +#ifdef EVAS_STATIC_BUILD_AVIF + EVAS_EINA_STATIC_MODULE_USE(image_saver, avif), +#endif #ifdef EVAS_STATIC_BUILD_EET EVAS_EINA_STATIC_MODULE_USE(image_saver, eet), #endif diff --git a/src/lib/evas/meson.build b/src/lib/evas/meson.build index fca88309ad..56dabb9eb4 100644 --- a/src/lib/evas/meson.build +++ b/src/lib/evas/meson.build @@ -10,8 +10,10 @@ giflib = cc.find_library('gif') webp = dependency('libwebp', version: ['>=0.5.0'], required: get_option('evas-loaders-disabler').contains('webp') == false) webpdemux = dependency('libwebpdemux', version: ['>=0.5.0'], required: get_option('evas-loaders-disabler').contains('webp') == false) libopenjp2 = dependency('libopenjp2', required: get_option('evas-loaders-disabler').contains('jp2k') == false) +libavif = dependency('libavif', required: get_option('evas-loaders-disabler').contains('avif') == false) evas_image_loaders_file = [ + ['avif', 'shared', [libavif]], ['bmp', 'shared', []], ['eet', 'static', [eet]], ['generic', 'shared', [rt]], @@ -31,6 +33,7 @@ evas_image_loaders_file = [ ] evas_image_savers_file = [ + ['avif', 'shared', [libavif]], ['eet', 'static', [eet]], ['jpeg', 'static', [jpeg]], ['png', 'static', [png]], diff --git a/src/modules/evas/image_loaders/avif/evas_image_load_avif.c b/src/modules/evas/image_loaders/avif/evas_image_load_avif.c new file mode 100644 index 0000000000..34b6da9bd3 --- /dev/null +++ b/src/modules/evas/image_loaders/avif/evas_image_load_avif.c @@ -0,0 +1,382 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include "Evas_Loader.h" +#include "evas_common_private.h" + +typedef struct _Evas_Loader_Internal Evas_Loader_Internal; +struct _Evas_Loader_Internal +{ + Eina_File *f; + Evas_Image_Load_Opts *opts; + Evas_Image_Animated *animated; + avifDecoder *decoder; + double duration; +}; + +static int _evas_loader_avif_log_dom = -1; + +#ifdef ERR +# undef ERR +#endif +#define ERR(...) EINA_LOG_DOM_ERR(_evas_loader_avif_log_dom, __VA_ARGS__) + +#ifdef WRN +# undef WRN +#endif +#define WRN(...) EINA_LOG_DOM_WARN(_evas_loader_avif_log_dom, __VA_ARGS__) + +#ifdef INF +# undef INF +#endif +#define INF(...) EINA_LOG_DOM_INFO(_evas_loader_avif_log_dom, __VA_ARGS__) + +static Eina_Bool +evas_image_load_file_head_avif_internal(Evas_Loader_Internal *loader, + Emile_Image_Property *prop, + void *map, size_t length, + int *error) +{ + Evas_Image_Animated *animated; + avifROData raw; + avifDecoder *decoder; + avifResult res; + Eina_Bool ret; + + animated = loader->animated; + + ret = EINA_FALSE; + prop->w = 0; + prop->h = 0; + prop->alpha = EINA_FALSE; + + raw.size = length; + raw.data = (const uint8_t *)map; + + decoder = avifDecoderCreate(); + if (!decoder) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return ret; + } + + res = avifDecoderParse(decoder, &raw); + if (res != AVIF_RESULT_OK) + { + ERR("avif file format invalid"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + if (decoder->imageCount < 1) + { + ERR("avif file format invalid"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + res = avifDecoderNextImage(decoder); + if (res != AVIF_RESULT_OK) + { + ERR("avif file format invalid"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + prop->w = decoder->image->width; + prop->h = decoder->image->height; + + /* if size is invalid, we exit */ + if ((prop->w < 1) || (prop->h < 1) || + (prop->w > IMG_MAX_SIZE) || (prop->h > IMG_MAX_SIZE) || + IMG_TOO_BIG(prop->w, prop->h)) + { + if (IMG_TOO_BIG(prop->w, prop->h)) + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + else + *error= EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + prop->alpha = !!decoder->image->alphaPlane; + + if (decoder->imageCount > 1) + { + animated->loop_hint = EVAS_IMAGE_ANIMATED_HINT_NONE; + animated->frame_count = decoder->imageCount; + animated->loop_count = 1; + animated->animated = EINA_TRUE; + loader->duration = decoder->duration / decoder->imageCount; + } + + *error = EVAS_LOAD_ERROR_NONE; + ret = EINA_TRUE; + + destroy_decoder: + avifDecoderDestroy(decoder); + + return ret; +} + +static Eina_Bool +evas_image_load_file_data_avif_internal(Evas_Loader_Internal *loader, + void *pixels, + void *map, size_t length, + int *error) +{ + avifRGBImage rgb; + avifDecoder *decoder; + avifResult res; + Evas_Image_Animated *animated; + Eina_Bool ret; + + ret = EINA_FALSE; + + /* FIXME: create decoder in evas_image_load_file_data_avif instead ? */ + decoder = loader->decoder; + if (!decoder) + { + avifROData raw; + decoder = avifDecoderCreate(); + if (!decoder) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return EINA_FALSE; + } + + raw.size = length; + raw.data = (const uint8_t *)map; + + res = avifDecoderParse(decoder, &raw); + if (res != AVIF_RESULT_OK) + { + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + loader->decoder = decoder; + } + + animated = loader->animated; + if (animated->animated) + { + /* FIXME: next image instead ? */ + res = avifDecoderNthImage(decoder, animated->cur_frame + 1); + if (res != AVIF_RESULT_OK) + { + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + } + else + { + res = avifDecoderNextImage(decoder); + if (res != AVIF_RESULT_OK) + { + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + } + + avifRGBImageSetDefaults(&rgb, decoder->image); +#ifdef WORDS_BIGENDIAN + rgb.format = AVIF_RGB_FORMAT_ARGB; +#else + rgb.format = AVIF_RGB_FORMAT_BGRA; +#endif + rgb.depth = 8; + rgb.pixels = pixels; + rgb.rowBytes = 4 * decoder->image->width; + + avifImageYUVToRGB(decoder->image, &rgb); + + *error = EVAS_LOAD_ERROR_NONE; + + ret = EINA_TRUE; + + on_error: + + return ret; +} + +static void * +evas_image_load_file_open_avif(Eina_File *f, Eina_Stringshare *key EINA_UNUSED, + Evas_Image_Load_Opts *opts, + Evas_Image_Animated *animated, + int *error) +{ + Evas_Loader_Internal *loader; + + loader = calloc(1, sizeof (Evas_Loader_Internal)); + if (!loader) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return NULL; + } + + loader->f = f; + loader->opts = opts; + loader->animated = animated; + + return loader; +} + +static void +evas_image_load_file_close_avif(void *loader_data) +{ + Evas_Loader_Internal *loader; + + loader = loader_data; + avifDecoderDestroy(loader->decoder); + free(loader_data); +} + +static Eina_Bool +evas_image_load_file_head_avif(void *loader_data, + Evas_Image_Property *prop, + int *error) +{ + Evas_Loader_Internal *loader = loader_data; + Eina_File *f; + void *map; + Eina_Bool val; + + f = loader->f; + + map = eina_file_map_all(f, EINA_FILE_RANDOM); + if (!map) + { + *error = EVAS_LOAD_ERROR_DOES_NOT_EXIST; + return EINA_FALSE; + } + + val = evas_image_load_file_head_avif_internal(loader, + (Emile_Image_Property *)prop, + map, eina_file_size_get(f), + error); + + eina_file_map_free(f, map); + + return val; +} + +static Eina_Bool +evas_image_load_file_data_avif(void *loader_data, + Evas_Image_Property *prop EINA_UNUSED, + void *pixels, + int *error) +{ + Evas_Loader_Internal *loader; + Eina_File *f; + void *map; + Eina_Bool val = EINA_FALSE; + + loader = (Evas_Loader_Internal *)loader_data; + f = loader->f; + + map = eina_file_map_all(f, EINA_FILE_WILLNEED); + if (!map) + { + *error = EVAS_LOAD_ERROR_DOES_NOT_EXIST; + goto on_error; + } + + val = evas_image_load_file_data_avif_internal(loader, + pixels, + map, eina_file_size_get(f), + error); + + eina_file_map_free(f, map); + + on_error: + return val; +} + +static double +evas_image_load_frame_duration_avif(void *loader_data, + int start_frame, + int frame_num) +{ + Evas_Loader_Internal *loader; + Evas_Image_Animated *animated; + + loader = (Evas_Loader_Internal *)loader_data; + animated = loader->animated; + + if (!animated->animated) + return -1.0; + + if (frame_num < 0) + return -1.0; + + if ((start_frame + frame_num) > animated->frame_count) + return -1.0; + + if (frame_num < 1) + frame_num = 1; + + return loader->duration; +} + +static Evas_Image_Load_Func evas_image_load_avif_func = +{ + EVAS_IMAGE_LOAD_VERSION, + evas_image_load_file_open_avif, + evas_image_load_file_close_avif, + evas_image_load_file_head_avif, + NULL, + evas_image_load_file_data_avif, + evas_image_load_frame_duration_avif, + EINA_TRUE, + EINA_TRUE +}; + +static int +module_open(Evas_Module *em) +{ + if (!em) return 0; + + _evas_loader_avif_log_dom = eina_log_domain_register("evas-avif", EINA_COLOR_BLUE); + if (_evas_loader_avif_log_dom < 0) + { + EINA_LOG_ERR("Can not create a module log domain."); + return 0; + } + + em->functions = (void *)(&evas_image_load_avif_func); + + return 1; +} + +static void +module_close(Evas_Module *em EINA_UNUSED) +{ + if (_evas_loader_avif_log_dom >= 0) + { + eina_log_domain_unregister(_evas_loader_avif_log_dom); + _evas_loader_avif_log_dom = -1; + } +} + +static Evas_Module_Api evas_modapi = +{ + EVAS_MODULE_API_VERSION, + "avif", + "none", + { + module_open, + module_close + } +}; + +EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, avif); + +#ifndef EVAS_STATIC_BUILD_AVIF +EVAS_EINA_MODULE_DEFINE(image_loader, avif); +#endif + diff --git a/src/modules/evas/image_savers/avif/evas_image_save_avif.c b/src/modules/evas/image_savers/avif/evas_image_save_avif.c new file mode 100644 index 0000000000..61fefcefc7 --- /dev/null +++ b/src/modules/evas/image_savers/avif/evas_image_save_avif.c @@ -0,0 +1,181 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include + +#include "evas_common_private.h" +#include "evas_private.h" + + +static int +save_image_avif(RGBA_Image *im, const char *file, int quality) +{ + FILE *f; + avifRGBImage rgb; + avifRWData output; + avifImage *image; + avifEncoder * encoder; + avifPixelFormat format; + avifResult res; + size_t size; + int threads_count; + int quantizer; + int color; + int transfer; + int matrix; + int ret = 0; + + if (!im || !im->image.data || !file || !*file) + return 0; + + f = fopen(file, "wb"); + if (!f) + return ret; + + if (quality < 60) + { + format = AVIF_PIXEL_FORMAT_YUV420; +#if (AVIF_VERSION < 704) + matrix = AVIF_NCLX_MATRIX_COEFFICIENTS_BT601; + color = AVIF_NCLX_COLOUR_PRIMARIES_BT601; +#else + matrix = AVIF_MATRIX_COEFFICIENTS_BT601; + color = AVIF_COLOR_PRIMARIES_BT601; +#endif + } + else if (quality >= 90) + { + format = AVIF_PIXEL_FORMAT_YUV444; +#if (AVIF_VERSION < 704) + matrix = AVIF_NCLX_MATRIX_COEFFICIENTS_BT709; + color = AVIF_NCLX_COLOUR_PRIMARIES_BT709; +#else + matrix = AVIF_MATRIX_COEFFICIENTS_BT709; + color = AVIF_COLOR_PRIMARIES_BT709; +#endif + } + else + { + format = AVIF_PIXEL_FORMAT_YUV422; +#if (AVIF_VERSION < 704) + matrix = AVIF_NCLX_MATRIX_COEFFICIENTS_BT709; + color = AVIF_NCLX_COLOUR_PRIMARIES_BT709; +#else + matrix = AVIF_MATRIX_COEFFICIENTS_BT709; + color = AVIF_COLOR_PRIMARIES_BT709; +#endif + } + +#if (AVIF_VERSION < 704) + transfer = AVIF_NCLX_TRANSFER_CHARACTERISTICS_SRGB; +#else + transfer = AVIF_TRANSFER_CHARACTERISTICS_SRGB; +#endif + + image = avifImageCreate(im->cache_entry.w, im->cache_entry.h, 8, format); + if (!image) + goto close_f; + +#if (AVIF_VERSION < 704) + image->nclx.colourPrimaries = color; + image->nclx.transferCharacteristics = transfer; + image->nclx.matrixCoefficients = matrix; +#else + image->colorPrimaries = color; + image->transferCharacteristics = transfer; + image->matrixCoefficients = matrix; +#endif + image->yuvRange = AVIF_RANGE_FULL; + + avifRGBImageSetDefaults(&rgb, image); +#ifdef WORDS_BIGENDIAN + rgb.format = AVIF_RGB_FORMAT_ARGB; +#else + rgb.format = AVIF_RGB_FORMAT_BGRA; +#endif + rgb.depth = 8; + rgb.pixels = (uint8_t *)im->image.data; + rgb.rowBytes = 4 * im->cache_entry.w; + avifImageRGBToYUV(image, &rgb); + + output.data = NULL; + output.size = 0; + encoder = avifEncoderCreate(); + if (!encoder) + goto destroy_image; + + threads_count = 1; + if (eina_cpu_count() > 2) + threads_count = eina_cpu_count() - 1; + + quantizer = ((100 - quality) * AVIF_QUANTIZER_WORST_QUALITY) / 100; + + encoder->maxThreads = threads_count; + encoder->minQuantizer = quantizer; + encoder->maxQuantizer = quantizer; + res = avifEncoderWrite(encoder, image, &output); + + if (res != AVIF_RESULT_OK) + goto destroy_encoder; + + size = fwrite(output.data, output.size, 1, f); + if (size != output.size) + goto destroy_encoder; + + ret = 1; + + destroy_encoder: + avifEncoderDestroy(encoder); + avifRWDataFree(&output); + destroy_image: + avifImageDestroy(image); + close_f: + fclose(f); + + return ret; +} + +static int evas_image_save_file_avif(RGBA_Image *im, const char *file, const char *key EINA_UNUSED, + int quality, int compress EINA_UNUSED, const char *encoding EINA_UNUSED) +{ + return save_image_avif(im, file, quality); +} + + +static Evas_Image_Save_Func evas_image_save_avif_func = +{ + evas_image_save_file_avif +}; + +static int +module_open(Evas_Module *em) +{ + if (!em) return 0; + em->functions = (void *)(&evas_image_save_avif_func); + return 1; +} + +static void +module_close(Evas_Module *em EINA_UNUSED) +{ +} + +static Evas_Module_Api evas_modapi = +{ + EVAS_MODULE_API_VERSION, + "avif", + "none", + { + module_open, + module_close + } +}; + +EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_SAVER, image_saver, avif); + +#ifndef EVAS_STATIC_BUILD_AVIF +EVAS_EINA_MODULE_DEFINE(image_saver, avif); +#endif