From 4ca8bfd15c2be8870320ceee86e5bb89a1a2d2d8 Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Tue, 17 Mar 2015 08:50:19 +0100 Subject: [PATCH] emile: add JPEG support. --- configure.ac | 2 + src/lib/emile/emile_image.c | 1285 ++++++++++++++++++++++++++++++++++- 2 files changed, 1276 insertions(+), 11 deletions(-) diff --git a/configure.ac b/configure.ac index 6b0b799214..0466b01147 100644 --- a/configure.ac +++ b/configure.ac @@ -1045,6 +1045,8 @@ EFL_LIB_START([Emile]) ### Checks for libraries +EFL_CHECK_LIBS([EMILE], [libjpeg]) + ## Compatibility layers EFL_PLATFORM_DEPEND([EMILE], [evil]) diff --git a/src/lib/emile/emile_image.c b/src/lib/emile/emile_image.c index 9f26a9aa00..9aa9a75653 100644 --- a/src/lib/emile/emile_image.c +++ b/src/lib/emile/emile_image.c @@ -6,6 +6,10 @@ # include #endif +#ifdef HAVE_EVIL +# include +#endif + #ifdef _WIN32 # include #endif /* ifdef _WIN32 */ @@ -16,6 +20,14 @@ # include "lz4.h" #endif +#ifdef _WIN32 +# define XMD_H /* This prevents libjpeg to redefine INT32 */ +#endif + +#include +#include +#include + #include "rg_etc1.h" #include "Emile.h" @@ -23,6 +35,45 @@ #include #endif +#define IMG_MAX_SIZE 65000 + +#define IMG_TOO_BIG(w, h) \ + ((((unsigned long long)w) * ((unsigned long long)h)) >= \ + ((1ULL << (29 * (sizeof(void *) / 4))) - 2048)) + +#define SPANS_COMMON(x1, w1, x2, w2) \ + (!(( (int)((x2) + (int)(w2)) <= (int)(x1)) || (int)((x2) >= (int)((x1) + (int)(w1))))) + +#define RECTS_INTERSECT(x, y, w, h, xx, yy, ww, hh) \ + ((SPANS_COMMON((x), (w), (xx), (ww))) && (SPANS_COMMON((y), (h), (yy), (hh)))) + +#define RECTS_CLIP_TO_RECT(_x, _y, _w, _h, _cx, _cy, _cw, _ch) \ + { \ + if (RECTS_INTERSECT(_x, _y, _w, _h, _cx, _cy, _cw, _ch)) \ + { \ + if ((int)_x < (int)(_cx)) \ + { \ + if ((int)_w + ((int)_x - (int)(_cx)) < 0) _w = 0; \ + else _w += ((int)_x - (int)(_cx)); \ + _x = (_cx); \ + } \ + if ((int)(_x + _w) > (int)((_cx) + (_cw))) \ + _w = (_cx) + (_cw) - _x; \ + if ((int)_y < (int)(_cy)) \ + { \ + if ((int)_h + ((int)_y - (int)(_cy)) < 0) _h = 0; \ + else _h += ((int)_y - (int)(_cy)); \ + _y = (_cy); \ + } \ + if ((int)(_y + _h) > (int)((_cy) + (_ch))) \ + _h = (_cy) + (_ch) - _y; \ + } \ + else \ + { \ + _w = 0; _h = 0; \ + } \ + } + #ifndef WORDS_BIGENDIAN /* x86 */ #define A_VAL(p) (((uint8_t *)(p))[3]) @@ -37,6 +88,9 @@ #define B_VAL(p) (((uint8_t *)(p))[3]) #endif +#define ARGB_JOIN(a,r,g,b) \ + (((a) << 24) + ((r) << 16) + ((g) << 8) + (b)) + #define OFFSET_BLOCK_SIZE 4 #define OFFSET_ALGORITHM 5 #define OFFSET_OPTIONS 6 @@ -147,6 +201,11 @@ static const Emile_Colorspace cspaces_etc1_alpha[2] = { EMILE_COLORSPACE_ARGB8888 }; +static const Emile_Colorspace cspaces_gry[2] = { + EMILE_COLORSPACE_GRY8, + EMILE_COLORSPACE_ARGB8888 +}; + /************************************************************** * The TGV file format is oriented around compression mecanism * that hardware are good at decompressing. We do still provide @@ -181,8 +240,8 @@ static const Emile_Colorspace cspaces_etc1_alpha[2] = { static Eina_Bool _emile_tgv_bind(Emile_Image *image, - Emile_Image_Load_Opts *opts, - Emile_Image_Animated *animated, + Emile_Image_Load_Opts *opts EINA_UNUSED, + Emile_Image_Animated *animated EINA_UNUSED, Emile_Image_Load_Error *error) { const unsigned char *m; @@ -220,6 +279,9 @@ _emile_tgv_head(Emile_Image *image, m = _emile_image_file_source_map(image, &length); if (!m) return EINA_FALSE; + // This can be used for later ABI change of the structure. + if (sizeof (Emile_Image_Property) != property_size) return EINA_FALSE; + switch (m[OFFSET_ALGORITHM] & 0xFF) { case 0: @@ -343,6 +405,8 @@ _emile_tgv_data(Emile_Image *image, return EINA_FALSE; } + if (sizeof (Emile_Image_Property) != property_size) return EINA_FALSE; + offset = OFFSET_BLOCKS; *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; @@ -412,7 +476,6 @@ _emile_tgv_data(Emile_Image *image, Eina_Rectangle current; Eina_Binbuf *data_start; const unsigned char *it; - unsigned int expand_length; unsigned int i, j; block_length = _tgv_length_get(m + offset, length, &offset); @@ -566,22 +629,692 @@ _emile_tgv_close(Emile_Image *image EINA_UNUSED) } /* JPEG Handling */ -static Eina_Bool -_emile_jpeg_bind(Emile_Image *image, - Emile_Image_Load_Opts *opts, - Emile_Image_Animated *animated, - Emile_Image_Load_Error *error) + +typedef struct _JPEG_error_mgr *emptr; +struct _JPEG_error_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +struct jpeg_membuf_src +{ + struct jpeg_source_mgr pub; + + const unsigned char *buf; + size_t len; + struct jpeg_membuf_src *self; +}; + +static void +_JPEGFatalErrorHandler(j_common_ptr cinfo) +{ + emptr errmgr; + + errmgr = (emptr) cinfo->err; + longjmp(errmgr->setjmp_buffer, 1); + return; +} + +static void +_JPEGErrorHandler(j_common_ptr cinfo EINA_UNUSED) +{ + return; +} + +static void +_JPEGErrorHandler2(j_common_ptr cinfo EINA_UNUSED, int msg_level EINA_UNUSED) +{ + return; +} + +static void +_emile_jpeg_membuf_src_init(j_decompress_ptr cinfo EINA_UNUSED) +{ +} + +static boolean +_emile_jpeg_membuf_src_fill(j_decompress_ptr cinfo) +{ + static const JOCTET jpeg_eoi[2] = { 0xFF, JPEG_EOI }; + struct jpeg_membuf_src *src = (struct jpeg_membuf_src *)cinfo->src; + + src->pub.bytes_in_buffer = sizeof(jpeg_eoi); + src->pub.next_input_byte = jpeg_eoi; + + return TRUE; +} + +static void +_emile_jpeg_membuf_src_skip(j_decompress_ptr cinfo, + long num_bytes) +{ + struct jpeg_membuf_src *src = (struct jpeg_membuf_src *)cinfo->src; + + if ((((long)src->pub.bytes_in_buffer - (long)src->len) > num_bytes) || + ((long)src->pub.bytes_in_buffer < num_bytes)) + { + (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)); + return; + } + src->pub.bytes_in_buffer -= num_bytes; + src->pub.next_input_byte += num_bytes; +} + +static void +_emile_jpeg_membuf_src_term(j_decompress_ptr cinfo) +{ + struct jpeg_membuf_src *src = (struct jpeg_membuf_src *)cinfo->src; + if (!src) return; + free(src); + cinfo->src = NULL; +} + +static int +_emile_jpeg_membuf_src(j_decompress_ptr cinfo, + const void *map, size_t length) +{ + struct jpeg_membuf_src *src; + + src = calloc(1, sizeof(*src)); + if (!src) + return -1; + + src->self = src; + + cinfo->src = &src->pub; + src->buf = map; + src->len = length; + src->pub.init_source = _emile_jpeg_membuf_src_init; + src->pub.fill_input_buffer = _emile_jpeg_membuf_src_fill; + src->pub.skip_input_data = _emile_jpeg_membuf_src_skip; + src->pub.resync_to_restart = jpeg_resync_to_restart; + src->pub.term_source = _emile_jpeg_membuf_src_term; + src->pub.bytes_in_buffer = src->len; + src->pub.next_input_byte = src->buf; + + return 0; +} + +/*! Magic number for EXIF header, App0, App1*/ +static const unsigned char ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00}; +static const unsigned char JfifHeader[] = {0x4A, 0x46, 0x49, 0x46, 0x00}; +static const unsigned char JfxxHeader[] = {0x4A, 0x46, 0x58, 0x58, 0x00}; +static const unsigned char App0[] = {0xff, 0xe0}; +static const unsigned char App1[] = {0xff, 0xe1}; +static const unsigned char II[] = {0x49, 0x49}; +static const unsigned char MM[] = {0x4d, 0x4d}; +typedef enum { + EXIF_BYTE_ALIGN_II, + EXIF_BYTE_ALIGN_MM +} ExifByteAlign; + +static Eina_Bool +_get_next_app0(const unsigned char *map, size_t fsize, size_t *position) +{ + unsigned short length = 0; + unsigned int w = 0, h = 0; + unsigned int format = 0; + unsigned int data_size = 0; + const unsigned char *app0_head, *p; + + /* header_mark:2, length:2, identifier:5 version:2, unit:1, den=4 thum=2 */ + if ((*position + 16) >= fsize) return EINA_FALSE; + app0_head = map + *position; + + /* p is appn's start pointer excluding app0 marker */ + p = app0_head + 2; + + length = ((*p << 8) + *(p + 1)); + + /* JFIF segment format */ + if (!memcmp(p + 2, JfifHeader, sizeof (JfifHeader))) + { + format = 3; + w = *(p + 14); + h = *(p + 15); + } + else if (!memcmp(p + 2, JfxxHeader, sizeof (JfxxHeader))) + { + if (*(p + 7) == 0x11) + format = 1; + else + format = 3; + w = *(p + 8); + h = *(p + 9); + } + + data_size = format * w * h; + + if ((*position + 2+ length + data_size) > fsize) + return EINA_FALSE; + + *position = *position + 2 + length + data_size; + + return EINA_TRUE; +} + +/* If app1 data is abnormal, returns EINA_FALSE. + If app1 data is normal, returns EINA_TRUE. + If app1 data is normal but not orientation data, orientation value is -1. + */ + +static Eina_Bool +_get_orientation_app1(const unsigned char *map, size_t fsize, size_t *position, + int *orientation_res, Eina_Bool *flipped) +{ + const unsigned char *app1_head, *buf; + unsigned char orientation[2]; + ExifByteAlign byte_align; + unsigned int num_directory = 0; + unsigned int i, j; + int direction; + unsigned int data_size = 0; + + /* app1 mark:2, data_size:2, exif:6 tiff:8 */ + if ((*position + 18) >= fsize) return EINA_FALSE; + app1_head = map + *position; + buf = app1_head; + + data_size = ((*(buf + 2) << 8) + *(buf + 3)); + if ((*position + 2 + data_size) > fsize) return EINA_FALSE; + + if (memcmp(buf + 4, ExifHeader, sizeof (ExifHeader))) + { + *position = *position + 2 + data_size; + *orientation_res = -1; + return EINA_TRUE; + } + + /* 2. get 10&11 byte get info of "II(0x4949)" or "MM(0x4d4d)" */ + /* 3. get [18]&[19] get directory entry # */ + if (!memcmp(buf + 10, MM, sizeof (MM))) + { + byte_align = EXIF_BYTE_ALIGN_MM; + num_directory = ((*(buf + 18) << 8) + *(buf + 19)); + orientation[0] = 0x01; + orientation[1] = 0x12; + } + else if (!memcmp(buf + 10, II, sizeof (II))) + { + byte_align = EXIF_BYTE_ALIGN_II; + num_directory = ((*(buf + 19) << 8) + *(buf + 18)); + orientation[0] = 0x12; + orientation[1] = 0x01; + } + else return EINA_FALSE; + + /* check num_directory data */ + if ((*position + (12 * num_directory + 20)) > fsize) return EINA_FALSE; + + buf = app1_head + 20; + + j = 0; + + for (i = 0; i < num_directory; i++ ) + { + if (!memcmp(buf + j, orientation, 2)) + { + /*get orientation tag */ + if (byte_align == EXIF_BYTE_ALIGN_MM) + direction = *(buf+ j + 9); + else direction = *(buf+ j + 8); + switch (direction) + { + case 3: + *orientation_res = 180; + *flipped = EINA_FALSE; + return EINA_TRUE; + case 4: + *orientation_res = 180; + *flipped = EINA_TRUE; + return EINA_TRUE; + case 6: + *orientation_res = 90; + *flipped = EINA_FALSE; + return EINA_TRUE; + case 7: + *orientation_res = 90; + *flipped = EINA_TRUE; + return EINA_TRUE; + case 5: + *orientation_res = 270; + *flipped = EINA_TRUE; + return EINA_TRUE; + case 8: + *orientation_res = 270; + *flipped = EINA_FALSE; + return EINA_TRUE; + case 2: + *orientation_res = 0; + *flipped = EINA_TRUE; + return EINA_TRUE; + default: + *orientation_res = 0; + *flipped = EINA_FALSE; + return EINA_TRUE; + } + } + else + j = j + 12; + } return EINA_FALSE; } +static int +_get_orientation(const void *map, size_t length, Eina_Bool *flipped) +{ + unsigned char *buf; + size_t position = 0; + int orientation = -1; + Eina_Bool res = EINA_FALSE; + + *flipped = EINA_FALSE; + + /* open file and get 22 byte frome file */ + if (!map) return 0; + /* 1. read 22byte */ + if (length < 22) return 0; + buf = (unsigned char *)map; + + position = 2; + /* 2. check 2,3 bypte with APP0(0xFFE0) or APP1(0xFFE1) */ + while((length - position) > 0) + { + if (!memcmp(buf + position, App0, sizeof (App0))) + { + res = _get_next_app0(map, length, &position); + if (!res) break; + } + else if (!memcmp(buf + position, App1, sizeof (App1))) + { + res = _get_orientation_app1(map, length, &position, &orientation, flipped); + if (!res) break; + if (orientation != -1) return orientation; + } + else break; + } + return 0; +} + +static void +_rotate_region(unsigned int *r_x, unsigned int *r_y, unsigned int *r_w, unsigned int *r_h, + unsigned int x, unsigned int y, unsigned int w, unsigned int h, + unsigned int output_w, unsigned int output_h, + int degree, Eina_Bool flipped) +{ + switch (degree) + { + case 90: + if (flipped) + { + *r_x = output_w - (y + h); + *r_y = output_h - (x + w); + *r_w = h; + *r_h = w; + } + else + { + *r_x = y; + *r_y = output_h - (x + y); + *r_w = h; + *r_h = w; + } + break; + case 180: + if (flipped) + { + *r_y = output_h - (y + h); + } + else + { + *r_x = output_w - (x + w); + *r_y = output_h - (y + h); + } + break; + case 270: + if (flipped) + { + *r_x = y; + *r_y = x; + *r_w = h; + *r_h = w; + } + else + { + *r_x = output_w - (y + h); + *r_y = x; + *r_w = h; + *r_h = w; + } + break; + default: + if (flipped) + *r_x = output_w - (x + w); + break; + } +} + +static void +_rotate_180(uint32_t *data, int w, int h) +{ + uint32_t *p1, *p2; + uint32_t pt; + int x; + + p1 = data; + p2 = data + (h * w) - 1; + for (x = (w * h) / 2; --x >= 0;) + { + pt = *p1; + *p1 = *p2; + *p2 = pt; + p1++; + p2--; + } +} + +static void +_flip_horizontal(uint32_t *data, int w, int h) +{ + uint32_t *p1, *p2; + uint32_t pt; + int x, y; + + for (y = 0; y < h; y++) + { + p1 = data + (y * w); + p2 = data + ((y + 1) * w) - 1; + for (x = 0; x < (w >> 1); x++) + { + pt = *p1; + *p1 = *p2; + *p2 = pt; + p1++; + p2--; + } + } +} + +static void +_flip_vertical(uint32_t *data, int w, int h) +{ + uint32_t *p1, *p2; + uint32_t pt; + int x, y; + + for (y = 0; y < (h >> 1); y++) + { + p1 = data + (y * w); + p2 = data + ((h - 1 - y) * w); + for (x = 0; x < w; x++) + { + pt = *p1; + *p1 = *p2; + *p2 = pt; + p1++; + p2++; + } + } +} + +static void +_rotate_change_wh(uint32_t *to, uint32_t *from, + int w, int h, + int dx, int dy) +{ + int x, y; + + for (x = h; --x >= 0;) + { + for (y = w; --y >= 0;) + { + *to = *from; + from++; + to += dy; + } + to += dx; + } +} + +static Eina_Bool +_emile_jpeg_bind(Emile_Image *image EINA_UNUSED, + Emile_Image_Load_Opts *opts EINA_UNUSED, + Emile_Image_Animated *animated EINA_UNUSED, + Emile_Image_Load_Error *error EINA_UNUSED) +{ + return EINA_TRUE; +} + static Eina_Bool _emile_jpeg_head(Emile_Image *image, Emile_Image_Property *prop, unsigned int property_size, Emile_Image_Load_Error *error) { - return EINA_FALSE; + Emile_Image_Load_Opts *opts = NULL; + const unsigned char *m; + unsigned int scalew, scaleh; + struct jpeg_decompress_struct cinfo; + struct _JPEG_error_mgr jerr; + unsigned int length; + + /* for rotation decoding */ + int degree = 0; + Eina_Bool change_wh = EINA_FALSE; + unsigned int load_opts_w = 0, load_opts_h = 0; + + + if (sizeof (Emile_Image_Property) != property_size) + return EINA_FALSE; + + m = _emile_image_file_source_map(image, &length); + if (!m) return EINA_FALSE; + + if (image->load_opts) + opts = &image->opts; + + memset(&cinfo, 0, sizeof(cinfo)); + cinfo.err = jpeg_std_error(&(jerr.pub)); + jerr.pub.error_exit = _JPEGFatalErrorHandler; + jerr.pub.emit_message = _JPEGErrorHandler2; + jerr.pub.output_message = _JPEGErrorHandler; + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + if (cinfo.saw_JFIF_marker) + *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; + else + *error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT; + return EINA_FALSE; + } + jpeg_create_decompress(&cinfo); + + if (_emile_jpeg_membuf_src(&cinfo, m, length)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return EINA_FALSE; + } + + if (cinfo.output_components == 1) + { + // We do handle GRY8 colorspace as an output for JPEG + prop->cspaces = cspaces_gry; + } + + jpeg_read_header(&cinfo, TRUE); + cinfo.do_fancy_upsampling = FALSE; + cinfo.do_block_smoothing = FALSE; + cinfo.dct_method = JDCT_ISLOW; // JDCT_FLOAT JDCT_IFAST(quality loss) + cinfo.dither_mode = JDITHER_ORDERED; + cinfo.buffered_image = TRUE; // buffered mode in case jpg is progressive + jpeg_start_decompress(&cinfo); + + /* rotation decoding */ + if (opts->orientation) + { + degree = _get_orientation(m, length, &prop->flipped); + if (degree != 0 || prop->flipped) + { + opts->degree = degree; + prop->rotated = EINA_TRUE; + + if (degree == 90 || degree == 270) + change_wh = EINA_TRUE; + } + + } + + /* head decoding */ + prop->w = cinfo.output_width; + prop->h = cinfo.output_height; + if ((prop->w < 1) || (prop->h < 1) || (prop->w > IMG_MAX_SIZE) || (prop->h > IMG_MAX_SIZE) || + (IMG_TOO_BIG(prop->w, prop->h))) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + if (IMG_TOO_BIG(prop->w, prop->h)) + *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + else + *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; + return EINA_FALSE; + } + if (opts->scale_down_by > 1) + { + prop->w /= opts->scale_down_by; + prop->h /= opts->scale_down_by; + } + else if (opts->dpi > 0.0) + { + prop->w = (prop->w * opts->dpi) / 90.0; + prop->h = (prop->h * opts->dpi) / 90.0; + } + else if ((opts->w > 0) && (opts->h > 0)) + { + unsigned int w2 = prop->w, h2 = prop->h; + /* user set load_opts' w,h on the assumption + that image already rotated according to it's orientation info */ + if (change_wh) + { + load_opts_w = opts->w; + load_opts_h = opts->h; + opts->w = load_opts_h; + opts->h = load_opts_w; + } + + if (opts->w > 0) + { + w2 = opts->w; + h2 = (opts->w * prop->h) / prop->w; + if ((opts->h > 0) && (h2 > opts->h)) + { + unsigned int w3; + h2 = opts->h; + w3 = (opts->h * prop->w) / prop->h; + if (w3 > w2) + w2 = w3; + } + } + else if (opts->h > 0) + { + h2 = opts->h; + w2 = (opts->h * prop->w) / prop->h; + } + prop->w = w2; + prop->h = h2; + if (change_wh) + { + opts->w = load_opts_w; + opts->h = load_opts_h; + } + } + if (prop->w < 1) prop->w = 1; + if (prop->h < 1) prop->h = 1; + + if ((prop->w != cinfo.output_width) || (prop->h != cinfo.output_height)) + { + scalew = cinfo.output_width / prop->w; + scaleh = cinfo.output_height / prop->h; + + prop->scale = scalew; + if (scaleh < scalew) prop->scale = scaleh; + + if (prop->scale > 8) prop->scale = 8; + else if (prop->scale < 1) prop->scale = 1; + + if (prop->scale == 3) prop->scale = 2; + else if (prop->scale == 5) prop->scale = 4; + else if (prop->scale == 6) prop->scale = 4; + else if (prop->scale == 7) prop->scale = 4; + } + + if (prop->scale > 1) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + jpeg_create_decompress(&cinfo); + + if (_emile_jpeg_membuf_src(&cinfo, m, length)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return EINA_FALSE; + } + + jpeg_read_header(&cinfo, TRUE); + cinfo.do_fancy_upsampling = FALSE; + cinfo.do_block_smoothing = FALSE; + cinfo.scale_num = 1; + cinfo.scale_denom = prop->scale; + cinfo.buffered_image = TRUE; // buffered mode in case jpg is progressive + jpeg_calc_output_dimensions(&(cinfo)); + jpeg_start_decompress(&cinfo); + } + + prop->w = cinfo.output_width; + prop->h = cinfo.output_height; + + // be nice and clip region to image. if its totally outside, fail load + if ((opts->region.w > 0) && (opts->region.h > 0)) + { + unsigned int load_region_x = opts->region.x, load_region_y = opts->region.y; + unsigned int load_region_w = opts->region.w, load_region_h = opts->region.h; + if (prop->rotated) + { + _rotate_region(&load_region_x, &load_region_y, &load_region_w, &load_region_h, + opts->region.x, opts->region.y, opts->region.w, opts->region.h, + prop->w, prop->h, degree, prop->flipped); + } + RECTS_CLIP_TO_RECT(load_region_x, load_region_y, + load_region_w, load_region_h, + 0, 0, prop->w, prop->h); + if ((load_region_w <= 0) || (load_region_h <= 0)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; + return EINA_FALSE; + } + prop->w = load_region_w; + prop->h = load_region_h; + } +/* end head decoding */ + + if (change_wh) + { + unsigned int tmp; + tmp = prop->w; + prop->w = prop->h; + prop->h = tmp; + } + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_NONE; + return EINA_TRUE; } static Eina_Bool @@ -591,12 +1324,542 @@ _emile_jpeg_data(Emile_Image *image, void *pixels, Emile_Image_Load_Error *error) { - return EINA_FALSE; + // Handle RGB, ARGB and GRY + Emile_Image_Load_Opts *opts = NULL; + unsigned int w, h; + struct jpeg_decompress_struct cinfo; + struct _JPEG_error_mgr jerr; + const unsigned char *m = NULL; + uint8_t *ptr, *line[16], *data; + uint32_t *ptr2, *ptr_rotate = NULL; + unsigned int x, y, l, i, scans; + int region = 0; + /* rotation setting */ + unsigned int ie_w = 0, ie_h = 0; + struct { + unsigned int x, y, w, h; + } opts_region; + volatile int degree = 0; + volatile Eina_Bool change_wh = EINA_FALSE; + Eina_Bool line_done = EINA_FALSE; + unsigned int length; + + if (sizeof (Emile_Image_Property) != property_size) + return EINA_FALSE; + + m = _emile_image_file_source_map(image, &length); + if (!m) return EINA_FALSE; + + if (image->load_opts) + opts = &image->opts; + + memset(&cinfo, 0, sizeof(cinfo)); + if (prop->rotated) + { + degree = opts->degree; + if (degree == 90 || degree == 270) + change_wh = EINA_TRUE; + } + + cinfo.err = jpeg_std_error(&(jerr.pub)); + jerr.pub.error_exit = _JPEGFatalErrorHandler; + jerr.pub.emit_message = _JPEGErrorHandler2; + jerr.pub.output_message = _JPEGErrorHandler; + if (setjmp(jerr.setjmp_buffer)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; + return EINA_FALSE; + } + jpeg_create_decompress(&cinfo); + + if (_emile_jpeg_membuf_src(&cinfo, m, length)) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return 0; + } + + jpeg_read_header(&cinfo, TRUE); + cinfo.do_fancy_upsampling = FALSE; + cinfo.do_block_smoothing = FALSE; + cinfo.dct_method = JDCT_ISLOW; // JDCT_FLOAT JDCT_IFAST(quality loss) + cinfo.dither_mode = JDITHER_ORDERED; + + if (prop->scale > 1) + { + cinfo.scale_num = 1; + cinfo.scale_denom = prop->scale; + } + + /* Colorspace conversion options */ + /* libjpeg can do the following conversions: */ + /* GRAYSCLAE => RGB YCbCr => RGB and YCCK => CMYK */ + switch (cinfo.jpeg_color_space) + { + case JCS_UNKNOWN: + break; + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + cinfo.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + cinfo.out_color_space = JCS_CMYK; + break; + default: + cinfo.out_color_space = JCS_RGB; + break; + } + +/* head decoding */ + jpeg_calc_output_dimensions(&(cinfo)); + jpeg_start_decompress(&cinfo); + + w = cinfo.output_width; + h = cinfo.output_height; + + if (change_wh) + { + ie_w = prop->h; + ie_h = prop->w; + } + else + { + ie_w = prop->w; + ie_h = prop->h; + } + + if ((opts->region.w > 0) && (opts->region.h > 0)) + { + region = 1; + + opts_region.x = opts->region.x; + opts_region.y = opts->region.y; + opts_region.w = opts->region.w; + opts_region.h = opts->region.h; + + if (prop->rotated) + { + unsigned int load_region_x = 0, load_region_y = 0; + unsigned int load_region_w = 0, load_region_h = 0; + + load_region_x = opts->region.x; + load_region_y = opts->region.y; + load_region_w = opts->region.w; + load_region_h = opts->region.h; + + _rotate_region(&opts_region.x, &opts_region.y, &opts_region.w, &opts_region.h, + load_region_x, load_region_y, load_region_w, load_region_h, + w, h, degree, prop->flipped); + } +#ifdef BUILD_LOADER_JPEG_REGION + cinfo.region_x = opts_region.x; + cinfo.region_y = opts_region.y; + cinfo.region_w = opts_region.w; + cinfo.region_h = opts_region.h; +#endif + } + if ((!region) && ((w != ie_w) || (h != ie_h))) + { + // race condition, the file could have change from when we call header + // this test will not solve the problem with region code. + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; + return EINA_FALSE; + } + if ((region) && + ((ie_w != opts_region.w) || (ie_h != opts_region.h))) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; + return EINA_FALSE; + /* ie_w = opts_region.w; */ + /* ie_h = opts_region.h; */ + /* if (change_wh) */ + /* { */ + /* ie->w = ie_h; */ + /* ie->h = ie_w; */ + /* } */ + /* else */ + /* { */ + /* ie->w = ie_w; */ + /* ie->h = ie_h; */ + /* } */ + } + + if (!(((cinfo.out_color_space == JCS_RGB) && + ((cinfo.output_components == 3) || (cinfo.output_components == 1))) || + ((cinfo.out_color_space == JCS_CMYK) && (cinfo.output_components == 4)))) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT; + return EINA_FALSE; + } + +/* end head decoding */ +/* data decoding */ + if (cinfo.rec_outbuf_height > 16) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT; + return EINA_FALSE; + } + data = alloca(w * 16 * cinfo.output_components); + if ((prop->rotated) && change_wh) + { + ptr2 = malloc(w * h * sizeof(uint32_t)); + ptr_rotate = ptr2; + } + else + ptr2 = pixels; + + if (!ptr2) + { + *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; + return EINA_FALSE; + } + + /* We handle first CMYK (4 components) */ + if (cinfo.output_components == 4) + { + // FIXME: handle region + for (i = 0; (int)i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 4); + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + if ((h - l) < scans) scans = h - l; + ptr = data; + if (!region) + { + for (y = 0; y < scans; y++) + { + if (cinfo.saw_Adobe_marker) + { + for (x = 0; x < w; x++) + { + /* According to libjpeg doc, Photoshop inverse the values of C, M, Y and K, */ + /* that is C is replaces by 255 - C, etc...*/ + /* See the comment below for the computation of RGB values from CMYK ones. */ + *ptr2 = + (0xff000000) | + ((ptr[0] * ptr[3] / 255) << 16) | + ((ptr[1] * ptr[3] / 255) << 8) | + ((ptr[2] * ptr[3] / 255)); + ptr += 4; + ptr2++; + } + } + else + { + for (x = 0; x < w; x++) + { + /* Conversion from CMYK to RGB is done in 2 steps: */ + /* CMYK => CMY => RGB (see http://www.easyrgb.com/index.php?X=MATH) */ + /* after computation, if C, M, Y and K are between 0 and 1, we have: */ + /* R = (1 - C) * (1 - K) * 255 */ + /* G = (1 - M) * (1 - K) * 255 */ + /* B = (1 - Y) * (1 - K) * 255 */ + /* libjpeg stores CMYK values between 0 and 255, */ + /* so we replace C by C * 255 / 255, etc... and we obtain: */ + /* R = (255 - C) * (255 - K) / 255 */ + /* G = (255 - M) * (255 - K) / 255 */ + /* B = (255 - Y) * (255 - K) / 255 */ + /* with C, M, Y and K between 0 and 255. */ + *ptr2 = + (0xff000000) | + (((255 - ptr[0]) * (255 - ptr[3]) / 255) << 16) | + (((255 - ptr[1]) * (255 - ptr[3]) / 255) << 8) | + (((255 - ptr[2]) * (255 - ptr[3]) / 255)); + ptr += 4; + ptr2++; + } + } + } + } + else + { + // if line # > region last line, break + if (l >= (opts_region.y + opts_region.h)) + { + line_done = EINA_TRUE; + /* if rotation flag is set , we have to rotate image */ + goto done; + /*jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_NONE; + return EINA_FALSE;*/ + } + // els if scan block intersects region start or later + else if ((l + scans) > + (opts_region.y)) + { + for (y = 0; y < scans; y++) + { + if (((y + l) >= opts_region.y) && + ((y + l) < (opts_region.y + opts_region.h))) + { + ptr += opts_region.x; + if (cinfo.saw_Adobe_marker) + { + for (x = 0; x < opts_region.w; x++) + { + /* According to libjpeg doc, Photoshop inverse the values of C, M, Y and K, */ + /* that is C is replaces by 255 - C, etc...*/ + /* See the comment below for the computation of RGB values from CMYK ones. */ + *ptr2 = + (0xff000000) | + ((ptr[0] * ptr[3] / 255) << 16) | + ((ptr[1] * ptr[3] / 255) << 8) | + ((ptr[2] * ptr[3] / 255)); + ptr += 4; + ptr2++; + } + } + else + { + for (x = 0; x < opts_region.w; x++) + { + /* Conversion from CMYK to RGB is done in 2 steps: */ + /* CMYK => CMY => RGB (see http://www.easyrgb.com/index.php?X=MATH) */ + /* after computation, if C, M, Y and K are between 0 and 1, we have: */ + /* R = (1 - C) * (1 - K) * 255 */ + /* G = (1 - M) * (1 - K) * 255 */ + /* B = (1 - Y) * (1 - K) * 255 */ + /* libjpeg stores CMYK values between 0 and 255, */ + /* so we replace C by C * 255 / 255, etc... and we obtain: */ + /* R = (255 - C) * (255 - K) / 255 */ + /* G = (255 - M) * (255 - K) / 255 */ + /* B = (255 - Y) * (255 - K) / 255 */ + /* with C, M, Y and K between 0 and 255. */ + *ptr2 = + (0xff000000) | + (((255 - ptr[0]) * (255 - ptr[3]) / 255) << 16) | + (((255 - ptr[1]) * (255 - ptr[3]) / 255) << 8) | + (((255 - ptr[2]) * (255 - ptr[3]) / 255)); + ptr += 4; + ptr2++; + } + } + ptr += (4 * (w - (opts_region.x + opts_region.w))); + } + else + ptr += (4 * w); + } + } + } + } + } + /* We handle then RGB with 3 components */ + else if (cinfo.output_components == 3) + { +/* + double t; + if (region) + { + // debug for now + printf("R| %p %5ix%5i %s: %5i %5i %5ix%5i - ", + ie, + ie->w, ie->h, + ie->file, + opts_region.x, + opts_region.y, + opts_region.w, + opts_region.h); + } + t = get_time(); + */ + for (i = 0; (int)i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w * 3); + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + if ((h - l) < scans) scans = h - l; + ptr = data; + if (!region) + { + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + *ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[1], ptr[2]); + ptr += 3; + ptr2++; + } + } + } + else + { + // if line # > region last line, break + // but not return immediately for rotation job + if (l >= (opts_region.y + opts_region.h)) + { + line_done = EINA_TRUE; + /* if rotation flag is set , we have to rotate image */ + goto done; + } + // else if scan block intersects region start or later + else if ((l + scans) > + (opts_region.y)) + { + for (y = 0; y < scans; y++) + { + if (((y + l) >= opts_region.y) && + ((y + l) < (opts_region.y + opts_region.h))) + { + ptr += (3 * opts_region.x); + for (x = 0; x < opts_region.w; x++) + { + *ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[1], ptr[2]); + ptr += 3; + ptr2++; + } + ptr += (3 * (w - (opts_region.x + opts_region.w))); + } + else + ptr += (3 * w); + } + } + } + } +/* + t = get_time() - t; + printf("%3.3f\n", t); + */ + } + /* We finally handle RGB with 1 component */ + else if (cinfo.output_components == 1) + { + for (i = 0; (int)i < cinfo.rec_outbuf_height; i++) + line[i] = data + (i * w); + for (l = 0; l < h; l += cinfo.rec_outbuf_height) + { + jpeg_read_scanlines(&cinfo, line, cinfo.rec_outbuf_height); + scans = cinfo.rec_outbuf_height; + if ((h - l) < scans) scans = h - l; + ptr = data; + if (!region) + { + for (y = 0; y < scans; y++) + { + for (x = 0; x < w; x++) + { + *ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[0], ptr[0]); + ptr++; + ptr2++; + } + } + } + else + { + // if line # > region last line, break + if (l >= (opts_region.y + opts_region.h)) + { + line_done = EINA_TRUE; + /* if rotation flag is set , we have to rotate image */ + goto done; + /*jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_NONE; + return EINA_TRUE;*/ + } + // els if scan block intersects region start or later + else if ((l + scans) > + (opts_region.y)) + { + for (y = 0; y < scans; y++) + { + if (((y + l) >= opts_region.y) && + ((y + l) < (opts_region.y + opts_region.h))) + { + ptr += opts_region.x; + for (x = 0; x < opts_region.w; x++) + { + *ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[0], ptr[0]); + ptr++; + ptr2++; + } + ptr += w - (opts_region.x + opts_region.w); + } + else + ptr += w; + } + } + } + } + } + /* if rotation operation need, rotate it */ +done: + + if (prop->rotated) + { + uint32_t *to; + int hw; + + hw = w * h; + to = pixels; + + switch (degree) + { + case 90: + if (prop->flipped) + _rotate_change_wh(to + hw - 1, ptr_rotate, w, h, hw - 1, -h); + else + _rotate_change_wh(to + h - 1, ptr_rotate, w, h, -hw - 1, h); + break; + case 180: + if (prop->flipped) + _flip_vertical(to, w, h); + else + _rotate_180(to, w, h); + break; + case 270: + if (prop->flipped) + _rotate_change_wh(to, ptr_rotate, w, h, -hw + 1, h); + else + _rotate_change_wh(to + hw - h, ptr_rotate, w, h, hw + 1, -h); + break; + default: + if (prop->flipped) + _flip_horizontal(to, w, h); + break; + } + if (ptr_rotate) + { + free(ptr_rotate); + ptr_rotate = NULL; + } + } + + if (line_done) + { + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_NONE; + return EINA_FALSE; + } + /* end data decoding */ + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + _emile_jpeg_membuf_src_term(&cinfo); + *error = EMILE_IMAGE_LOAD_ERROR_NONE; + return EINA_TRUE; } static void -_emile_jpeg_close(Emile_Image *image) +_emile_jpeg_close(Emile_Image *image EINA_UNUSED) { + // JPEG file loader doesn't keep any data allocated around (for now) } /* Generic helper to instantiate a new Emile_Image */