Evas: add avif evas loader and saver
authorVincent Torri <vincent.torri@gmail.com>
Wed, 15 Jul 2020 17:51:27 +0000 (18:51 +0100)
committerJongmin Lee <jm105.lee@samsung.com>
Wed, 15 Jul 2020 22:22:59 +0000 (07:22 +0900)
Summary:
Add AV1 image file loader and saver to Evas

The loader can be tested with this code :

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

#include <Eina.h>
#include <Ecore.h>
#include <Evas.h>
#include <Ecore_Evas.h>

static int i = 0;

static unsigned char _timer(void *data)
{
  Evas_Object *o = (Evas_Object *)data;

  if (i < evas_object_image_animated_frame_count_get(o))
    {
      evas_object_image_animated_frame_set(o, i);
      i++;
      return ECORE_CALLBACK_RENEW;
    }

  return ECORE_CALLBACK_DONE;
}

static void _quit(Ecore_Evas *ee)
{
  ecore_main_loop_quit();
  (void)ee;
}

int main(int argc, char *argv[])
{
  Ecore_Evas *ee;
  Evas *evas;
  Evas_Object *o;
  int w,h;
  Evas_Load_Error err;

  if (argc < 2)
    {
      printf("usage : %s file\n", argv[0]);
      return 1;
    }

  ecore_evas_init();

  ee = ecore_evas_new(NULL, 0, 0, 1, 1, NULL);
  if (!ee)
    {
      printf("no ee\n");
      return 0;
    }

  evas = ecore_evas_get(ee);
  ecore_evas_title_set(ee, "avif test");
  ecore_evas_callback_delete_request_set(ee, _quit);

  o = evas_object_image_add(evas);
  evas_object_image_file_set(o, argv[1], NULL);
  err = evas_object_image_load_error_get(o);
  if (err != EVAS_LOAD_ERROR_NONE)
    {
      fprintf(stderr, "could not load image '%s'. error string is \"%s\"\n",
              argv[1], evas_load_error_str(err));
      return 1;
    }

  evas_object_image_size_get(o, &w, &h);
  evas_object_image_fill_set(o, 0, 0, w, h);
  evas_object_move(o, 0, 0);
  evas_object_resize(o, w, h);
      evas_object_show(o);

  printf("animated : %s\n", evas_object_image_animated_get(o) ? "yes" : "no");
  fflush(stdout);

  if (evas_object_image_animated_get(o))
    {
      Ecore_Timer *timer;
      printf("frame count : %d\n", evas_object_image_animated_frame_count_get(o));
      printf("duration    : %f\n", evas_object_image_animated_frame_duration_get(o,1,0));
      printf("loop count  : %d\n", evas_object_image_animated_loop_count_get(o));
      fflush(stdout);

      timer = ecore_timer_add(evas_object_image_animated_frame_duration_get(o,1,0), _timer, o);
    }

  ecore_evas_resize(ee, w,  h);
  ecore_evas_show(ee);

  ecore_main_loop_begin();

  ecore_evas_shutdown();

  return 0;
}
```

non animated files : https://github.com/AOMediaCodec/libavif/tree/master/tests/data/originals
animated files : https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix/avifs

to test the saver :

```
#include <stdlib.h>
#include <stdio.h>
#include <math.h>

#include <Eina.h>
#include <Ecore.h>
#include <Evas.h>
#include <Ecore_Evas.h>

void _quit(Ecore_Evas *ee)
{
  ecore_main_loop_quit();
  (void)ee;
}

static Evas_Object *
display_data(int w, int h, const char *title, unsigned int *data)
{
  Ecore_Evas *ee;
  Evas *evas;
  Evas_Object *o;
  unsigned int *d;

  ee = ecore_evas_new(NULL, 0, 0, w, h, NULL);
  if (!ee)
    return NULL;

  evas = ecore_evas_get(ee);
  ecore_evas_title_set(ee, title);
  ecore_evas_callback_delete_request_set(ee, _quit);

  o = evas_object_image_add(evas);
  evas_object_image_fill_set(o, 0, 0, w, h);
  evas_object_image_size_set(o, w, h);

  d = evas_object_image_data_get(o, 1);
  for (int i = 0; i < w*h; i++)
    d[i] = data[i];
  evas_object_image_data_set(o, d);
  evas_object_image_data_update_add(o, 0, 0, w, h);
  evas_object_move(o, 0, 0);
  evas_object_resize(o, w, h);
  evas_object_show(o);

  ecore_evas_show(ee);

  return o;
}

static unsigned int *
display_file(const char *title, const char *filename, int *w, int *h)
{
  Ecore_Evas *ee;
  Evas *evas;
  Evas_Object *o;
  Evas_Load_Error err;
  unsigned int *data;

  ee = ecore_evas_new(NULL, 0, 0, 1, 1, NULL);
  if (!ee)
    return NULL;

  evas = ecore_evas_get(ee);
  ecore_evas_title_set(ee, title);
  ecore_evas_callback_delete_request_set(ee, _quit);

  o = evas_object_image_add(evas);
  evas_object_image_file_set(o, filename, NULL);
  err = evas_object_image_load_error_get(o);
  if (err != EVAS_LOAD_ERROR_NONE)
    {
      fprintf(stderr, "could not load image '%s'. error string is \"%s\"\n",
              filename, evas_load_error_str(err));
      fflush(stderr);
      return NULL;
    }

  evas_object_image_size_get(o, w, h);
  evas_object_image_fill_set(o, 0, 0, *w, *h);
  evas_object_image_size_set(o, *w, *h);
  evas_object_move(o, 0, 0);
  evas_object_resize(o, *w, *h);
  evas_object_show(o);

  ecore_evas_resize(ee, *w, *h);
  ecore_evas_show(ee);

  data = evas_object_image_data_get(o, 1);

  return data;
}

double psnr(int w, int h, unsigned int *data_orig, unsigned int *data)
{
  unsigned char *iter_orig;
  unsigned char *iter;
  double psnr;

  psnr = 0.0;
  iter_orig = (unsigned char *)data_orig;
  iter = (unsigned char *)data;
  for (int i = 0; i < 4 * w * h; i++, iter_orig++, iter++)
    psnr += (*iter_orig - *iter) * (*iter_orig - *iter);
  psnr /= 4 * w * h;
  psnr = 10 * log10(255.0 * 255.0 / psnr);
  return psnr;
}

void compare(int quality, int w, int h, unsigned int *data_orig)
{
  char title[1024];
  char filename[1024];
  unsigned char *data;
  unsigned int *data_jpeg;
  unsigned int *data_avif;
  unsigned char *iter_orig;
  unsigned char *iter_jpeg;
  unsigned char *iter_avif;
  double psnr_jpeg;
  double psnr_avif;
  Eina_File *f_jpeg;
  Eina_File *f_avif;
  size_t size_jpeg;
  size_t size_avif;

  /* jpeg */

  snprintf(title, sizeof(title), "jpeg test quality %d", quality);
  snprintf(filename, sizeof(filename), "test_%d.jpg", quality);
  data_jpeg = display_file(title, filename, &w, &h);
  if (!data_jpeg)
    return;

  f_jpeg = eina_file_open(filename, EINA_FALSE);
  size_jpeg = eina_file_size_get(f_jpeg);
  eina_file_close(f_jpeg);
  fprintf(stderr, "size : %u\n", (unsigned int)size_jpeg);
  fflush(stderr);

  /* avif */

  snprintf(title, sizeof(title), "avif test quality %d", quality);
  snprintf(filename, sizeof(filename), "test_%d.avif", quality);
  data_avif = display_file(title, filename, &w, &h);
  if (!data_avif)
    return;

  f_avif = eina_file_open(filename, EINA_FALSE);
  size_avif = eina_file_size_get(f_avif);
  eina_file_close(f_avif);
  fprintf(stderr, "size : %u\n", (unsigned int)size_avif);
  fflush(stderr);

  psnr_jpeg = psnr(w, h, data_orig, data_jpeg);
  fprintf(stderr, "psnr jpeg : %f\n", psnr_jpeg);
  fflush(stderr);

  snprintf(title, sizeof(title), "jpeg vs orig (psnr: %.2f, size: %u b)", psnr_jpeg, (unsigned int)size_jpeg);
  iter_orig = (unsigned char *)data_orig;
  iter_jpeg = (unsigned char *)data_jpeg;
  data = malloc(4*w*h);
  for (int i = 0; i < 4*w*h; i++, iter_orig++, iter_jpeg++)
    data[i] = abs(*iter_jpeg - *iter_orig);
  display_data(w, h, title, (unsigned int *)data);

  psnr_avif = psnr(w, h, data_orig, data_avif);
  fprintf(stderr, "psnr avif : %f\n", psnr_avif);
  fflush(stderr);

  snprintf(title, sizeof(title), "avif vs orig (psnr: %.2f, size: %u b)", psnr_avif, (unsigned int)size_avif);
  iter_orig = (unsigned char *)data_orig;
  iter_avif = (unsigned char *)data_avif;
  data = malloc(4*w*h);
  for (int i = 0; i < 4*w*h; i++, iter_orig++, iter_avif++)
    data[i] = abs(*iter_avif - *iter_orig);
  display_data(w, h, title, (unsigned int *)data);
}

int main()
{
  Ecore_Evas *ee;
  Evas *evas;
  Evas_Object *o;
  Evas_Load_Error err;
  unsigned int *data;
  int w,h;

  ecore_evas_init();

  ee = ecore_evas_new(NULL, 0, 0, 1, 1, NULL);
  if (!ee)
    return 1;

  evas = ecore_evas_get(ee);
  ecore_evas_title_set(ee, "original");
  ecore_evas_callback_delete_request_set(ee, _quit);

  o = evas_object_image_add(evas);
  evas_object_image_file_set(o, "x1d-II-sample-02.fff", NULL);
  err = evas_object_image_load_error_get(o);
  if (err != EVAS_LOAD_ERROR_NONE)
    {
      fprintf(stderr, "could not load image '%s'. error string is \"%s\"\n",
              "x1d-II-sample-02.fff", evas_load_error_str(err));
      fflush(stderr);
      return 1;
    }

  evas_object_image_size_get(o, &w, &h);
  evas_object_image_fill_set(o, 0, 0, w, h);
  evas_object_image_size_set(o, w, h);
  evas_object_move(o, 0, 0);
  evas_object_resize(o, w, h);
  evas_object_show(o);

  data = evas_object_image_data_get(o, 1);

  ecore_evas_resize(ee, w, h);
  ecore_evas_show(ee);

  /* evas_object_image_save(o, "test_100.jpg", NULL, "quality=100"); */

  evas_object_image_save(o, "test_90.jpg", NULL, "quality=90");
  /* evas_object_image_save(o, "test_70.jpg", NULL, "quality=70"); */
  /* evas_object_image_save(o, "test_50.jpg", NULL, "quality=50"); */

  /* evas_object_image_save(o, "test_100.avif", NULL, "quality=100"); */

  evas_object_image_save(o, "test_90.avif", NULL, "quality=90");
  /* evas_object_image_save(o, "test_70.avif", NULL, "quality=70"); */
  /* evas_object_image_save(o, "test_50.avif", NULL, "quality=50"); */

  compare(90, w, h, data);

  ecore_main_loop_begin();

  ecore_evas_shutdown();

  return 0;
}
```

the raw file canbe found here : https://www.hasselblad.com/learn/sample-images/

Test Plan: test executable with avif files found in libavif project

Reviewers: raster, q66

Reviewed By: q66

Subscribers: q66, cedric, #reviewers, #committers

Tags: #efl

Differential Revision: https://phab.enlightenment.org/D12051

meson_options.txt
po/POTFILES.in
src/lib/evas/common/evas_image_load.c
src/lib/evas/common/evas_image_save.c
src/lib/evas/file/evas_module.c
src/lib/evas/meson.build
src/modules/evas/image_loaders/avif/evas_image_load_avif.c [new file with mode: 0644]
src/modules/evas/image_savers/avif/evas_image_save_avif.c [new file with mode: 0644]

index 2f5d4ce..cb1f8a9 100644 (file)
@@ -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',
index 4776e4c..aefb381 100644 (file)
@@ -552,12 +552,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
index 1d28253..96caa16 100644 (file)
@@ -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
index 9a6a762..45718c5 100644 (file)
@@ -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)
index a1e8b62..83f1d80 100644 (file)
@@ -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
index 9e68a69..7a7319d 100644 (file)
@@ -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 (file)
index 0000000..34b6da9
--- /dev/null
@@ -0,0 +1,382 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <avif/avif.h>
+
+#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 (file)
index 0000000..61fefce
--- /dev/null
@@ -0,0 +1,181 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <avif/avif.h>
+
+#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