diff --git a/src/modules/evas/image_loaders/jxl/evas_image_load_jxl.c b/src/modules/evas/image_loaders/jxl/evas_image_load_jxl.c new file mode 100644 index 0000000000..530c22e23d --- /dev/null +++ b/src/modules/evas/image_loaders/jxl/evas_image_load_jxl.c @@ -0,0 +1,526 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include + +#include +#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; + JxlParallelRunner *runner; + JxlDecoder *decoder; + double duration; +}; + +static int _evas_loader_jxl_log_dom = -1; + +#ifdef ERR +# undef ERR +#endif +#define ERR(...) EINA_LOG_DOM_ERR(_evas_loader_jxl_log_dom, __VA_ARGS__) + +#ifdef WRN +# undef WRN +#endif +#define WRN(...) EINA_LOG_DOM_WARN(_evas_loader_jxl_log_dom, __VA_ARGS__) + +#ifdef INF +# undef INF +#endif +#define INF(...) EINA_LOG_DOM_INFO(_evas_loader_jxl_log_dom, __VA_ARGS__) + +void _rgba_to_bgra(void *pixels, int size /* in pixels */) +{ + unsigned long long int *iter = pixels; + int i; + + for (i = 0; i < (size >> 1); i++, iter++) + { + *iter = + /* we keep A and G */ + (*iter & 0xff00ff00ff00ff00) | + /* we shift R */ + ((*iter & 0x000000ff000000ff) << 16) | + /* we shift B */ + ((*iter & 0x00ff000000ff0000) >> 16); + } +} + +static Eina_Bool +evas_image_load_file_head_jxl_internal(Evas_Loader_Internal *loader, + Emile_Image_Property *prop, + void *map, size_t length, + int *error) +{ + Evas_Image_Animated *animated; + JxlBasicInfo basic_info; + JxlFrameHeader frame_header; + JxlDecoder *decoder; + JxlDecoderStatus s; + JxlDecoderStatus st; + uint32_t frame_count = 0; + Eina_Bool ret; + + animated = loader->animated; + + ret = EINA_FALSE; + prop->w = 0; + prop->h = 0; + prop->alpha = EINA_FALSE; + + decoder = JxlDecoderCreate(NULL); + if (!decoder) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return ret; + } + + JxlDecoderSetKeepOrientation(decoder, JXL_TRUE); + + st = JxlDecoderSubscribeEvents(decoder, + JXL_DEC_BASIC_INFO | + JXL_DEC_FRAME); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not subscribe to JXL events"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + st = JxlDecoderSetInput(decoder, map, length); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not set JXL input"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto destroy_decoder; + } + + JxlDecoderCloseInput(decoder); + + /* First, JXL_DEC_BASIC_INFO event */ + st = JxlDecoderProcessInput(decoder); + if (st != JXL_DEC_BASIC_INFO) + { + ERR("Can not set JXL input (JXL_DEC_BASIC_INFO): %d", st); + *error = EVAS_LOAD_ERROR_GENERIC; + goto release_input; + } + + s = JxlDecoderGetBasicInfo(decoder, &basic_info); + if (s != JXL_DEC_SUCCESS) + { + ERR("Can not retrieve basic info"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto release_input; + } + + prop->w = basic_info.xsize; + prop->h = basic_info.ysize; + /* 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 release_input; + } + + prop->alpha = (basic_info.alpha_bits != 0); + + /* Then, JXL_DEC_FRAME event */ + + if (basic_info.have_animation) + { + frame_count = 0; + } + for (;;) + { + st = JxlDecoderProcessInput(decoder); + if (st == JXL_DEC_FRAME) + { + JxlDecoderGetFrameHeader(decoder, &frame_header); + frame_count++; + if (frame_header.is_last) + break; + } + } + + /* Finally, JXL_DEC_SUCCESS event */ + st = JxlDecoderProcessInput(decoder); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not set JXL input (JXL_DEC_SUCCESS)"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto release_input; + } + + if (basic_info.have_animation) + { + animated->loop_hint = basic_info.animation.num_loops ? EVAS_IMAGE_ANIMATED_HINT_NONE : EVAS_IMAGE_ANIMATED_HINT_LOOP; + animated->frame_count = frame_count; + animated->loop_count = basic_info.animation.num_loops; + animated->animated = EINA_TRUE; + loader->duration = ((double)frame_header.duration * (double)basic_info.animation.tps_denominator) / (double)basic_info.animation.tps_numerator; + } + + *error = EVAS_LOAD_ERROR_NONE; + ret = EINA_TRUE; + + release_input: + JxlDecoderReleaseInput(decoder); + destroy_decoder: + JxlDecoderDestroy(decoder); + + return ret; +} + +static Eina_Bool +evas_image_load_file_data_jxl_internal(Evas_Loader_Internal *loader, + Emile_Image_Property *prop, + void *pixels, + void *map, size_t length, + int *error) +{ + Evas_Image_Animated *animated; + JxlParallelRunner *runner; + JxlDecoder *decoder; + JxlPixelFormat pixel_format; + JxlDecoderStatus st; + size_t buffer_size; + Eina_Bool ret = EINA_FALSE; + + double t1, t2; + t1 = ecore_time_get(); + + animated = loader->animated; + + runner = loader->runner; + decoder = loader->decoder; + if (!runner || !decoder) + { + runner = JxlResizableParallelRunnerCreate(NULL); + if (!runner) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + goto on_error; + } + + decoder = JxlDecoderCreate(NULL); + if (!decoder) + { + *error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + goto on_error; + } + + st = JxlDecoderSetParallelRunner(decoder, + JxlResizableParallelRunner, + runner); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not set JXL runner"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + JxlResizableParallelRunnerSetThreads(runner, + JxlResizableParallelRunnerSuggestThreads(prop->w, prop->h)); + + JxlDecoderSetKeepOrientation(decoder, JXL_TRUE); + + st = JxlDecoderSetInput(decoder, map, length); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not set JXL input"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + JxlDecoderCloseInput(decoder); + + st = JxlDecoderSubscribeEvents(decoder, + JXL_DEC_FULL_IMAGE); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not subscribe to JXL events"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + } + + pixel_format.num_channels = 4; + pixel_format.data_type = JXL_TYPE_UINT8; +#ifdef WORDS_BIGENDIAN + pixel_format.endianness = JXL_BIG_ENDIAN; +#else + pixel_format.endianness = JXL_LITTLE_ENDIAN; +#endif + pixel_format.align = 0; + + if (animated->animated) + { + /* + * According to the libjxl devsn there is a better way than + * JxlDecoderSkipFrames(), but i can't... + */ + JxlDecoderSkipFrames(decoder, animated->cur_frame); + } + + st = JxlDecoderProcessInput(decoder); + if (animated->animated) + { + if (st == JXL_DEC_SUCCESS) + goto on_success; + } + + if (st != JXL_DEC_NEED_IMAGE_OUT_BUFFER) + { + ERR("Can not process JXL_DEC_NEED_IMAGE_OUT_BUFFER events (st=%d)", st); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + st = JxlDecoderImageOutBufferSize(decoder, + &pixel_format, + &buffer_size); + if (st != JXL_DEC_SUCCESS) + { + ERR("JxlDecoderImageOutBufferSize failed"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + if (buffer_size != (size_t)(prop->w * prop->h * 4)) + { + ERR("buffer size does not match image size"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + st = JxlDecoderSetImageOutBuffer(decoder, + &pixel_format, + pixels, + buffer_size); + if (st != JXL_DEC_SUCCESS) + { + ERR("Can not set image output buffer"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + st = JxlDecoderProcessInput(decoder); + if (st != JXL_DEC_FULL_IMAGE) + { + ERR("Can not process JXL_DEC_FULL_IMAGE events"); + *error = EVAS_LOAD_ERROR_GENERIC; + goto on_error; + } + + _rgba_to_bgra(pixels, prop->w * prop->h); + t2 = ecore_time_get(); + printf("time: %e\n", t2 - t1); + + on_success: + *error = EVAS_LOAD_ERROR_NONE; + ret = EINA_TRUE; + + on_error: + return ret; +} + +static void * +evas_image_load_file_open_jxl(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_jxl(void *loader_data) +{ + Evas_Loader_Internal *loader; + + loader = loader_data; + if (loader->decoder) + { + JxlDecoderReleaseInput(loader->decoder); + JxlDecoderDestroy(loader->decoder); + /* if decoder is valid, runner is necessarly valid */ + JxlResizableParallelRunnerDestroy(loader->runner); + } + free(loader_data); +} + +static Eina_Bool +evas_image_load_file_head_jxl(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_jxl_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_jxl(void *loader_data, + Evas_Image_Property *prop, + 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_jxl_internal(loader, + (Emile_Image_Property *)prop, + 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_jxl(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_jxl_func = +{ + EVAS_IMAGE_LOAD_VERSION, + evas_image_load_file_open_jxl, + evas_image_load_file_close_jxl, + evas_image_load_file_head_jxl, + NULL, + evas_image_load_file_data_jxl, + evas_image_load_frame_duration_jxl, + EINA_TRUE, + EINA_FALSE +}; + +static int +module_open(Evas_Module *em) +{ + if (!em) return 0; + + _evas_loader_jxl_log_dom = eina_log_domain_register("evas-jxl", EINA_COLOR_BLUE); + if (_evas_loader_jxl_log_dom < 0) + { + EINA_LOG_ERR("Can not create a module log domain."); + return 0; + } + + em->functions = (void *)(&evas_image_load_jxl_func); + + return 1; +} + +static void +module_close(Evas_Module *em EINA_UNUSED) +{ + if (_evas_loader_jxl_log_dom >= 0) + { + eina_log_domain_unregister(_evas_loader_jxl_log_dom); + _evas_loader_jxl_log_dom = -1; + } +} + +static Evas_Module_Api evas_modapi = +{ + EVAS_MODULE_API_VERSION, + "jxl", + "none", + { + module_open, + module_close + } +}; + +EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, jxl); + +#ifndef EVAS_STATIC_BUILD_JXL +EVAS_EINA_MODULE_DEFINE(image_loader, jxl); +#endif diff --git a/src/modules/evas/image_savers/jxl/evas_image_save_jxl.c b/src/modules/evas/image_savers/jxl/evas_image_save_jxl.c new file mode 100644 index 0000000000..c80f3c7446 --- /dev/null +++ b/src/modules/evas/image_savers/jxl/evas_image_save_jxl.c @@ -0,0 +1,213 @@ +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +#include +#include + +#include "evas_common_private.h" +#include "evas_private.h" + + +static int +save_image_jxl(RGBA_Image *im, const char *file, int quality) +{ + FILE *f; + JxlParallelRunner *runner; + JxlEncoder *encoder; + JxlPixelFormat pixel_format; + JxlBasicInfo basic_info; + JxlColorEncoding color_encoding; + JxlEncoderFrameSettings* frame_settings; + JxlEncoderStatus st; + JxlEncoderStatus process_result; + unsigned char *compressed; + unsigned char *next; + void *pixels; + unsigned long long int *iter_src; + unsigned long long int *iter_dst; + size_t size; + size_t avail; + size_t sz; + unsigned int i; + int ret = 0; + + if (!im || !im->image.data || !file || !*file) + return ret; + + f = fopen(file, "wb"); + if (!f) + return ret; + + runner = JxlResizableParallelRunnerCreate(NULL); + if (!runner) + goto close_f; + + encoder = JxlEncoderCreate(NULL); + if (!encoder) + goto destroy_runner; + + st = JxlEncoderSetParallelRunner(encoder, + JxlResizableParallelRunner, + runner); + if (st != JXL_ENC_SUCCESS) + goto destroy_encoder; + + JxlResizableParallelRunnerSetThreads(runner, + JxlResizableParallelRunnerSuggestThreads(im->cache_entry.w, im->cache_entry.h)); + + pixel_format.num_channels = 4; + pixel_format.data_type = JXL_TYPE_UINT8; +#ifdef WORDS_BIGENDIAN + pixel_format.endianness = JXL_BIG_ENDIAN; +#else + pixel_format.endianness = JXL_LITTLE_ENDIAN; +#endif + pixel_format.align = 0; + + JxlEncoderInitBasicInfo(&basic_info); + basic_info.xsize = im->cache_entry.w; + basic_info.ysize = im->cache_entry.h; + basic_info.bits_per_sample = 8; + basic_info.exponent_bits_per_sample = 0; + basic_info.uses_original_profile = JXL_FALSE; + basic_info.num_color_channels = 3; + basic_info.num_extra_channels =1; + basic_info.alpha_bits = 8; + st = JxlEncoderSetBasicInfo(encoder, &basic_info); + if (st != JXL_ENC_SUCCESS) + goto destroy_encoder; + + memset(&color_encoding, 0, sizeof(JxlColorEncoding)); + JxlColorEncodingSetToSRGB(&color_encoding, + /*is gray ? */ + pixel_format.num_channels < 3); + st = JxlEncoderSetColorEncoding(encoder, &color_encoding); + if (st != JXL_ENC_SUCCESS) + goto destroy_encoder; + + frame_settings = JxlEncoderFrameSettingsCreate(encoder, NULL); + if (!frame_settings) + goto destroy_encoder; + + st = JxlEncoderFrameSettingsSetOption(frame_settings, + JXL_ENC_FRAME_SETTING_EFFORT, + (quality * 7) /100); + if (st != JXL_ENC_SUCCESS) + goto destroy_encoder; + + /* conversion RGBA --> BGRA */ + pixels = malloc(4 * im->cache_entry.w * im->cache_entry.h); + if (!pixels) + goto destroy_encoder; + + iter_src = (unsigned long long int *)im->image.data; + iter_dst = (unsigned long long int *)pixels; + + for (i = 0; i < ((im->cache_entry.w * im->cache_entry.h) >> 1); i++, iter_src++, iter_dst++) + { + *iter_dst = + /* we keep A and G */ + (*iter_src & 0xff00ff00ff00ff00) | + /* we shift R */ + ((*iter_src & 0x000000ff000000ff) << 16) | + /* we shift B */ + ((*iter_src & 0x00ff000000ff0000) >> 16); + } + + st = JxlEncoderAddImageFrame(frame_settings, &pixel_format, + (void*)pixels, + sizeof(int) * im->cache_entry.w * im->cache_entry.h); + if (st != JXL_ENC_SUCCESS) + goto free_pixels; + + JxlEncoderCloseInput(encoder); + + size = 64; + compressed = (unsigned char *)malloc(size); + if (!compressed) + goto free_pixels; + + next = compressed; + avail = size - (next - compressed); + process_result = JXL_ENC_NEED_MORE_OUTPUT; + while (process_result == JXL_ENC_NEED_MORE_OUTPUT) + { + process_result = JxlEncoderProcessOutput(encoder, &next, &avail); + if (process_result == JXL_ENC_NEED_MORE_OUTPUT) + { + size_t offset = next - compressed; + size *= 2; + compressed = realloc(compressed, size); + next = compressed + offset; + avail = size - offset; + } + } + size = next - compressed; + compressed = realloc(compressed, size); + if (process_result != JXL_ENC_SUCCESS) + goto free_compressed; + + sz = fwrite(compressed, size, 1, f); + if (sz != 1) + goto free_compressed; + + ret = 1; + + free_compressed: + free(compressed); + free_pixels: + free(pixels); + destroy_encoder: + JxlEncoderDestroy(encoder); + destroy_runner: + JxlResizableParallelRunnerDestroy(runner); + close_f: + fclose(f); + + return ret; +} + +static int evas_image_save_file_jxl(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_jxl(im, file, quality); +} + + +static Evas_Image_Save_Func evas_image_save_jxl_func = +{ + evas_image_save_file_jxl +}; + +static int +module_open(Evas_Module *em) +{ + if (!em) return 0; + em->functions = (void *)(&evas_image_save_jxl_func); + return 1; +} + +static void +module_close(Evas_Module *em EINA_UNUSED) +{ +} + +static Evas_Module_Api evas_modapi = +{ + EVAS_MODULE_API_VERSION, + "jxl", + "none", + { + module_open, + module_close + } +}; + +EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_SAVER, image_saver, jxl); + +#ifndef EVAS_STATIC_BUILD_JXL +EVAS_EINA_MODULE_DEFINE(image_saver, jxl); +#endif