diff --git a/data/elementary/images/animated_webp_image.webp b/data/elementary/images/animated_webp_image.webp new file mode 100755 index 0000000000..5b44046e2c 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 index 0000000000..0da983e2ce 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 index 0000000000..24bc79ae72 --- /dev/null +++ b/src/examples/elementary/image_webp_example_01.c @@ -0,0 +1,38 @@ +//Compile with: +//gcc -g image_webp_example_01.c -o image_webp_example_01 `pkg-config --cflags --libs elementary` + +#include + +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 index 0000000000..3bfaf4a71c --- /dev/null +++ b/src/examples/elementary/image_webp_example_02.c @@ -0,0 +1,41 @@ +//Compile with: +//gcc -g image_webp_example_02.c -o image_webp_example_02 `pkg-config --cflags --libs elementary` + +#include + +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() diff --git a/src/examples/elementary/meson.build b/src/examples/elementary/meson.build index 7876285349..7abffa8a2f 100644 --- a/src/examples/elementary/meson.build +++ b/src/examples/elementary/meson.build @@ -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', diff --git a/src/lib/evas/meson.build b/src/lib/evas/meson.build index 3b49e2bea6..2196952d21 100644 --- a/src/lib/evas/meson.build +++ b/src/lib/evas/meson.build @@ -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', []] ] diff --git a/src/modules/evas/image_loaders/webp/evas_image_load_webp.c b/src/modules/evas/image_loaders/webp/evas_image_load_webp.c index bd082455a2..8026e0c880 100644 --- a/src/modules/evas/image_loaders/webp/evas_image_load_webp.c +++ b/src/modules/evas/image_loaders/webp/evas_image_load_webp.c @@ -5,10 +5,30 @@ #include #include #include +#include #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) { - return f; + 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 -evas_image_load_file_close_webp(void *loader_data EINA_UNUSED) +_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) +{ + // 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 +_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,39 +252,55 @@ evas_image_load_file_data_webp(void *loader_data, void *pixels, int *error) { - Eina_File *f = loader_data; - void *data = NULL; - void *decoded = NULL; - void *surface = NULL; - int width, height; + Loader_Info *loader = loader_data; + Evas_Image_Animated *animated = loader->animated; - data = eina_file_map_all(f, EINA_FILE_SEQUENTIAL); - - surface = pixels; - - 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; - if ((int) prop->w != width || - (int) prop->h != height) - goto free_data; + void *surface = NULL; + int width, height; + int index = 0; - // XXX: this copy of the surface is inefficient - memcpy(surface, decoded, width * height * 4); + 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; - free_data: - if (data) eina_file_map_free(f, data); - free(decoded); - return EINA_TRUE; } +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; + + if (!animated->animated) return -1.0; + if (frame_num < 0) return -1.0; + if (start_frame < 1) return -1.0; + + // Calculate Duration of Current Frame + Image_Frame *frame = _find_frame(loader, start_frame); + if (frame == NULL) return -1.0; + + return frame->delay; +} + static Evas_Image_Load_Func evas_image_load_webp_func = { EVAS_IMAGE_LOAD_VERSION, @@ -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 };