Support new animation encoding function for WebP 05/237605/24
authorjiyong.min <jiyong.min@samsung.com>
Wed, 1 Jul 2020 23:11:35 +0000 (08:11 +0900)
committerjiyong.min <jiyong.min@samsung.com>
Sun, 26 Jul 2020 23:23:58 +0000 (08:23 +0900)
Change-Id: I2f7f481ca2ce518284eee3b1ffb0208f479d8c00

CMakeLists.txt
packaging/libmm-utility.spec
webp/CMakeLists.txt [new file with mode: 0644]
webp/include/mm_util_webp.h [new file with mode: 0644]
webp/mm_util_webp.c [new file with mode: 0644]
webp/mmutil-webp.pc.in [new file with mode: 0644]
webp/test/CMakeLists.txt [new file with mode: 0644]
webp/test/mm_util_webp_testsuite.c [new file with mode: 0644]

index 86d803a..102e468 100644 (file)
@@ -22,6 +22,7 @@ ADD_SUBDIRECTORY(imgcv)
 ADD_SUBDIRECTORY(imgp)
 ADD_SUBDIRECTORY(jpeg)
 ADD_SUBDIRECTORY(magick)
+ADD_SUBDIRECTORY(webp)
 
 IF(UNIX)
 
index 72f7546..03dd1f8 100644 (file)
@@ -18,6 +18,8 @@ BuildRequires:  pkgconfig(gmodule-2.0)
 BuildRequires:  libjpeg-turbo-devel
 BuildRequires:  pkgconfig(libtzplatform-config)
 BuildRequires:  pkgconfig(opencv) >= 3.4.1
+BuildRequires:  pkgconfig(libwebp)
+BuildRequires:  pkgconfig(libwebpmux)
 BuildRequires:  giflib-devel
 BuildRequires:  pkgconfig(GraphicsMagick)
 %if 0%{?gtests:1}
diff --git a/webp/CMakeLists.txt b/webp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b5d4f26
--- /dev/null
@@ -0,0 +1,73 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
+SET(fw_name "mmutil_webp")
+
+PROJECT(${fw_name})
+
+SET(VERSION_MAJOR 0)
+SET(VERSION "${VERSION_MAJOR}.0.0")
+SET(CMAKE_INSTALL_PREFIX /usr)
+SET(PREFIX ${CMAKE_INSTALL_PREFIX})
+
+SET(INC_DIR
+    include
+)
+INCLUDE_DIRECTORIES(${INC_DIR}
+    ../common/include
+    ../magick/include
+)
+
+SET(dependents "dlog glib-2.0 libwebp libwebpmux")
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(${fw_name} REQUIRED ${dependents})
+FOREACH(flag ${${fw_name}_CFLAGS})
+    SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -fPIC -Wall -Werror")
+SET(CMAKE_C_FLAGS_DEBUG "-O0 -g")
+
+IF("${ARCH}" STREQUAL "arm")
+    ADD_DEFINITIONS("-DTARGET")
+ENDIF("${ARCH}" STREQUAL "arm")
+
+ADD_DEFINITIONS("-DPREFIX=\"${CMAKE_INSTALL_PREFIX}\"")
+ADD_DEFINITIONS("-DLIBPREFIX=\"${LIB_INSTALL_DIR}\"")
+ADD_DEFINITIONS("-DTIZEN_DEBUG")
+
+SET(CMAKE_EXE_LINKER_FLAGS "-Wl,--as-needed -Wl,--rpath=${LIB_INSTALL_DIR}")
+
+aux_source_directory(. SOURCES)
+ADD_LIBRARY(${fw_name} SHARED ${SOURCES})
+TARGET_LINK_LIBRARIES(${fw_name} ${${fw_name}_LDFLAGS} mmutil_common)
+SET_TARGET_PROPERTIES(${fw_name}
+    PROPERTIES
+    VERSION ${VERSION}
+    SOVERSION ${VERSION_MAJOR}
+    CLEAN_DIRECT_OUTPUT 1
+)
+
+INSTALL(TARGETS ${fw_name} DESTINATION ${LIB_INSTALL_DIR})
+INSTALL(
+        DIRECTORY ${INC_DIR}/ DESTINATION include/mmf
+        FILES_MATCHING
+        PATTERN "*_internal.h" EXCLUDE
+        PATTERN "*_private.h" EXCLUDE
+        PATTERN "${INC_DIR}/*.h"
+)
+
+SET(PC_NAME mmutil-webp)
+SET(PC_LDFLAGS -l${fw_name})
+SET(PC_FILE_NAME ${PC_NAME}.pc.in)
+
+CONFIGURE_FILE(
+    ${PC_FILE_NAME}
+    ${CMAKE_CURRENT_SOURCE_DIR}/${PC_NAME}.pc
+    @ONLY
+)
+INSTALL(FILES ${CMAKE_CURRENT_SOURCE_DIR}/${PC_NAME}.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig)
+
+ADD_SUBDIRECTORY(test)
+#IF(BUILD_GTESTS)
+#    ADD_SUBDIRECTORY(unittest)
+#ENDIF(BUILD_GTESTS)
diff --git a/webp/include/mm_util_webp.h b/webp/include/mm_util_webp.h
new file mode 100644 (file)
index 0000000..4d50f0c
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#ifndef __MM_UTIL_WEBP_H__
+#define __MM_UTIL_WEBP_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include "mm_util_type.h"
+
+
+typedef void *mm_util_webp_anim_enc_h;
+
+int mm_util_webp_anim_enc_create(mm_util_webp_anim_enc_h *anim_enc_h);
+int mm_util_webp_anim_enc_set_bgcolor(mm_util_webp_anim_enc_h anim_enc_h, unsigned char alpha, unsigned char red, unsigned char green, unsigned char blue);
+int mm_util_webp_anim_enc_set_loop_count(mm_util_webp_anim_enc_h anim_enc_h, unsigned int loop_count);
+int mm_util_webp_anim_enc_set_lossless(mm_util_webp_anim_enc_h anim_enc_h, bool lossless);
+int mm_util_webp_anim_enc_add_image(mm_util_webp_anim_enc_h anim_enc_h, mm_util_image_h image);
+int mm_util_webp_anim_enc_save_to_file(mm_util_webp_anim_enc_h anim_enc_h, const char *path);
+int mm_util_webp_anim_enc_save_to_buffer(mm_util_webp_anim_enc_h anim_enc_h, void **buf, size_t *buf_size);
+void mm_util_webp_anim_enc_destroy(mm_util_webp_anim_enc_h anim_enc_h);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif   /*__MM_UTIL_WEBP_H__*/
diff --git a/webp/mm_util_webp.c b/webp/mm_util_webp.c
new file mode 100644 (file)
index 0000000..3b8b118
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <stdio.h>
+
+#include <glib.h>
+#include <webp/encode.h>
+#include <webp/mux.h>
+
+#include "mm_util_webp.h"
+#include "mm_util_private.h"
+
+typedef struct {
+       WebPAnimEncoder *enc;
+       WebPMuxAnimParams anim_params;
+       bool lossless;
+       unsigned int timestamp;
+} mm_util_webp_anim_enc_t;
+
+
+static int __webp_anim_enc_init(mm_util_webp_anim_enc_t *anim_enc, mm_image_info_s *image)
+{
+       WebPAnimEncoderOptions enc_options;
+
+       mm_util_retvm_if(!anim_enc, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc");
+       mm_util_retvm_if(!image, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid image_info");
+
+       mm_util_retvm_if(!WebPAnimEncoderOptionsInit(&enc_options), MM_UTIL_ERROR_INVALID_OPERATION, "Error! WebPAnimEncoderOptionsInit");
+
+       anim_enc->enc = WebPAnimEncoderNew(image->width, image->height, &enc_options);
+       mm_util_retvm_if(!anim_enc->enc, MM_UTIL_ERROR_INVALID_OPERATION, "Error! WebPAnimEncoderNew");
+
+       anim_enc->timestamp = 0;
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+static void __webp_anim_enc_deinit(mm_util_webp_anim_enc_t *anim_enc)
+{
+       if (!anim_enc)
+               return;
+
+       WebPAnimEncoderDelete(anim_enc->enc);
+       anim_enc->enc = NULL;
+}
+
+static int __mm_image_info_to_webp_picture(mm_image_info_s *image, WebPPicture *picture)
+{
+       size_t idx = 0;
+       unsigned char *ptr = NULL, *a = NULL, *r = NULL, *g = NULL, *b = NULL;
+       unsigned int bpp = 4;
+
+       mm_util_retvm_if(!image, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid image");
+       mm_util_retvm_if(!picture, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid picture");
+
+       picture->width = image->width;
+       picture->height = image->height;
+       // use ARGB because of libwebp recommendation(YUV incurs loss).
+       picture->use_argb = 1;
+
+       mm_util_retvm_if(!WebPPictureAlloc(picture), MM_UTIL_ERROR_INVALID_OPERATION, "Error! WebPPictureAlloc");
+
+       // convert 'unsigned char'(LSB) type to 'uint32_t'(MSB) type
+       // If we did not change the data type, the color of encoded webp was broken.
+       mm_util_debug("image colorspace: %d", image->color);
+
+       // make uint32_t(MSB) 'ARGB' from unsigned char(LSB) 'ARGB/BGRA/RGBA'.
+       ptr = (unsigned char*)(image->data);
+       switch(image->color) {
+       case MM_UTIL_COLOR_ARGB:
+               a = ptr;
+               r = ptr + 1;
+               g = ptr + 2;
+               b = ptr + 3;
+               bpp = 4;
+               break;
+       case MM_UTIL_COLOR_BGRA:
+               a = ptr + 3;
+               r = ptr + 2;
+               g = ptr + 1;
+               b = ptr;
+               bpp = 4;
+               break;
+       case MM_UTIL_COLOR_RGBA:
+               a = ptr + 3;
+               r = ptr;
+               g = ptr + 1;
+               b = ptr + 2;
+               bpp = 4;
+               break;
+       default:
+               mm_util_error("not supported colorspace %d", image->color);
+               WebPPictureFree(picture);
+               return MM_UTIL_ERROR_NOT_SUPPORTED_FORMAT;
+       }
+
+       while(idx < (size_t)(picture->argb_stride * picture->height)) {
+               picture->argb[idx++] = ((*a << 24) | (*r << 16) | (*g << 8) | (*b));
+
+               a += bpp;
+               r += bpp;
+               g += bpp;
+               b += bpp;
+       }
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+static int __mm_util_webp_anim_enc_save(mm_util_webp_anim_enc_t *anim_enc, WebPData *webp_data)
+{
+       WebPMux *mux = NULL;
+       WebPMuxError err = WEBP_MUX_OK;
+
+       mm_util_retvm_if(!anim_enc, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc");
+       mm_util_retvm_if(!webp_data, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid webp_data");
+
+       mm_util_fenter();
+
+       // add NULL to end animation
+       if (!WebPAnimEncoderAdd(anim_enc->enc, NULL, anim_enc->timestamp, NULL)) {
+               mm_util_error("Error! WebPAnimEncoderAdd(%s)", WebPAnimEncoderGetError(anim_enc->enc));
+               return MM_UTIL_ERROR_INVALID_OPERATION;
+       }
+       if (!WebPAnimEncoderAssemble(anim_enc->enc, webp_data)) {
+               mm_util_error("Error! WebPAnimEncoderAssemble(%s)", WebPAnimEncoderGetError(anim_enc->enc));
+               return MM_UTIL_ERROR_INVALID_OPERATION;
+       }
+
+       // add animation container into webp
+       mux = WebPMuxCreate(webp_data, 1);
+       if (!mux) {
+               mm_util_error("Error! WebPMuxCreate");
+               return MM_UTIL_ERROR_INVALID_OPERATION;
+       }
+
+       err = WebPMuxSetAnimationParams(mux, &anim_enc->anim_params);
+       if (err != WEBP_MUX_OK) {
+               mm_util_error("Error! WebPMuxSetAnimationParams(%d)", err);
+               WebPMuxDelete(mux);
+               return MM_UTIL_ERROR_INVALID_OPERATION;
+       }
+
+       err = WebPMuxAssemble(mux, webp_data);
+       if (err != WEBP_MUX_OK) {
+               mm_util_error("Error! WebPMuxAssemble(%d)", err);
+               WebPMuxDelete(mux);
+               return MM_UTIL_ERROR_INVALID_OPERATION;
+       }
+
+       WebPMuxDelete(mux);
+
+       mm_util_fleave();
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+int mm_util_webp_anim_enc_create(mm_util_webp_anim_enc_h *anim_enc_h)
+{
+       mm_util_webp_anim_enc_t *anim_enc = NULL;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       anim_enc = g_new0(mm_util_webp_anim_enc_t, 1);
+       anim_enc->anim_params.bgcolor = 0xFFFFFFFF;     // white
+       anim_enc->anim_params.loop_count = 0;           // infinite
+       anim_enc->lossless = false;
+
+       *anim_enc_h = anim_enc;
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+int mm_util_webp_anim_enc_set_bgcolor(mm_util_webp_anim_enc_h anim_enc_h, unsigned char alpha, unsigned char red, unsigned char green, unsigned char blue)
+{
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       // background color of the animation, default is white
+       anim_enc->anim_params.bgcolor = (uint32_t)((alpha << 24) | (red << 16) | (green << 8) | blue);
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+int mm_util_webp_anim_enc_set_loop_count(mm_util_webp_anim_enc_h anim_enc_h, unsigned int loop_count)
+{
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       // number of times to repeat the animation, default is 0[infinite]
+       anim_enc->anim_params.loop_count = (int)loop_count;
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+int mm_util_webp_anim_enc_set_lossless(mm_util_webp_anim_enc_h anim_enc_h, bool lossless)
+{
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       // lossless, default is false due to performance
+       anim_enc->lossless = lossless;
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+int mm_util_webp_anim_enc_add_image(mm_util_webp_anim_enc_h anim_enc_h, mm_util_image_h image)
+{
+       int ret = MM_UTIL_ERROR_NONE;
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+       mm_image_info_s *frame = (mm_image_info_s *)image;
+       WebPPicture picture;
+       WebPConfig config;
+
+       mm_util_fenter();
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+       mm_util_retvm_if(!image, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid image");
+
+       mm_util_retvm_if(!WebPPictureInit(&picture), MM_UTIL_ERROR_INVALID_OPERATION, "Error! WebPPictureInit");
+       mm_util_retvm_if(!WebPConfigInit(&config), MM_UTIL_ERROR_INVALID_OPERATION, "Error! WebPConfigInit");
+
+       config.lossless = anim_enc->lossless;
+
+       ret = __mm_image_info_to_webp_picture(frame, &picture);
+       mm_util_retvm_if(ret != MM_UTIL_ERROR_NONE, ret, "Error! __mm_image_info_to_webp_picture(%d)", ret);
+
+       // if first frame, create animation container of webp
+       if (!anim_enc->enc) {
+               ret = __webp_anim_enc_init(anim_enc, frame);
+               if (ret != MM_UTIL_ERROR_NONE) {
+                       mm_util_error("Error! __webp_anim_enc_init(%d)", ret);
+                       WebPPictureFree(&picture);
+                       return ret;
+               }
+       }
+
+       // add picture into animation container
+       if (!WebPAnimEncoderAdd(anim_enc->enc, &picture, (int)anim_enc->timestamp, &config)) {
+               mm_util_error("Error! WebPAnimEncoderAdd(%s)", WebPAnimEncoderGetError(anim_enc->enc));
+               __webp_anim_enc_deinit(anim_enc);
+               ret = MM_UTIL_ERROR_INVALID_OPERATION;
+       } else {
+               // convert the delay between frames to timestamp of each frame
+               anim_enc->timestamp += frame->delay_time;
+       }
+
+       WebPPictureFree(&picture);
+
+       mm_util_fleave();
+
+       return ret;
+}
+
+int mm_util_webp_anim_enc_save_to_file(mm_util_webp_anim_enc_h anim_enc_h, const char *path)
+{
+       int ret = MM_UTIL_ERROR_NONE;
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+       WebPData webp_data;
+       GError *g_error = NULL;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       WebPDataInit(&webp_data);
+
+       ret = __mm_util_webp_anim_enc_save(anim_enc, &webp_data);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               mm_util_error("Error! __mm_util_webp_anim_enc_save(%d)", ret);
+               goto END;
+       }
+
+       if (!g_file_set_contents(path, (gchar *)webp_data.bytes, (gssize)webp_data.size, &g_error))
+               mm_util_error("Error! g_file_set_contents(%s)", (g_error ? g_error->message : "none"));
+
+       if (g_error)
+               g_error_free(g_error);
+END:
+
+       WebPDataClear(&webp_data);
+       __webp_anim_enc_deinit(anim_enc);
+
+       return ret;
+}
+
+int mm_util_webp_anim_enc_save_to_buffer(mm_util_webp_anim_enc_h anim_enc_h, void **buf, size_t *buf_size)
+{
+       int ret = MM_UTIL_ERROR_NONE;
+       mm_util_webp_anim_enc_t *anim_enc = (mm_util_webp_anim_enc_t *)anim_enc_h;
+       WebPData webp_data;
+
+       mm_util_retvm_if(!anim_enc_h, MM_UTIL_ERROR_INVALID_PARAMETER, "invalid anim_enc_h");
+
+       WebPDataInit(&webp_data);
+
+       ret = __mm_util_webp_anim_enc_save(anim_enc, &webp_data);
+       if (ret == MM_UTIL_ERROR_NONE) {
+               *buf = g_memdup(webp_data.bytes, webp_data.size);
+               *buf_size = webp_data.size;
+       } else {
+               mm_util_error("Error! __mm_util_webp_anim_enc_save(%d)", ret);
+       }
+
+       WebPDataClear(&webp_data);
+       __webp_anim_enc_deinit(anim_enc);
+
+       return MM_UTIL_ERROR_NONE;
+}
+
+void mm_util_webp_anim_enc_destroy(mm_util_webp_anim_enc_h anim_enc_h)
+{
+       if (!anim_enc_h)
+               return;
+
+       __webp_anim_enc_deinit((mm_util_webp_anim_enc_t *)anim_enc_h);
+       g_free(anim_enc_h);
+}
diff --git a/webp/mmutil-webp.pc.in b/webp/mmutil-webp.pc.in
new file mode 100644 (file)
index 0000000..91f801d
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@PREFIX@
+exec_prefix=/usr
+libdir=@LIB_INSTALL_DIR@
+includedir=/usr/include/mmf
+
+Name : @PC_NAME@
+Description : Multimedia Framework Utility Library
+Requires :
+Version : @VERSION@
+Libs : -L${libdir} @PC_LDFLAGS@
+Cflags : -I${includedir}
diff --git a/webp/test/CMakeLists.txt b/webp/test/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b459d27
--- /dev/null
@@ -0,0 +1,18 @@
+SET(fw_name "mmutil_webp")
+SET(fw_test "${fw_name}-test")
+
+INCLUDE(FindPkgConfig)
+pkg_check_modules(${fw_test} REQUIRED glib-2.0 dlog libtzplatform-config)
+FOREACH(flag ${${fw_test}_CFLAGS})
+    SET(EXTRA_CFLAGS "${EXTRA_CFLAGS} ${flag}")
+ENDFOREACH(flag)
+
+SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EXTRA_CFLAGS} -Wall -fPIE")
+
+aux_source_directory(. sources)
+FOREACH(src ${sources})
+    GET_FILENAME_COMPONENT(src_name ${src} NAME_WE)
+    MESSAGE("${src_name}")
+    ADD_EXECUTABLE(${src_name} ${src})
+    TARGET_LINK_LIBRARIES(${src_name} ${fw_name} ${${fw_test}_LDFLAGS} mmutil_common mmutil_magick)
+ENDFOREACH()
diff --git a/webp/test/mm_util_webp_testsuite.c b/webp/test/mm_util_webp_testsuite.c
new file mode 100644 (file)
index 0000000..fbf71b6
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+* Copyright (c) 2020 Samsung Electronics Co., Ltd All Rights Reserved
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <glib.h>
+#include <limits.h>
+
+#include <mm_util_image.h>
+#include <mm_util_magick.h>
+#include <mm_util_webp.h>
+#include <tzplatform_config.h>
+
+#define TEST_STRING_VALID(str) ((str != NULL && strlen(str) > 0) ? TRUE : FALSE)
+
+#define ENCODE_FILE_PATH       tzplatform_mkpath(TZ_USER_CONTENT, "webp_test_enc_file.webp")
+#define ENCODE_MEM_PATH                tzplatform_mkpath(TZ_USER_CONTENT, "webp_test_enc_mem.webp")
+
+static char *g_path = NULL;
+
+/* for reading files */
+static GQueue *g_queue_files = NULL;
+static GQueue *g_queue_images = NULL;
+
+static const mm_util_color_format_e g_color = MM_UTIL_COLOR_ARGB;
+static const unsigned int g_delay_time = 500;  // 500ms
+static const int g_loop_count = 0;  // if loop_count is 0, it is infinite loop
+static uint8_t g_bgcolor[] = { 0xFF, 0x00, 0x00, 0x00 };  // black
+
+static void __decode_func(gpointer data, gpointer user_data)
+{
+       int ret = MM_UTIL_ERROR_NONE;
+       char *path = (char *)data;
+       mm_util_image_h image = NULL;
+
+       if (!TEST_STRING_VALID(path)) {
+               g_print("invalid path %s\n", path);
+               return;
+       }
+
+       ret = mm_util_decode_image_from_file(path, g_color, &image);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               g_print("mm_util_decode_image_from_file failed %d\n", ret);
+               return;
+       }
+
+       ret = mm_image_set_delay_time(image, g_delay_time);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               g_print("mm_image_set_delay_time failed %d\n", ret);
+               return;
+       }
+
+       g_queue_push_tail(g_queue_images, image);
+}
+
+static gboolean __decode_files_in_queue()
+{
+       if (g_queue_images)
+               g_queue_free_full(g_queue_images, mm_image_destroy_image);
+
+       g_queue_images = g_queue_new();
+
+       g_queue_foreach(g_queue_files, __decode_func, NULL);
+
+       if (g_queue_images->length == 0) {
+               g_print("No valid image\n");
+               return FALSE;
+       }
+
+       g_print("%u valid image have been decoded.\n", g_queue_images->length);
+
+       return TRUE;
+}
+
+static int __sort_compare(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+       const char *_a = (const char *)a;
+       const char *_b = (const char *)b;
+       if (strlen(_a) < strlen(_b))
+               return -1;
+
+       if (strlen(_a) > strlen(_b))
+               return 1;
+
+       return g_ascii_strcasecmp(_a, _b);
+}
+
+static gboolean __set_input_dir(const char *path)
+{
+       GDir *dir = NULL;
+       const gchar *filename = NULL;
+       GError *g_error = NULL;
+
+       dir = g_dir_open(path, 0, &g_error);
+       if (!dir) {
+               g_print("invalid dir %s (%s)\n", path, g_error ? g_error->message : "none");
+               g_error_free(g_error);
+               return FALSE;
+       }
+
+       if (g_queue_files)
+               g_queue_free_full(g_queue_files, g_free);
+
+       g_queue_files = g_queue_new();
+
+       /* push files of dir into queue */
+       while ((filename = g_dir_read_name(dir)) != NULL)
+               g_queue_insert_sorted(g_queue_files, g_strdup_printf("%s/%s", path, filename), __sort_compare, NULL);
+
+       g_dir_close(dir);
+
+       if (g_queue_files->length == 0) {
+               g_print("\tNo test file in directory(%s)!\n", path);
+               return FALSE;
+       }
+
+       /* decode files of dir */
+       if (!__decode_files_in_queue()) {
+               g_print("Fail to decode files from dir! %s\n", path);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void __print_help(const char *argv0)
+{
+       fprintf(stderr, "\t[usage] Encode animation webp with images\n");
+       fprintf(stderr, "\t\t1. encode : %s directory(has numeric named files)\n", argv0);
+       fprintf(stderr, "\t\t2. support jpeg/png/gif/bmp/webp files to animation webp\n");
+}
+
+static gboolean __get_arguments(int argc, char *argv[])
+{
+       g_path = g_strdup(argv[1]);
+
+       if (!TEST_STRING_VALID(g_path)) {
+               fprintf(stderr, "\t[WEBP_testsuite] invalid path %s\n", argv[1]);
+               return FALSE;
+       }
+
+       if (!__set_input_dir(g_path)) {
+               fprintf(stderr, "\t[WEBP_testsuite] __set_input_dir failed\n");
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static void __add_image(gpointer data, gpointer user_data)
+{
+       int ret = 0;
+
+       ret = mm_util_webp_anim_enc_add_image((mm_util_webp_anim_enc_h)user_data, (mm_util_image_h)data);
+       if (ret != MM_UTIL_ERROR_NONE)
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_add_image failed : %d\n", ret);
+}
+
+static gboolean __test_encode_to_file()
+{
+       int ret = 0;
+       mm_util_webp_anim_enc_h anim_enc = NULL;
+
+       ret = mm_util_webp_anim_enc_create(&anim_enc);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_create failed : %d\n", ret);
+               return FALSE;
+       }
+
+       ret = mm_util_webp_anim_enc_set_bgcolor(anim_enc, g_bgcolor[0], g_bgcolor[1], g_bgcolor[2], g_bgcolor[3]);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_set_bgcolor failed : %d\n", ret);
+               goto END;
+       }
+
+       ret = mm_util_webp_anim_enc_set_loop_count(anim_enc, g_loop_count);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_set_loop_count failed : %d\n", ret);
+               goto END;
+       }
+
+       g_queue_foreach(g_queue_images, __add_image, anim_enc);
+
+       ret = mm_util_webp_anim_enc_save_to_file(anim_enc, ENCODE_FILE_PATH);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_encode_webp_to_file failed : %d\n", ret);
+               goto END;
+       }
+
+END:
+       mm_util_webp_anim_enc_destroy(anim_enc);
+
+       if (ret != MM_UTIL_ERROR_NONE)
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean __test_encode_to_buffer()
+{
+       int ret = 0;
+       mm_util_webp_anim_enc_h anim_enc = NULL;
+       /* for encoding webp to memory */
+       void *encoded_data = NULL;
+       size_t encoded_size = 0;
+       GError *g_error = NULL;
+
+       ret = mm_util_webp_anim_enc_create(&anim_enc);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_create failed : %d\n", ret);
+               return FALSE;
+       }
+
+       ret = mm_util_webp_anim_enc_set_bgcolor(anim_enc, g_bgcolor[0], g_bgcolor[1], g_bgcolor[2], g_bgcolor[3]);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_set_bgcolor failed : %d\n", ret);
+               goto END;
+       }
+
+       ret = mm_util_webp_anim_enc_set_loop_count(anim_enc, g_loop_count);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_set_loop_count failed : %d\n", ret);
+               goto END;
+       }
+
+       g_queue_foreach(g_queue_images, __add_image, anim_enc);
+
+       ret = mm_util_webp_anim_enc_save_to_buffer(anim_enc, &encoded_data, &encoded_size);
+       if (ret != MM_UTIL_ERROR_NONE) {
+               fprintf(stderr, "\t[WEBP_testsuite] mm_util_webp_anim_enc_save_to_buffer failed : %d\n", ret);
+               goto END;
+       }
+       if (!g_file_set_contents(ENCODE_MEM_PATH, (gchar *)encoded_data, (gssize)encoded_size, &g_error)) {
+               fprintf(stderr, "\t[WEBP_testsuite] g_file_set_contents failed : %s\n", ENCODE_MEM_PATH);
+               if (g_error)
+                       g_error_free(g_error);
+               goto END;
+       }
+
+END:
+       mm_util_webp_anim_enc_destroy(anim_enc);
+
+       g_free(encoded_data);
+
+       if (ret != MM_UTIL_ERROR_NONE)
+               return FALSE;
+
+       return TRUE;
+}
+
+int main(int argc, char *argv[])
+{
+       if (argc < 2) {
+               __print_help(argv[0]);
+               return 0;
+       }
+
+       if (FALSE == __get_arguments(argc, argv)) {
+               fprintf(stderr, "\t[WEBP_testsuite] _get_arguments failed\n");
+               goto out;
+       }
+
+       /* test encoding animation webp to file */
+       if (FALSE == __test_encode_to_file()) {
+               fprintf(stderr, "\t[WEBP_testsuite] __test_encode_to_file failed\n");
+               goto out;
+       }
+
+       /* test encoding animation webp to buffer */
+       if (FALSE == __test_encode_to_buffer()) {
+               fprintf(stderr, "\t[WEBP_testsuite] __test_encode_to_buffer failed\n");
+               goto out;
+       }
+
+out:
+       g_free(g_path);
+       g_queue_free_full(g_queue_images, mm_image_destroy_image);
+       g_queue_free_full(g_queue_files, g_free);
+
+       return 0;
+}
+