Support WebP Animation Image Files
authorTaehyub Kim <taehyub.kim@samsung.com>
Fri, 29 May 2020 02:40:37 +0000 (11:40 +0900)
committerJongmin Lee <jm105.lee@samsung.com>
Sun, 31 May 2020 21:34:44 +0000 (06:34 +0900)
Summary:
Support WebP Animate Format Imaeg Files.
To support webp animation, apply webp animation decoder.

Test Plan:
1. compile src/exmaple/elementary/image_webp_example_01.c and 02.c
2. run the samples

Reviewers: Hermet, kimcinoo, jsuya, bu5hm4n

Reviewed By: Hermet, kimcinoo, jsuya

Subscribers: cedric, #reviewers, #committers

Tags: #efl

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

data/elementary/images/animated_webp_image.webp [new file with mode: 0755]
data/elementary/images/static_webp_image.webp [new file with mode: 0644]
src/examples/elementary/image_webp_example_01.c [new file with mode: 0644]
src/examples/elementary/image_webp_example_02.c [new file with mode: 0644]
src/examples/elementary/meson.build
src/lib/evas/meson.build
src/modules/evas/image_loaders/webp/evas_image_load_webp.c

diff --git a/data/elementary/images/animated_webp_image.webp b/data/elementary/images/animated_webp_image.webp
new file mode 100755 (executable)
index 0000000..5b44046
Binary files /dev/null and b/data/elementary/images/animated_webp_image.webp differ
diff --git a/data/elementary/images/static_webp_image.webp b/data/elementary/images/static_webp_image.webp
new file mode 100644 (file)
index 0000000..0da983e
Binary files /dev/null and b/data/elementary/images/static_webp_image.webp differ
diff --git a/src/examples/elementary/image_webp_example_01.c b/src/examples/elementary/image_webp_example_01.c
new file mode 100644 (file)
index 0000000..24bc79a
--- /dev/null
@@ -0,0 +1,38 @@
+//Compile with:
+//gcc -g image_webp_example_01.c -o image_webp_example_01 `pkg-config --cflags --libs elementary`
+
+#include <Elementary.h>
+
+int
+elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED)
+{
+   Evas_Object *win, *image;
+   char buf[PATH_MAX];
+
+   elm_app_info_set(elm_main, "elementary", "images/static_webp_image.webp");
+   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
+
+   win = elm_win_util_standard_add("WebP Image", "WebP Image");
+   elm_win_autodel_set(win, EINA_TRUE);
+
+   snprintf(buf, sizeof(buf), "%s/images/static_webp_image.webp", elm_app_data_dir_get());
+
+   image = elm_image_add(win);
+   if (!elm_image_file_set(image, buf, NULL))
+     {
+        printf("error: could not load image \"%s\"\n", buf);
+        return -1;
+     }
+
+   evas_object_size_hint_weight_set(image, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, image);
+   evas_object_show(image);
+
+   evas_object_resize(win, 320, 320);
+   evas_object_show(win);
+
+   elm_run();
+
+   return 0;
+}
+ELM_MAIN()
diff --git a/src/examples/elementary/image_webp_example_02.c b/src/examples/elementary/image_webp_example_02.c
new file mode 100644 (file)
index 0000000..3bfaf4a
--- /dev/null
@@ -0,0 +1,41 @@
+//Compile with:
+//gcc -g image_webp_example_02.c -o image_webp_example_02 `pkg-config --cflags --libs elementary`
+
+#include <Elementary.h>
+
+int
+elm_main(int argc EINA_UNUSED, char **argv EINA_UNUSED)
+{
+   Evas_Object *win, *image;
+   char buf[PATH_MAX];
+
+   elm_app_info_set(elm_main, "elementary", "images/animated_webp_image.webp");
+   elm_policy_set(ELM_POLICY_QUIT, ELM_POLICY_QUIT_LAST_WINDOW_CLOSED);
+
+   win = elm_win_util_standard_add("WebP Image", "WebP Image");
+   elm_win_autodel_set(win, EINA_TRUE);
+
+   snprintf(buf, sizeof(buf), "%s/images/animated_webp_image.webp", elm_app_data_dir_get());
+
+   image = elm_image_add(win);
+   if (!elm_image_file_set(image, buf, NULL))
+     {
+        printf("error: could not load image \"%s\"\n", buf);
+        return -1;
+     }
+
+   elm_image_animated_set(image, EINA_TRUE);
+   elm_image_animated_play_set(image, EINA_TRUE);
+
+   evas_object_size_hint_weight_set(image, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, image);
+   evas_object_show(image);
+
+   evas_object_resize(win, 320, 320);
+   evas_object_show(win);
+
+   elm_run();
+
+   return 0;
+}
+ELM_MAIN()
index 7876285..7abffa8 100644 (file)
@@ -46,6 +46,8 @@ examples = [
   'hoversel_example_01',
   'icon_example_01',
   'image_example_01',
+  'image_webp_example_01',
+  'image_webp_example_02',
   'index_example_01',
   'index_example_02',
   'inwin_example',
index 7178599..825d2a5 100644 (file)
@@ -8,6 +8,7 @@ png = dependency('libpng')
 tiff = dependency('libtiff-4', required: get_option('evas-loaders-disabler').contains('tiff') == false)
 giflib = cc.find_library('gif')
 webp = dependency('libwebp', required: get_option('evas-loaders-disabler').contains('webp') == false)
+webpdemux = dependency('libwebpdemux', required: get_option('evas-loaders-disabler').contains('webp') == false)
 libopenjp2 = dependency('libopenjp2', required: get_option('evas-loaders-disabler').contains('jp2k') == false)
 
 evas_image_loaders_file = [
@@ -25,7 +26,7 @@ evas_image_loaders_file = [
      ['tgv',     'shared', [rg_etc, lz4]],
      ['tiff',    'shared', [tiff]],
      ['wbmp',    'shared', []],
-     ['webp',    'shared', [webp]],
+     ['webp',    'shared', [webp, webpdemux]],
      ['xpm',     'shared', []]
 ]
 
index bd08245..8026e0c 100644 (file)
@@ -5,10 +5,30 @@
 #include <stdio.h>
 #include <string.h>
 #include <webp/decode.h>
+#include <webp/demux.h>
 
 #include "evas_common_private.h"
 #include "evas_private.h"
 
+typedef struct _Loader_Info
+{
+   Eina_File *f;
+   Evas_Image_Load_Opts *opts;
+   Evas_Image_Animated *animated;
+   WebPAnimDecoder *dec;
+   void *map;
+   Eina_Array *frames;
+}Loader_Info;
+
+// WebP Frame Information
+typedef struct _Image_Frame
+{
+   int index;
+   int timestamp;
+   double delay;
+   uint8_t *data;
+}Image_Frame;
+
 static Eina_Bool
 evas_image_load_file_check(Eina_File *f, void *map,
                           unsigned int *w, unsigned int *h, Eina_Bool *alpha,
@@ -38,16 +58,95 @@ evas_image_load_file_check(Eina_File *f, void *map,
 
 static void *
 evas_image_load_file_open_webp(Eina_File *f, Eina_Stringshare *key EINA_UNUSED,
-                              Evas_Image_Load_Opts *opts EINA_UNUSED,
-                              Evas_Image_Animated *animated EINA_UNUSED,
-                              int *error EINA_UNUSED)
+                              Evas_Image_Load_Opts *opts,
+                              Evas_Image_Animated *animated,
+                              int *error)
+{
+   Loader_Info *loader = calloc(1, sizeof (Loader_Info));
+   if (!loader)
+     {
+        *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
+        return NULL;
+     }
+   loader->f = eina_file_dup(f);
+   loader->opts = opts;
+   loader->animated = animated;
+   return loader;
+}
+
+static void
+_free_all_frame(Loader_Info *loader)
+{
+   Image_Frame *frame;
+
+   if (!loader->frames) return;
+
+   for (unsigned int i = 0; i < eina_array_count(loader->frames); ++i)
+     {
+        frame = eina_array_data_get(loader->frames, i);
+        if (frame->data)
+          {
+             free(frame->data);
+             frame->data = NULL;
+          }
+        free(frame);
+     }
+}
+
+
+static void
+evas_image_load_file_close_webp(void *loader_data)
 {
-   return f;
+   // Free Allocated Data
+   Loader_Info *loader = loader_data;
+   _free_all_frame(loader);
+   eina_array_free(loader->frames);
+   if (loader->dec) WebPAnimDecoderDelete(loader->dec);
+   if ((loader->map) && (loader->f))
+     eina_file_map_free(loader->f, loader->map);
+   if (loader->f) eina_file_close(loader->f);
+   free(loader);
 }
 
+
 static void
-evas_image_load_file_close_webp(void *loader_data EINA_UNUSED)
+_new_frame(Loader_Info *loader, uint8_t *data, int width, int height, int index,
+           int pre_timestamp, int cur_timestamp)
+{
+   // Allocate Frame Data
+   Image_Frame *frame;
+
+   frame = calloc(1, sizeof(Image_Frame));
+   if (!frame) return;
+
+   frame->data = calloc(width * height * 4, sizeof(uint8_t));
+   if (!frame->data)
+     {
+        free(frame);
+        return;
+     }
+
+   frame->index = index;
+   frame->timestamp = cur_timestamp;
+   frame->delay = ((double)(cur_timestamp - pre_timestamp)/1000.0);
+   memcpy(frame->data, data, width * height * 4);
+
+   eina_array_push(loader->frames, frame);
+}
+
+static Image_Frame *
+_find_frame(Loader_Info *loader, int index)
 {
+   // Find Frame
+   Image_Frame *frame;
+
+   if (!loader->frames) return NULL;
+
+   frame = eina_array_data_get(loader->frames, index - 1);
+   if (frame->index == index)
+     return frame;
+
+   return NULL;
 }
 
 static Eina_Bool
@@ -55,20 +154,96 @@ evas_image_load_file_head_webp(void *loader_data,
                               Emile_Image_Property *prop,
                               int *error)
 {
-   Eina_File *f = loader_data;
-   Eina_Bool r;
+   Loader_Info *loader = loader_data;
+   Evas_Image_Animated *animated = loader->animated;
+   Eina_File *f = loader->f;
    void *data;
 
    *error = EVAS_LOAD_ERROR_NONE;
 
    data = eina_file_map_all(f, EINA_FILE_RANDOM);
+   loader->map = data;
 
-   r = evas_image_load_file_check(f, data,
+   if (!evas_image_load_file_check(f, data,
                                  &prop->w, &prop->h, &prop->alpha,
-                                 error);
+                                 error))
+     {
+        ERR("Image File is Invalid");
+        *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
+        return EINA_FALSE;
+     }
 
-   if (data) eina_file_map_free(f, data);
-   return r;
+   // Init WebP Data
+   WebPData webp_data;
+   WebPDataInit(&webp_data);
+
+   // Assign Data
+   webp_data.bytes = data;
+   webp_data.size = eina_file_size_get(f);
+
+   // Set Decode Option
+   WebPAnimDecoderOptions dec_options;
+   WebPAnimDecoderOptionsInit(&dec_options);
+   dec_options.color_mode = MODE_BGRA;
+
+   // Create WebPAnimation Decoder
+   WebPAnimDecoder *dec = WebPAnimDecoderNew(&webp_data, &dec_options);
+   if (!dec)
+     {
+        ERR("WebP Decoder Creation is Failed");
+        *error = EVAS_LOAD_ERROR_GENERIC;
+        return EINA_FALSE;
+     }
+   loader->dec = dec;
+
+   // Get WebP Animation Info
+   WebPAnimInfo anim_info;
+   if (!WebPAnimDecoderGetInfo(dec, &anim_info))
+     {
+        ERR("Getting WebP Information is Failed");
+        *error = EVAS_LOAD_ERROR_GENERIC;
+        return EINA_FALSE;
+     }
+
+   uint8_t* buf;
+   int pre_timestamp = 0;
+   int cur_timestamp = 0;
+   int index = 1;
+
+   // Set Frame Array
+   loader->frames = eina_array_new(anim_info.frame_count);
+   if (!loader->frames)
+     {
+        ERR("Frame Array Allocation is Faild");
+        *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
+        return EINA_FALSE;
+     }
+
+   // Decode Frames
+   while (WebPAnimDecoderHasMoreFrames(dec))
+     {
+        if (!WebPAnimDecoderGetNext(dec, &buf, &cur_timestamp))
+          {
+             ERR("WebP Decoded Frame Get is Failed");
+             *error = EVAS_LOAD_ERROR_GENERIC;
+             return EINA_FALSE;
+          }
+        _new_frame(loader, buf, anim_info.canvas_width, anim_info.canvas_height, index,
+                   pre_timestamp, cur_timestamp);
+        pre_timestamp = cur_timestamp;
+        index++;
+     }
+
+   // Set Animation Info
+   if (anim_info.frame_count > 1)
+     {
+        animated->animated = 1;
+        animated->loop_count = anim_info.loop_count;
+        animated->loop_hint = EVAS_IMAGE_ANIMATED_HINT_LOOP;
+        animated->frame_count = anim_info.frame_count;
+     }
+
+   return EINA_TRUE;
 }
 
 static Eina_Bool
@@ -77,37 +252,53 @@ evas_image_load_file_data_webp(void *loader_data,
                               void *pixels,
                               int *error)
 {
-   Eina_File *f = loader_data;
-   void *data = NULL;
-   void *decoded = NULL;
+   Loader_Info *loader = loader_data;
+   Evas_Image_Animated *animated = loader->animated;
+
+   *error = EVAS_LOAD_ERROR_NONE;
+
    void *surface = NULL;
    int width, height;
+   int index = 0;
 
-   data = eina_file_map_all(f, EINA_FILE_SEQUENTIAL);
+   index = animated->cur_frame;
 
+   // Find Cur Frame
+   if (index == 0)
+     index = 1;
+   Image_Frame *frame = _find_frame(loader, index);
+   if (frame == NULL) return EINA_FALSE;
+
+   WebPAnimInfo anim_info;
+   WebPAnimDecoderGetInfo(loader->dec, &anim_info);
+   width = anim_info.canvas_width;
+   height = anim_info.canvas_height;
+
+   // Render Frame
    surface = pixels;
+   memcpy(surface, frame->data, width * height * 4);
+   prop->premul = EINA_TRUE;
 
-   decoded = WebPDecodeBGRA(data, eina_file_size_get(f), &width, &height);
-   if (!decoded)
-     {
-        *error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
-        goto free_data;
-     }
-   *error = EVAS_LOAD_ERROR_NONE;
+   return EINA_TRUE;
+}
 
-   if ((int) prop->w != width ||
-       (int) prop->h != height)
-     goto free_data;
+static double
+evas_image_load_frame_duration_webp(void *loader_data,
+                                    int start_frame,
+                                    int frame_num)
+{
+   Loader_Info *loader = loader_data;
+   Evas_Image_Animated *animated = loader->animated;
 
-   // XXX: this copy of the surface is inefficient
-   memcpy(surface, decoded, width * height * 4);
-   prop->premul = EINA_TRUE;
+   if (!animated->animated) return -1.0;
+   if (frame_num < 0) return -1.0;
+   if (start_frame < 1) return -1.0;
 
- free_data:
-   if (data) eina_file_map_free(f, data);
-   free(decoded);
+   // Calculate Duration of Current Frame
+   Image_Frame *frame = _find_frame(loader, start_frame);
+   if (frame == NULL) return -1.0;
 
-   return EINA_TRUE;
+   return frame->delay;
 }
 
 static Evas_Image_Load_Func evas_image_load_webp_func =
@@ -118,7 +309,7 @@ static Evas_Image_Load_Func evas_image_load_webp_func =
   (void*) evas_image_load_file_head_webp,
   NULL,
   (void*) evas_image_load_file_data_webp,
-  NULL,
+  evas_image_load_frame_duration_webp,
   EINA_TRUE,
   EINA_FALSE
 };