diff --git a/configure.ac b/configure.ac index 3642b96a65..a848345bda 100644 --- a/configure.ac +++ b/configure.ac @@ -1419,6 +1419,7 @@ ARG_ENABLE_EVAS_IMAGE_LOADER(Tiff, yes) ARG_ENABLE_EVAS_IMAGE_LOADER(WBMP, static) ARG_ENABLE_EVAS_IMAGE_LOADER(WEBP, no) ARG_ENABLE_EVAS_IMAGE_LOADER(XPM, static) +ARG_ENABLE_EVAS_IMAGE_LOADER(TGV, static) ### Default values @@ -1700,6 +1701,7 @@ EVAS_CHECK_IMAGE_LOADER([Tiff], [${want_evas_image_loader_tiff}]) EVAS_CHECK_IMAGE_LOADER([WBMP], [${want_evas_image_loader_wbmp}]) EVAS_CHECK_IMAGE_LOADER([WEBP], [${want_evas_image_loader_webp}]) EVAS_CHECK_IMAGE_LOADER([XPM], [${want_evas_image_loader_xpm}]) +EVAS_CHECK_IMAGE_LOADER([TGV], [${want_evas_image_loader_tgv}]) dnl Windows has no sigsetjmp function, nor equivalent. dnl So we disable the jpeg saver. diff --git a/m4/evas_check_loader.m4 b/m4/evas_check_loader.m4 index b34c28fc05..07d9cdd87e 100644 --- a/m4/evas_check_loader.m4 +++ b/m4/evas_check_loader.m4 @@ -341,6 +341,22 @@ AS_IF([test "x${have_dep}" = "xyes"], [$3], [$4]) ]) +dnl use: EVAS_CHECK_LOADER_DEP_TGV(loader, want_static[, ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) + +AC_DEFUN([EVAS_CHECK_LOADER_DEP_TGV], +[ + +have_dep="yes" +evas_image_loader_[]$1[]_cflags="" +evas_image_loader_[]$1[]_libs="" + +AC_SUBST([evas_image_loader_$1_cflags]) +AC_SUBST([evas_image_loader_$1_libs]) + +AS_IF([test "x${have_dep}" = "xyes"], [$3], [$4]) + +]) + dnl use: EVAS_CHECK_LOADER_DEP_SVG(loader, want_static[, ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) AC_DEFUN([EVAS_CHECK_LOADER_DEP_SVG], diff --git a/src/Makefile_Evas.am b/src/Makefile_Evas.am index 6132592ceb..5f07710a67 100644 --- a/src/Makefile_Evas.am +++ b/src/Makefile_Evas.am @@ -1712,6 +1712,58 @@ modules_evas_loaders_xpm_module_la_LIBTOOLFLAGS = --tag=disable-static endif endif +if BUILD_LOADER_TGV +if EVAS_STATIC_BUILD_TGV +lib_evas_libevas_la_SOURCES += \ +modules/evas/loaders/tgv/evas_image_load_tgv.c \ +static_libs/rg_etc/rg_etc1.c \ +static_libs/rg_etc/rg_etc1.h \ +static_libs/lz4/lz4.c \ +static_libs/lz4/lz4.h +lib_evas_libevas_la_CPPFLAGS += \ +-I$(top_srcdir)/src/static_libs/lz4 \ +-I$(top_srcdir)/src/static_libs/rg_etc \ +@evas_image_loader_tgv_cflags@ +lib_evas_libevas_la_LIBADD += @evas_image_loader_tgv_libs@ +if EVAS_CSERVE2 +bin_evas_evas_cserve2_slave_SOURCES += \ +modules/evas/loaders/tgv/evas_image_load_tgv.c \ +static_libs/rg_etc/rg_etc1.c \ +static_libs/rg_etc/rg_etc1.h \ +static_libs/lz4/lz4.c \ +static_libs/lz4/lz4.h +bin_evas_evas_cserve2_slave_CPPFLAGS += \ +-I$(top_builddir)/src/lib/efl \ +-I$(top_srcdir)/src/static_libs/lz4 \ +-I$(top_srcdir)/src/static_libs/rg_etc \ +-I$(top_srcdir)/src/lib/evas/ \ +@evas_image_loader_tgv_cflags@ +bin_evas_evas_cserve2_slave_LDADD += @evas_image_loader_tgv_libs@ +endif +else +loadertgvpkgdir = $(libdir)/evas/modules/loaders/tgv/$(MODULE_ARCH) +loadertgvpkg_LTLIBRARIES = modules/evas/loaders/tgv/module.la +modules_evas_loaders_tgv_module_la_SOURCES = \ +modules/evas/loaders/tgv/evas_image_load_tgv.c \ +static_libs/rg_etc/rg_etc1.c \ +static_libs/rg_etc/rg_etc1.h \ +static_libs/lz4/lz4.c \ +static_libs/lz4/lz4.h +modules_evas_loaders_tgv_module_la_CPPFLAGS = \ +-I$(top_builddir)/src/lib/efl \ +-I$(top_srcdir)/src/static_libs/lz4 \ +-I$(top_srcdir)/src/static_libs/rg_etc \ +-I$(top_srcdir)/src/lib/evas/ \ +@EVAS_CFLAGS@ \ +@evas_image_loader_tgv_cflags@ +modules_evas_loaders_tgv_module_la_LIBADD = \ +@USE_EVAS_LIBS@ \ +@evas_image_loader_tgv_libs@ +modules_evas_loaders_tgv_module_la_DEPENDENCIES = @USE_EVAS_INTERNAL_LIBS@ +modules_evas_loaders_tgv_module_la_LDFLAGS = -module @EFL_LTMODULE_FLAGS@ +modules_evas_loaders_tgv_module_la_LIBTOOLFLAGS = --tag=disable-static +endif +endif ### Unit tests diff --git a/src/lib/evas/common/evas_image_load.c b/src/lib/evas/common/evas_image_load.c index 92c6402f8c..52d3295ec4 100644 --- a/src/lib/evas/common/evas_image_load.c +++ b/src/lib/evas/common/evas_image_load.c @@ -64,6 +64,9 @@ static const struct ext_loader_s loaders[] = MATCHING(".cur", "ico"), MATCHING(".psd", "psd"), + + MATCHING(".tgv", "tgv"), + /* xcf - gefenric */ MATCHING(".xcf", "generic"), MATCHING(".xcf.gz", "generic"), diff --git a/src/lib/evas/file/evas_module.c b/src/lib/evas/file/evas_module.c index 28b4243cdd..6cb5dc66bd 100644 --- a/src/lib/evas/file/evas_module.c +++ b/src/lib/evas/file/evas_module.c @@ -140,6 +140,7 @@ EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, tiff); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, wbmp); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, webp); EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, xpm); +EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, tgv); #endif #if !EVAS_MODULE_NO_IMAGE_SAVERS @@ -232,6 +233,9 @@ static const struct { #ifdef EVAS_STATIC_BUILD_XPM EVAS_EINA_STATIC_MODULE_USE(image_loader, xpm), #endif +#ifdef EVAS_STATIC_BUILD_TGV + EVAS_EINA_STATIC_MODULE_USE(image_loader, tgv), +#endif #endif #if !EVAS_MODULE_NO_IMAGE_SAVERS #ifdef EVAS_STATIC_BUILD_EET diff --git a/src/modules/evas/loaders/tgv/evas_image_load_tgv.c b/src/modules/evas/loaders/tgv/evas_image_load_tgv.c new file mode 100644 index 0000000000..308d0b6f22 --- /dev/null +++ b/src/modules/evas/loaders/tgv/evas_image_load_tgv.c @@ -0,0 +1,359 @@ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_NETINET_IN_H +# include +#endif + +#ifdef _WIN32 +# include +#endif /* ifdef _WIN32 */ + +#include "lz4.h" +#include "rg_etc1.h" +#include "Evas_Loader.h" + +/************************************************************** + * The TGV file format is oriented around compression mecanism + * that hardware are good at decompressing. We do still provide + * a fully software implementation in case your hardware doesn't + * handle it. + * + * This file format is designed to compress/decompress things + * in block area. Giving opportunity to store really huge file + * and only decompress/compress them as we need. + * + * The file format is as follow : + * - char magic[4]: "TGV1" + * - uint8_t block_size (real block size = (4 << bits[0-3], 4 << bits[4-7]) + * - uint8_t algorithm (0 -> ETC1) + * - uint8_t options[2] (1 -> lz4) + * - uint32_t width + * - uint32_t height + * - blocks[] + * - 0 length encoded compress size (if length == 64 * block_size => no compression) + * - lzma encoded etc1 block + **************************************************************/ + +// FIXME: wondering if we should support mipmap +// FIXME: instead of the complete size, maybe just the usefull left over byte + number of block. + +typedef struct _Evas_Loader_Internal Evas_Loader_Internal; +struct _Evas_Loader_Internal +{ + Eina_File *f; + + Eina_Rectangle region; + + struct { + unsigned int width; + unsigned int height; + } block; + struct { + unsigned int width; + unsigned int height; + } size; + + Eina_Bool compress; +}; + + +static void * +evas_image_load_file_open_tgv(Eina_File *f, Eina_Stringshare *key EINA_UNUSED, + Evas_Image_Load_Opts *opts, + Evas_Image_Animated *animated EINA_UNUSED, + int *error) +{ + Evas_Loader_Internal *loader; + + loader = calloc(1, sizeof (Evas_Loader_Internal)); + if (!loader) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return NULL; + } + + if (eina_file_size_get(f) <= 16) + { + *error = EVAS_LOAD_ERROR_CORRUPT_FILE; + return NULL; + } + + loader->f = eina_file_dup(f); + if (!loader->f) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return NULL; + } + + if (opts && (opts->region.w > 0) && (opts->region.h > 0)) + { + EINA_RECTANGLE_SET(&loader->region, + opts->region.x, + opts->region.y, + opts->region.w, + opts->region.h); + } + else + { + EINA_RECTANGLE_SET(&loader->region, + 0, 0, + -1, -1); + } + + return loader; +} + + +static void +evas_image_load_file_close_tgv(void *loader_data) +{ + Evas_Loader_Internal *loader = loader_data; + + eina_file_close(loader->f); + free(loader); +} + +#define OFFSET_BLOCK_SIZE 4 +#define OFFSET_ALGORITHN 5 +#define OFFSET_OPTIONS 6 +#define OFFSET_WIDTH 8 +#define OFFSET_HEIGHT 12 +#define OFFSET_BLOCKS 16 + +static Eina_Bool +evas_image_load_file_head_tgv(void *loader_data, + Evas_Image_Property *prop, + int *error) +{ + Evas_Loader_Internal *loader = loader_data; + const char *m; + + m = eina_file_map_all(loader->f, EINA_FILE_SEQUENTIAL); + if (!m) + { + *error = EVAS_LOAD_ERROR_CORRUPT_FILE; + return EINA_FALSE; + } + + if (strncmp(m, "TGV1", 4) != 0) + { + *error = EVAS_LOAD_ERROR_CORRUPT_FILE; + return EINA_FALSE; + } + + loader->block.width = 4 << (m[OFFSET_BLOCK_SIZE] & 0x0f); + loader->block.height = 4 << ((m[OFFSET_BLOCK_SIZE] & 0xf0) >> 4); + + if (m[OFFSET_ALGORITHN] != 0) + { + *error = EVAS_LOAD_ERROR_CORRUPT_FILE; + return EINA_FALSE; + } + + loader->compress = m[OFFSET_OPTIONS] & 0x1; + + loader->size.width = ntohl(*((unsigned int*) &(m[OFFSET_WIDTH]))); + loader->size.height = ntohl(*((unsigned int*) &(m[OFFSET_HEIGHT]))); + + if (loader->region.w == -1 && + loader->region.h == -1) + { + loader->region.w = loader->size.width; + loader->region.h = loader->size.height; + } + else + { + Eina_Rectangle r; + + EINA_RECTANGLE_SET(&r, 0, 0, loader->size.width, loader->size.height); + if (!eina_rectangle_intersection(&loader->region, &r)) + { + *error = EVAS_LOAD_ERROR_GENERIC; + return EINA_FALSE; + } + } + + prop->w = loader->size.width; + prop->h = loader->size.height; + + return EINA_TRUE; +} + +static inline unsigned int +_tgv_length_get(const char *m, unsigned int length, unsigned int *offset) +{ + unsigned int r = 0; + unsigned int shift = 0; + + while (*offset < length && ((*m) & 0x80)) + { + r = r | (((*m) & 0x7F) << shift); + shift += 7; + m++; + (*offset)++; + } + if (*offset < length) + { + r = r | (((*m) & 0x7F) << shift); + (*offset)++; + } + + return r; +} + +Eina_Bool +evas_image_load_file_data_tgv(void *loader_data, + Evas_Image_Property *prop, + void *pixels, + int *error) +{ + Evas_Loader_Internal *loader = loader_data; + const char *m; + unsigned int *p = pixels; + char *buffer; + Eina_Rectangle master; + unsigned int block_length; + unsigned int length, offset; + unsigned int x, y; + unsigned int block_count; + Eina_Bool r = EINA_FALSE; + + length = eina_file_size_get(loader->f); + offset = OFFSET_BLOCKS; + + *error = EVAS_LOAD_ERROR_CORRUPT_FILE; + + m = eina_file_map_all(loader->f, EINA_FILE_WILLNEED); + if (!m) return EINA_FALSE; + + // By definition, prop{.w, .h} == region{.w, .h} + EINA_RECTANGLE_SET(&master, + loader->region.x, loader->region.y, + prop->w, prop->h); + + // Allocate space for each ETC1 block (64bytes per 4 * 4 pixels group) + block_count = loader->block.width * loader->block.height / (4 * 4); + if (loader->compress) + buffer = alloca(8 * block_count); + else + buffer = NULL; + + for (y = 0; y < loader->size.height; y += loader->block.height) + for (x = 0; x < loader->size.width; x += loader->block.width) + { + Eina_Rectangle current; + const char *data_start; + const char *it; + unsigned int expand_length; + unsigned int i, j; + + block_length = _tgv_length_get(m + offset, length, &offset); + + if (block_length == 0) goto on_error; + + data_start = m + offset; + offset += block_length; + + EINA_RECTANGLE_SET(¤t, x, y, + loader->block.width, loader->block.height); + + if (!eina_rectangle_intersection(¤t, &master)) + continue ; + + if (loader->compress) + { + expand_length = LZ4_uncompress(data_start, + buffer, block_count * 8); + // That's an overhead for now, need to be fixed + if (expand_length != block_length) + goto on_error; + } + else + { + buffer = (void*) data_start; + if (block_count * 8 != block_length) + goto on_error; + } + it = buffer; + + for (i = 0; i < loader->block.height; i += 4) + for (j = 0; j < loader->block.width; j += 4, it += 8) + { + Eina_Rectangle current_etc; + unsigned int temporary[4 * 4] = { 0 }; + unsigned int offset_x, offset_y; + int k; + + EINA_RECTANGLE_SET(¤t_etc, x + j, y + i, 4, 4); + + if (!eina_rectangle_intersection(¤t_etc, ¤t)) + continue ; + + if (!rg_etc1_unpack_block(it, temporary, 0)) + { + fprintf(stderr, "HOUSTON WE HAVE A PROBLEM ! Block starting at {%i, %i} is corrupted !\n", x + j, y + i); + continue ; + } + + offset_x = current_etc.x - x - j; + offset_y = current_etc.y - y - i; + for (k = 0; k < current_etc.h; k++) + { + memcpy(&p[current_etc.x + + (current_etc.y + k) * loader->region.w], + &temporary[offset_x + (offset_y + k) * 4], + current_etc.w * sizeof (unsigned int)); + } + } + } + + r = EINA_TRUE; + *error = EVAS_LOAD_ERROR_NONE; + + on_error: + eina_file_map_free(loader->f, (void*) m); + return r; +} + +Evas_Image_Load_Func evas_image_load_tgv_func = +{ + evas_image_load_file_open_tgv, + evas_image_load_file_close_tgv, + evas_image_load_file_head_tgv, + evas_image_load_file_data_tgv, + NULL, + EINA_TRUE, + EINA_FALSE +}; + +static int +module_open(Evas_Module *em) +{ + if (!em) return 0; + em->functions = (void *)(&evas_image_load_tgv_func); + return 1; +} + +static void +module_close(Evas_Module *em EINA_UNUSED) +{ +} + +static Evas_Module_Api evas_modapi = +{ + EVAS_MODULE_API_VERSION, + "tgv", + "none", + { + module_open, + module_close + } +}; + +EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, tgv); + +#ifndef EVAS_STATIC_BUILD_TGV +EVAS_EINA_MODULE_DEFINE(image_loader, tgv); +#endif