efl/src/lib/emile/emile_image.c

2545 lines
73 KiB
C

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef ENABLE_LIBLZ4
#include <lz4.h>
#else
#include "lz4.h"
#endif
#include <stdio.h>
#include <setjmp.h>
#include <jpeglib.h>
#include <jerror.h>
#include "rg_etc1.h"
#include "Emile.h"
#include "emile_private.h"
#ifdef BUILD_NEON
#include <arm_neon.h>
#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])
#define R_VAL(p) (((uint8_t *)(p))[2])
#define G_VAL(p) (((uint8_t *)(p))[1])
#define B_VAL(p) (((uint8_t *)(p))[0])
#else
/* ppc */
#define A_VAL(p) (((uint8_t *)(p))[0])
#define R_VAL(p) (((uint8_t *)(p))[1])
#define G_VAL(p) (((uint8_t *)(p))[2])
#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
#define OFFSET_WIDTH 8
#define OFFSET_HEIGHT 12
#define OFFSET_BLOCKS 16
struct _Emile_Image
{
Emile_Image_Load_Opts opts;
struct
{
unsigned int width;
unsigned int height;
} size, block;
Eina_Rectangle region;
union
{
Eina_Binbuf *bin;
Eina_File *f;
} source;
const unsigned char *source_data;
Eina_Bool (*bind)(Emile_Image *image, Emile_Image_Load_Opts *opts, Emile_Image_Animated *animated, Emile_Image_Load_Error *error);
Eina_Bool (*head)(Emile_Image *image, Emile_Image_Property *prop, unsigned int property_size, Emile_Image_Load_Error *error);
Eina_Bool (*data)(Emile_Image *image, Emile_Image_Property *prop, unsigned int property_size, void *pixels, Emile_Image_Load_Error *error);
void (*close)(Emile_Image *image);
Emile_Action_Cb cancelled;
const void *cancelled_data;
Emile_Colorspace cspace;
Eina_Bool bin_source : 1;
/* TGV option */
Eina_Bool unpremul : 1;
Eina_Bool compress : 1;
Eina_Bool blockless : 1;
Eina_Bool load_opts : 1;
};
static inline Eina_Bool
_emile_image_cancelled_is(Emile_Image *image)
{
if (!image->cancelled) return EINA_FALSE;
return image->cancelled((void*) image->cancelled_data, image, EMILE_ACTION_CANCELLED);
}
#define EMILE_IMAGE_TASK_CHECK(Image, Count, Mask, Error, Error_Handler) \
do { \
Count++; \
if ((Count & Mask) == Mask) \
{ \
Count = 0; \
if (_emile_image_cancelled_is(Image)) \
{ \
*Error = EMILE_IMAGE_LOAD_ERROR_CANCELLED; \
goto Error_Handler; \
} \
} \
} while (0);
static const unsigned char *
_emile_image_file_source_map(Emile_Image *image, unsigned int *length)
{
if (!image)
return NULL;
if (image->bin_source)
{
if (length)
*length = eina_binbuf_length_get(image->source.bin);
return eina_binbuf_string_get(image->source.bin);
}
if (length)
*length = eina_file_size_get(image->source.f);
if (!image->source_data)
{
image->source_data = eina_file_map_all(image->source.f, EINA_FILE_SEQUENTIAL);
}
return image->source_data;
}
static void
_emile_image_file_source_unmap(Emile_Image *image)
{
if (!(image && image->source_data))
return;
eina_file_map_free(image->source.f, (void *)image->source_data);
image->source_data = NULL;
}
static int
_roundup(int val, int rup)
{
if (val >= 0 && rup > 0)
return (val + rup - 1) - ((val + rup - 1) % rup);
return 0;
}
/* TGV Handling */
static const Emile_Colorspace cspaces_etc1[] = {
EMILE_COLORSPACE_ETC1,
EMILE_COLORSPACE_RGB8_ETC2,
EMILE_COLORSPACE_ARGB8888
};
static const Emile_Colorspace cspaces_rgb8_etc2[] = {
EMILE_COLORSPACE_RGB8_ETC2,
EMILE_COLORSPACE_ARGB8888
};
static const Emile_Colorspace cspaces_rgba8_etc2_eac[] = {
EMILE_COLORSPACE_RGBA8_ETC2_EAC,
EMILE_COLORSPACE_ARGB8888
};
static const Emile_Colorspace cspaces_etc1_alpha[] = {
EMILE_COLORSPACE_ETC1_ALPHA,
EMILE_COLORSPACE_ARGB8888
};
static const Emile_Colorspace cspaces_gry[] = {
EMILE_COLORSPACE_GRY8,
EMILE_COLORSPACE_AGRY88,
EMILE_COLORSPACE_ARGB8888
};
/**************************************************************
* 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. As OpenGL is pretty bad at handling border of
* texture, we do duplicate the first pixels of every border.
*
* 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. Note that region
* only work with software decompression as we don't have a sane
* way to duplicate border to avoid artifact when scaling texture.
*
* 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, 1 -> ETC2 RGB, 2 -> ETC2 RGBA, 3 -> ETC1+Alpha)
* - uint8_t options[2] (bitmask: 1 -> lz4, 2 for block-less, 4 -> unpremultiplied)
* - uint32_t width
* - uint32_t height
* - blocks[]
* - 0 length encoded compress size (if length == 64 * block_size => no compression)
* - lzma encoded etc1 block
*
* If the format is ETC1+Alpha (algo = 3), then a second image is encoded
* in ETC1 right after the first one, and it contains grey-scale alpha
* values.
**************************************************************/
// FIXME: wondering if we should support mipmap
// TODO: support ETC1+ETC2 images (RGB only)
static Eina_Bool
_emile_tgv_bind(Emile_Image *image,
Emile_Image_Load_Opts *opts EINA_UNUSED,
Emile_Image_Animated *animated EINA_UNUSED,
Emile_Image_Load_Error *error)
{
const unsigned char *m;
unsigned int length;
m = _emile_image_file_source_map(image, &length);
if (!m)
return EINA_FALSE;
/* Fast check for file characteristic (useful when trying to guess
a file format without extention). */
if (length < 16 || strncmp((const char *)m, "TGV1", 4) != 0)
goto on_error;
if ((m[OFFSET_ALGORITHM] & 0xFF) > 3)
goto on_error;
return EINA_TRUE;
on_error:
*error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT;
_emile_image_file_source_unmap(image);
return EINA_FALSE;
}
static Eina_Bool
_emile_tgv_head(Emile_Image *image,
Emile_Image_Property *prop,
unsigned int property_size,
Emile_Image_Load_Error *error)
{
const unsigned char *m;
unsigned int length;
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:
prop->cspaces = cspaces_etc1;
prop->alpha = EINA_FALSE;
image->cspace = EMILE_COLORSPACE_ETC1;
break;
case 1:
prop->cspaces = cspaces_rgb8_etc2;
prop->alpha = EINA_FALSE;
image->cspace = EMILE_COLORSPACE_RGB8_ETC2;
break;
case 2:
prop->cspaces = cspaces_rgba8_etc2_eac;
prop->alpha = EINA_TRUE;
image->cspace = EMILE_COLORSPACE_RGBA8_ETC2_EAC;
break;
case 3:
prop->cspaces = cspaces_etc1_alpha;
prop->alpha = EINA_TRUE;
prop->premul = !!(m[OFFSET_OPTIONS] & 0x4);
image->unpremul = prop->premul;
image->cspace = EMILE_COLORSPACE_ETC1_ALPHA;
break;
default:
*error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE;
return EINA_FALSE;
}
image->compress = m[OFFSET_OPTIONS] & 0x1;
image->blockless = (m[OFFSET_OPTIONS] & 0x2) != 0;
image->size.width = eina_ntohl(*((unsigned int *)&(m[OFFSET_WIDTH])));
image->size.height = eina_ntohl(*((unsigned int *)&(m[OFFSET_HEIGHT])));
if (image->blockless)
{
image->block.width = _roundup(image->size.width + 2, 4);
image->block.height = _roundup(image->size.height + 2, 4);
}
else
{
image->block.width = 4 << (m[OFFSET_BLOCK_SIZE] & 0x0f);
image->block.height = 4 << ((m[OFFSET_BLOCK_SIZE] & 0xf0) >> 4);
}
EINA_RECTANGLE_SET(&image->region,
0, 0,
image->size.width, image->size.height);
if (image->load_opts &&
((image->opts.region.w > 0) && (image->opts.region.h > 0) &&
(image->opts.region.w != (int) image->size.width) &&
(image->opts.region.h != (int) image->size.height)))
{
/* ETC colorspace doesn't work with region for now */
prop->cspaces = NULL;
if (!eina_rectangle_intersection(&image->region, &image->opts.region))
{
*error = EMILE_IMAGE_LOAD_ERROR_GENERIC;
return EINA_FALSE;
}
}
prop->w = image->size.width;
prop->h = image->size.height;
prop->borders.l = 1;
prop->borders.t = 1;
prop->borders.r = _roundup(prop->w + 2, 4) - prop->w - 1;
prop->borders.b = _roundup(prop->h + 2, 4) - prop->h - 1;
return EINA_TRUE;
}
static inline unsigned int
_tgv_length_get(const unsigned 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;
}
static Eina_Bool
_emile_tgv_data(Emile_Image *image,
Emile_Image_Property *prop,
unsigned int property_size,
void *pixels,
Emile_Image_Load_Error *error)
{
const unsigned char *m;
unsigned int *p = pixels;
unsigned char *p_etc = pixels;
Eina_Binbuf *buffer = NULL;
Eina_Rectangle master;
unsigned int block_length;
unsigned int length, offset;
unsigned int x, y;
unsigned int block_count;
unsigned int etc_width = 0;
unsigned int etc_block_size;
int num_planes = 1, plane, alpha_offset = 0;
Eina_Bool r = EINA_FALSE;
m = _emile_image_file_source_map(image, &length);
if (!m)
{
*error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
return EINA_FALSE;
}
if (sizeof(Emile_Image_Property) != property_size)
return EINA_FALSE;
offset = OFFSET_BLOCKS;
*error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE;
/* By definition, prop{.w, .h} == region{.w, .h} */
EINA_RECTANGLE_SET(&master,
image->region.x, image->region.y,
prop->w, prop->h);
switch (image->cspace)
{
case EMILE_COLORSPACE_ETC1:
case EMILE_COLORSPACE_RGB8_ETC2:
etc_block_size = 8;
break;
case EMILE_COLORSPACE_RGBA8_ETC2_EAC:
etc_block_size = 16;
break;
case EMILE_COLORSPACE_ETC1_ALPHA:
etc_block_size = 8;
num_planes = 2;
alpha_offset = ((prop->w + 2 + 3) / 4) * ((prop->h + 2 + 3) / 4) * 8 / sizeof(*p_etc);
break;
default:
abort();
}
etc_width = ((prop->w + 2 + 3) / 4) * etc_block_size;
switch (prop->cspace)
{
case EMILE_COLORSPACE_ETC1:
case EMILE_COLORSPACE_RGB8_ETC2:
case EMILE_COLORSPACE_RGBA8_ETC2_EAC:
case EMILE_COLORSPACE_ETC1_ALPHA:
if (master.x % 4 || master.y % 4)
// FIXME: Should we really abort here ? Seems like a late check for me
abort();
break;
case EMILE_COLORSPACE_ARGB8888:
/* Offset to take duplicated pixels into account */
master.x += 1;
master.y += 1;
break;
default:
abort();
}
if (prop->cspace != EMILE_COLORSPACE_ARGB8888 &&
prop->cspace != image->cspace)
{
if (!((prop->cspace == EMILE_COLORSPACE_RGB8_ETC2) &&
(image->cspace == EMILE_COLORSPACE_ETC1)))
{
*error = EMILE_IMAGE_LOAD_ERROR_GENERIC;
return EINA_FALSE;
}
/* else: ETC2 is compatible with ETC1 and is preferred */
}
/* Allocate space for each ETC block (8 or 16 bytes per 4 * 4 pixels group) */
block_count = image->block.width * image->block.height / (4 * 4);
if (image->compress)
buffer = eina_binbuf_manage_new(alloca(etc_block_size * block_count),
etc_block_size * block_count,
EINA_TRUE);
for (plane = 0; plane < num_planes; plane++)
for (y = 0; y < image->size.height + 2; y += image->block.height)
for (x = 0; x < image->size.width + 2; x += image->block.width)
{
Eina_Rectangle current;
Eina_Binbuf *data_start;
const unsigned char *it;
unsigned int i, j;
block_length = _tgv_length_get(m + offset, length, &offset);
if (block_length == 0)
{
*error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE;
goto on_error;
}
data_start = eina_binbuf_manage_new(m + offset,
block_length,
EINA_TRUE);
offset += block_length;
EINA_RECTANGLE_SET(&current,
x, y,
image->block.width, image->block.height);
if (!eina_rectangle_intersection(&current, &master))
{
eina_binbuf_free(data_start);
continue;
}
if (image->compress)
{
if (!emile_expand(data_start, buffer, EMILE_LZ4HC))
{
eina_binbuf_free(data_start);
goto on_error;
}
}
else
{
buffer = data_start;
if (block_count * etc_block_size != block_length)
{
eina_binbuf_free(data_start);
goto on_error;
}
}
it = eina_binbuf_string_get(buffer);
for (i = 0; i < image->block.height; i += 4)
for (j = 0; j < image->block.width; j += 4, it += etc_block_size)
{
Eina_Rectangle current_etc;
unsigned int temporary[4 * 4];
unsigned int offset_x, offset_y;
int k, l;
EINA_RECTANGLE_SET(&current_etc, x + j, y + i, 4, 4);
if (!eina_rectangle_intersection(&current_etc, &current))
continue;
switch (prop->cspace)
{
case EMILE_COLORSPACE_ARGB8888:
switch (image->cspace)
{
case EMILE_COLORSPACE_ETC1:
case EMILE_COLORSPACE_ETC1_ALPHA:
if (!rg_etc1_unpack_block(it, temporary, 0))
{
// TODO: Should we decode as RGB8_ETC2?
fprintf(stderr, "ETC1: Block starting at {%i, %i} is corrupted!\n", x + j, y + i);
continue;
}
break;
case EMILE_COLORSPACE_RGB8_ETC2:
rg_etc2_rgb8_decode_block((uint8_t *)it, temporary);
break;
case EMILE_COLORSPACE_RGBA8_ETC2_EAC:
rg_etc2_rgba8_decode_block((uint8_t *)it, temporary);
break;
default:
abort();
}
offset_x = current_etc.x - x - j;
offset_y = current_etc.y - y - i;
if (!plane)
{
#ifdef BUILD_NEON
if (eina_cpu_features_get() & EINA_CPU_NEON)
{
uint32_t *dst = &p[current_etc.x - 1 + (current_etc.y - 1) * master.w];
uint32_t *src = &temporary[offset_x + offset_y * 4];
for (k = 0; k < current_etc.h; k++)
{
if (current_etc.w == 4)
vst1q_u32(dst, vld1q_u32(src));
else if (current_etc.w == 3)
{
vst1_u32(dst, vld1_u32(src));
*(dst + 2) = *(src + 2);
}
else if (current_etc.w == 2)
vst1_u32(dst, vld1_u32(src));
else
*dst = *src;
dst += master.w;
src += 4;
}
}
else
#endif
for (k = 0; k < current_etc.h; k++)
{
memcpy(&p[current_etc.x - 1 + (current_etc.y - 1 + k) * master.w],
&temporary[offset_x + (offset_y + k) * 4],
current_etc.w * sizeof(unsigned int));
}
}
else
{
for (k = 0; k < current_etc.h; k++)
for (l = 0; l < current_etc.w; l++)
{
unsigned int *rgbdata = &p[current_etc.x - 1 + (current_etc.y - 1 + k) * master.w + l];
unsigned int *adata = &temporary[offset_x + (offset_y + k) * 4 + l];
A_VAL(rgbdata) = G_VAL(adata);
}
}
break;
case EMILE_COLORSPACE_ETC1:
case EMILE_COLORSPACE_RGB8_ETC2:
case EMILE_COLORSPACE_RGBA8_ETC2_EAC:
memcpy(&p_etc[(current_etc.x / 4) * etc_block_size + (current_etc.y / 4) * etc_width],
it,
etc_block_size);
break;
case EMILE_COLORSPACE_ETC1_ALPHA:
memcpy(&p_etc[(current_etc.x / 4) * etc_block_size + (current_etc.y / 4) * etc_width + plane * alpha_offset],
it,
etc_block_size);
break;
default:
abort();
}
} /* bx,by inside blocks */
eina_binbuf_free(data_start);
} /* x,y macroblocks */
// TODO: Add support for more unpremultiplied modes (ETC2)
if (prop->cspace == EMILE_COLORSPACE_ARGB8888)
prop->premul = image->unpremul; /* call premul if unpremul data */
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
r = EINA_TRUE;
on_error:
if (image->compress) eina_binbuf_free(buffer);
_emile_image_file_source_unmap(image);
return r;
}
static void
_emile_tgv_close(Emile_Image *image EINA_UNUSED)
{
/* TGV file loader doesn't keep any data allocated around */
}
/* JPEG Handling */
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
_emile_image_jpeg_error_exit_cb(j_common_ptr cinfo)
{
char buffer[JMSG_LENGTH_MAX];
emptr errmgr;
// Avoid message "Not a JPEG file: starts with 0x%02x 0x%02x"
if (cinfo->client_data || (cinfo->err->msg_code != JERR_NO_SOI))
{
(*cinfo->err->format_message)(cinfo, buffer);
ERR("%s", buffer);
}
errmgr = (emptr)cinfo->err;
longjmp(errmgr->setjmp_buffer, 1);
}
static void
_emile_image_jpeg_emit_message_cb(j_common_ptr cinfo, int msg_level)
{
char buffer[JMSG_LENGTH_MAX];
struct jpeg_error_mgr *err;
err = cinfo->err;
if (msg_level < 0)
{
if ((err->num_warnings == 0) || (err->trace_level >= 3))
{
(*cinfo->err->format_message)(cinfo, buffer);
WRN("%s", buffer);
}
err->num_warnings++;
}
else
{
if (err->trace_level >= msg_level)
{
(*cinfo->err->format_message)(cinfo, buffer);
INF("%s", buffer);
}
}
}
static void
_emile_image_jpeg_output_message_cb(j_common_ptr cinfo)
{
char buffer[JMSG_LENGTH_MAX];
(*cinfo->err->format_message)(cinfo, buffer);
ERR("%s", buffer);
}
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]; //orientation tag
ExifByteAlign byte_align;
unsigned int num_directory = 0;
unsigned int ifd_offset = 10; //IFD offset start at 10th byte (mark:2 + data_size:2 + exif:6)
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 14th - 17th byte get info for IFD offset */
/* 4. get directory entry IFD */
if (!memcmp(buf + 10, MM, sizeof(MM)))
{
// get 4byte by little endian
ifd_offset += (*(buf + 14) << 24) + (*(buf + 15) << 16) + (*(buf + 16) << 8) + (*(buf + 17));
if (ifd_offset > fsize)
return EINA_FALSE;
byte_align = EXIF_BYTE_ALIGN_MM;
num_directory = ((*(buf + ifd_offset) << 8) + *(buf + ifd_offset + 1));
orientation[0] = 0x01;
orientation[1] = 0x12;
}
else if (!memcmp(buf + 10, II, sizeof(II)))
{
// get 4byte by big endian
ifd_offset += (*(buf + 14)) + (*(buf + 15) << 8) + (*(buf + 16) << 16) + (*(buf + 17) << 24);
if (ifd_offset > fsize)
return EINA_FALSE;
byte_align = EXIF_BYTE_ALIGN_II;
num_directory = ((*(buf + ifd_offset + 1) << 8) + *(buf + ifd_offset));
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 + ifd_offset + 2; //next to 0th ifd (1st tag)
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 + w);
*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 void
_rotate8_180(uint8_t *data, int w, int h)
{
uint8_t *p1, *p2;
uint8_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_horizontal8(uint8_t *data, int w, int h)
{
uint8_t *p1, *p2;
uint8_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_vertical8(uint8_t *data, int w, int h)
{
uint8_t *p1, *p2;
uint8_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_wh8(uint8_t *to, uint8_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 void
_rotate16_180(uint16_t *data, int w, int h)
{
uint16_t *p1, *p2;
uint16_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_horizontal16(uint16_t *data, int w, int h)
{
uint16_t *p1, *p2;
uint16_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_vertical16(uint16_t *data, int w, int h)
{
uint16_t *p1, *p2;
uint16_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_wh16(uint16_t *to, uint16_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)
{
volatile Emile_Image_Load_Opts *opts = (image->load_opts) ? &image->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 */
volatile int degree = 0;
volatile Eina_Bool change_wh = EINA_FALSE;
volatile 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;
memset(&cinfo, 0, sizeof(cinfo));
cinfo.err = jpeg_std_error(&(jerr.pub));
cinfo.client_data = NULL;
jerr.pub.error_exit = _emile_image_jpeg_error_exit_cb;
jerr.pub.emit_message = _emile_image_jpeg_emit_message_cb;
jerr.pub.output_message = _emile_image_jpeg_output_message_cb;
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;
}
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);
if (cinfo.jpeg_color_space == JCS_GRAYSCALE)
{
/* We do handle GRY8 and AGRY88 (with FF for alpha) colorspace as an output for JPEG */
prop->cspaces = cspaces_gry;
}
/* rotation decoding */
if (opts && 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 && opts->scale_down_by > 1)
{
prop->w /= opts->scale_down_by;
prop->h /= opts->scale_down_by;
}
else if (opts && opts->dpi > 0.0)
{
prop->w = (prop->w * opts->dpi) / 90.0;
prop->h = (prop->h * opts->dpi) / 90.0;
}
else if (opts && ((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;
}
w2 = opts->w;
h2 = (opts->w * prop->h) / prop->w;
if (h2 > opts->h)
{
unsigned int w3;
h2 = opts->h;
w3 = (opts->h * prop->w) / prop->h;
if (w3 > w2)
w2 = w3;
}
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 && ((opts->region.w > 0) && (opts->region.h > 0)))
{
unsigned int load_region_x = opts->region.x;
unsigned int load_region_y = opts->region.y;
unsigned int load_region_w = opts->region.w;
unsigned int 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);
}
if (prop->scale > 1)
{
load_region_x /= prop->scale;
load_region_y /= prop->scale;
load_region_w /= prop->scale;
load_region_h /= prop->scale;
}
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 inline void
_jpeg_convert_copy(volatile uint32_t **dst, uint8_t **src, unsigned int w, Eina_Bool adobe_marker)
{
uint32_t *ptr2 = (uint32_t*) *dst;
uint8_t *ptr = *src;
unsigned int x;
if (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++;
}
}
*dst = ptr2;
*src = ptr;
}
static inline void
_jpeg_gry8_convert_copy(uint8_t **dst, uint8_t **src, unsigned int w)
{
uint8_t *ptrg = (uint8_t*) *dst;
uint8_t *ptr = *src;
unsigned int x;
for (x = 0; x < w; x++)
{
*ptrg = ptr[0];
ptrg++;
ptr++;
}
*dst = ptrg;
*src = ptr;
}
static inline void
_jpeg_agry88_convert_copy(uint16_t **dst, uint8_t **src, unsigned int w)
{
uint16_t *ptrag = (uint16_t*) *dst;
uint8_t *ptr = *src;
unsigned int x;
for (x = 0; x < w; x++)
{
*ptrag = 0xFF00 | ptr[0];
ptrag++;
ptr++;
}
*dst = ptrag;
*src = ptr;
}
static inline void
_jpeg_argb8888_convert_copy(volatile uint32_t **dst, uint8_t **src, unsigned int w)
{
uint32_t *ptr2 = (uint32_t*) *dst;
uint8_t *ptr = *src;
unsigned int x;
for (x = 0; x < w; x++)
{
*ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[0], ptr[0]);
ptr2++;
ptr++;
}
*dst = ptr2;
*src = ptr;
}
static inline void
_jpeg_copy(volatile uint32_t **dst, uint8_t **src, unsigned int w)
{
uint32_t *ptr2 = (uint32_t*) *dst;
uint8_t *ptr = *src;
unsigned int x;
for (x = 0; x < w; x++)
{
*ptr2 = ARGB_JOIN(0xff, ptr[0], ptr[1], ptr[2]);
ptr += 3;
ptr2++;
}
*dst = ptr2;
*src = ptr;
}
static Eina_Bool
_emile_jpeg_data(Emile_Image *image,
Emile_Image_Property *prop,
unsigned int property_size,
void *pixels,
Emile_Image_Load_Error *error)
{
/* Handle RGB, ARGB, GRY and AGRY */
volatile Emile_Image_Load_Opts *opts = (image->load_opts) ? &image->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;
volatile uint32_t *ptr2 = NULL;
uint32_t *ptr_rotate = NULL;
uint16_t *ptrag = NULL, *ptrag_rotate = NULL;
uint8_t *ptrg = NULL, *ptrg_rotate = NULL;
unsigned int y, l, i, scans;
volatile int region = 0;
/* rotation setting */
unsigned int ie_w = 0, ie_h = 0;
struct
{
unsigned int x, y, w, h;
} opts_region = {0, 0, 0, 0};
volatile int degree = 0;
volatile Eina_Bool change_wh = EINA_FALSE;
volatile Eina_Bool line_done = EINA_FALSE;
volatile Eina_Bool ptrg_free = EINA_FALSE;
volatile Eina_Bool ptrag_free = EINA_FALSE;
volatile Eina_Bool r = EINA_FALSE;
unsigned int length;
volatile unsigned short count = 0;
if (sizeof(Emile_Image_Property) != property_size)
return EINA_FALSE;
m = _emile_image_file_source_map(image, &length);
if (!m)
return EINA_FALSE;
memset(&cinfo, 0, sizeof(cinfo));
if (prop->rotated)
{
degree = opts ? opts->degree : 0;
if (degree == 90 || degree == 270)
change_wh = EINA_TRUE;
}
cinfo.err = jpeg_std_error(&(jerr.pub));
cinfo.client_data = (void *)(intptr_t) 0x1;
jerr.pub.error_exit = _emile_image_jpeg_error_exit_cb;
jerr.pub.emit_message = _emile_image_jpeg_emit_message_cb;
jerr.pub.output_message = _emile_image_jpeg_output_message_cb;
if (setjmp(jerr.setjmp_buffer))
{
*error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE;
goto on_error;
}
jpeg_create_decompress(&cinfo);
if (_emile_jpeg_membuf_src(&cinfo, m, length))
{
*error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto on_error;
}
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:
if (prop->cspace == EMILE_COLORSPACE_GRY8 ||
prop->cspace == EMILE_COLORSPACE_AGRY88)
{
cinfo.out_color_space = JCS_GRAYSCALE;
break;
}
/* The caller doesn't handle GRAYSCALE,
* fallback to RGB.
*/
EINA_FALLTHROUGH;
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 && ((opts->region.w > 0) && (opts->region.h > 0)))
{
region = 1;
/* scale value already applied when decompress.
When access to decoded image, have to apply scale value to region value */
if (prop->scale > 1)
{
opts_region.x = opts->region.x / prop->scale;
opts_region.y = opts->region.y / prop->scale;
opts_region.w = opts->region.w / prop->scale;
opts_region.h = opts->region.h / prop->scale;
}
else
{
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);
}
}
if ((!region) && ((w != ie_w) || (h != ie_h)))
{
*error = EMILE_IMAGE_LOAD_ERROR_GENERIC;
goto on_error;
}
if ((region) && ((ie_w != opts_region.w) || (ie_h != opts_region.h)))
{
*error = EMILE_IMAGE_LOAD_ERROR_GENERIC;
goto on_error;
}
switch (prop->cspace)
{
case EMILE_COLORSPACE_GRY8:
case EMILE_COLORSPACE_AGRY88:
if (!(cinfo.out_color_space == JCS_GRAYSCALE &&
cinfo.output_components == 1))
{
*error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
break;
case EMILE_COLORSPACE_ARGB8888:
if (!((cinfo.out_color_space == JCS_RGB && cinfo.output_components == 3) ||
(cinfo.out_color_space == JCS_CMYK && cinfo.output_components == 4)))
{
*error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
break;
default:
*error = EMILE_IMAGE_LOAD_ERROR_GENERIC;
goto on_error;
}
/* end head decoding */
/* data decoding */
if (cinfo.rec_outbuf_height > 16)
{
*error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT;
goto on_error;
}
data = alloca(w * 16 * cinfo.output_components);
if ((prop->rotated) && change_wh)
{
if (prop->cspace == EMILE_COLORSPACE_GRY8)
{
ptrg = malloc(w * h * sizeof(uint8_t));
ptrg_rotate = ptrg;
ptrg_free = EINA_TRUE;
}
else if (prop->cspace == EMILE_COLORSPACE_AGRY88)
{
ptrag = malloc(w * h * sizeof(uint16_t));
ptrag_rotate = ptrag;
ptrag_free = EINA_TRUE;
}
else
{
ptr2 = malloc(w * h * sizeof(uint32_t));
ptr_rotate = (uint32_t*) ptr2;
}
}
else
{
ptr2 = pixels;
ptrag = pixels;
ptrg = pixels;
}
if (!ptr2 && !ptrag && !ptrg)
{
*error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
goto on_error;
}
/* 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)
{
// Check for continuing every 16 scanlines fetch
EMILE_IMAGE_TASK_CHECK(image, count, 0xF, error, on_error);
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++)
{
_jpeg_convert_copy(&ptr2, &ptr, w, cinfo.saw_Adobe_marker);
}
}
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; */
}
/* 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 += opts_region.x;
_jpeg_convert_copy(&ptr2, &ptr, opts_region.w, cinfo.saw_Adobe_marker);
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)
{
// Check for continuing every 16 scanlines fetch
EMILE_IMAGE_TASK_CHECK(image, count, 0xF, error, on_error);
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++)
{
_jpeg_copy(&ptr2, &ptr, w);
}
}
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);
_jpeg_copy(&ptr2, &ptr, opts_region.w);
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)
{
// Check for continuing every 16 scanlines fetch
EMILE_IMAGE_TASK_CHECK(image, count, 0xF, error, on_error);
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++)
{
switch (prop->cspace)
{
case EMILE_COLORSPACE_GRY8:
_jpeg_gry8_convert_copy(&ptrg, &ptr, w);
break;
case EMILE_COLORSPACE_AGRY88:
_jpeg_agry88_convert_copy(&ptrag, &ptr, w);
break;
default:
_jpeg_argb8888_convert_copy(&ptr2, &ptr, w);
break;
}
}
}
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; */
}
/* 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 += opts_region.x;
switch (prop->cspace)
{
case EMILE_COLORSPACE_GRY8:
_jpeg_gry8_convert_copy(&ptrg, &ptr, opts_region.w);
break;
case EMILE_COLORSPACE_AGRY88:
_jpeg_agry88_convert_copy(&ptrag, &ptr, opts_region.w);
break;
default:
_jpeg_argb8888_convert_copy(&ptr2, &ptr, opts_region.w);
break;
}
ptr += w - (opts_region.x + opts_region.w);
}
else
ptr += w;
}
}
}
}
}
/* if rotation operation need, rotate it */
done:
if (prop->rotated)
{
uint32_t *to;
uint8_t *to8;
uint16_t *to16;
int hw;
hw = ie_w * ie_h;
to = pixels;
to8 = pixels;
to16 = pixels;
switch (degree)
{
case 90:
if (prop->cspace == EMILE_COLORSPACE_GRY8)
{
if (prop->flipped)
_rotate_change_wh8(to8 + hw - 1, ptrg_rotate, ie_w, ie_h, hw - 1, -ie_h);
else
_rotate_change_wh8(to8 + ie_h - 1, ptrg_rotate, ie_w, ie_h, -hw - 1, ie_h);
}
else if (prop->cspace == EMILE_COLORSPACE_AGRY88)
{
if (prop->flipped)
_rotate_change_wh16(to16 + hw - 1, ptrag_rotate, ie_w, ie_h, hw - 1, -ie_h);
else
_rotate_change_wh16(to16 + ie_h - 1, ptrag_rotate, ie_w, ie_h, -hw - 1, ie_h);
}
else
{
if (prop->flipped)
_rotate_change_wh(to + hw - 1, ptr_rotate, ie_w, ie_h, hw - 1, -ie_h);
else
_rotate_change_wh(to + ie_h - 1, ptr_rotate, ie_w, ie_h, -hw - 1, ie_h);
}
break;
case 180:
if (prop->cspace == EMILE_COLORSPACE_GRY8)
{
if (prop->flipped)
_flip_vertical8(to8, ie_w, ie_h);
else
_rotate8_180(to8, ie_w, ie_h);
}
else if (prop->cspace == EMILE_COLORSPACE_AGRY88)
{
if (prop->flipped)
_flip_vertical16(to16, ie_w, ie_h);
else
_rotate16_180(to16, ie_w, ie_h);
}
else
{
if (prop->flipped)
_flip_vertical(to, ie_w, ie_h);
else
_rotate_180(to, ie_w, ie_h);
}
break;
case 270:
if (prop->cspace == EMILE_COLORSPACE_GRY8)
{
if (prop->flipped)
_rotate_change_wh8(to8, ptrg_rotate, ie_w, ie_h, -hw + 1, ie_h);
else
_rotate_change_wh8(to8 + hw - ie_h, ptrg_rotate, ie_w, ie_h, hw + 1, -ie_h);
}
else if (prop->cspace == EMILE_COLORSPACE_AGRY88)
{
if (prop->flipped)
_rotate_change_wh16(to16, ptrag_rotate, w, h, -hw + 1, h);
else
_rotate_change_wh16(to16 + hw - ie_h, ptrag_rotate, ie_w, ie_h, hw + 1, -ie_h);
}
else
{
if (prop->flipped)
_rotate_change_wh(to, ptr_rotate, ie_w, ie_h, -hw + 1, ie_h);
else
_rotate_change_wh(to + hw - ie_h, ptr_rotate, ie_w, ie_h, hw + 1, -ie_h);
}
break;
default:
if (prop->flipped)
{
if (prop->cspace == EMILE_COLORSPACE_GRY8)
_flip_horizontal8(to8, ie_w, ie_h);
else if (prop->cspace == EMILE_COLORSPACE_AGRY88)
_flip_horizontal16(to16, ie_w, ie_h);
else
_flip_horizontal(to, ie_w, ie_h);
}
break;
}
if (ptr_rotate)
{
free(ptr_rotate);
ptr_rotate = NULL;
}
}
if (line_done)
{
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
goto on_error;
}
/* end data decoding */
jpeg_finish_decompress(&cinfo);
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
r = EINA_TRUE;
on_error:
if (ptrg_free) free(ptrg);
if (ptrag_free) free(ptrag);
jpeg_destroy_decompress(&cinfo);
_emile_jpeg_membuf_src_term(&cinfo);
_emile_image_file_source_unmap(image);
return r;
}
static void
_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 */
static Emile_Image *
_emile_image_new(Eina_Bool (*bind)(Emile_Image *image, Emile_Image_Load_Opts *opts, Emile_Image_Animated *animated, Emile_Image_Load_Error *error),
Eina_Bool (*head)(Emile_Image *image, Emile_Image_Property *prop, unsigned int property_size, Emile_Image_Load_Error *error),
Eina_Bool (*data)(Emile_Image *image, Emile_Image_Property *prop, unsigned int property_size, void *pixels, Emile_Image_Load_Error *error),
void (*close)(Emile_Image *image))
{
Emile_Image *ei;
ei = calloc(1, sizeof(Emile_Image));
if (!ei)
return NULL;
ei->bind = bind;
ei->head = head;
ei->data = data;
ei->close = close;
return ei;
}
static void
_emile_image_binbuf_set(Emile_Image *ei, Eina_Binbuf *source)
{
ei->source.bin = source;
ei->bin_source = EINA_TRUE;
}
static void
_emile_image_file_set(Emile_Image *ei, Eina_File *source)
{
ei->source.f = eina_file_dup(source);
ei->bin_source = EINA_FALSE;
}
static Emile_Image *
_emile_image_bind(Emile_Image *ei,
Emile_Image_Load_Opts *opts,
Emile_Image_Animated *animated,
Emile_Image_Load_Error *error)
{
if (opts)
{
ei->opts = *opts;
ei->opts.orientation = !!ei->opts.orientation;
ei->load_opts = 1;
}
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
if (ei->bind(ei, opts, animated, error))
return ei;
/* File is not of that format */
if (!ei->bin_source)
eina_file_close(ei->source.f);
free(ei);
return NULL;
}
/* Public API to manipulate Emile_Image */
EAPI Emile_Image *
emile_image_tgv_memory_open(Eina_Binbuf *source,
Emile_Image_Load_Opts *opts,
Emile_Image_Animated *animated,
Emile_Image_Load_Error *error)
{
Emile_Image *ei;
ei = _emile_image_new(_emile_tgv_bind,
_emile_tgv_head,
_emile_tgv_data,
_emile_tgv_close);
if (!ei)
return NULL;
_emile_image_binbuf_set(ei, source);
return _emile_image_bind(ei, opts, animated, error);
}
EAPI Emile_Image *
emile_image_tgv_file_open(Eina_File *source,
Emile_Image_Load_Opts *opts,
Emile_Image_Animated *animated,
Emile_Image_Load_Error *error)
{
Emile_Image *ei;
ei = _emile_image_new(_emile_tgv_bind,
_emile_tgv_head,
_emile_tgv_data,
_emile_tgv_close);
if (!ei)
return NULL;
_emile_image_file_set(ei, source);
return _emile_image_bind(ei, opts, animated, error);
}
EAPI Emile_Image *
emile_image_jpeg_memory_open(Eina_Binbuf *source,
Emile_Image_Load_Opts *opts,
Emile_Image_Animated *animated,
Emile_Image_Load_Error *error)
{
Emile_Image *ei;
ei = _emile_image_new(_emile_jpeg_bind,
_emile_jpeg_head,
_emile_jpeg_data,
_emile_jpeg_close);
if (!ei)
return NULL;
_emile_image_binbuf_set(ei, source);
return _emile_image_bind(ei, opts, animated, error);
}
EAPI Emile_Image *
emile_image_jpeg_file_open(Eina_File *source,
Emile_Image_Load_Opts *opts,
Emile_Image_Animated *animated,
Emile_Image_Load_Error *error)
{
Emile_Image *ei;
ei = _emile_image_new(_emile_jpeg_bind,
_emile_jpeg_head,
_emile_jpeg_data,
_emile_jpeg_close);
if (!ei)
return NULL;
_emile_image_file_set(ei, source);
return _emile_image_bind(ei, opts, animated, error);
}
EAPI void
emile_image_callback_set(Emile_Image *image, Emile_Action_Cb callback, Emile_Action action, const void *data)
{
if (!image) return ;
// We only handle one type of callback for now
if (action != EMILE_ACTION_CANCELLED) return ;
image->cancelled_data = data;
image->cancelled = callback;
}
EAPI void
emile_image_close(Emile_Image *image)
{
if (!image)
return;
_emile_image_file_source_unmap(image);
if (!image->bin_source)
eina_file_close(image->source.f);
image->close(image);
free(image);
}
EAPI Eina_Bool
emile_image_head(Emile_Image *image,
Emile_Image_Property *prop,
unsigned int property_size,
Emile_Image_Load_Error *error)
{
if (!image)
return EINA_FALSE;
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
return image->head(image, prop, property_size, error);
}
EAPI Eina_Bool
emile_image_data(Emile_Image *image,
Emile_Image_Property *prop,
unsigned int property_size,
void *pixels,
Emile_Image_Load_Error *error)
{
if (!image)
return EINA_FALSE;
*error = EMILE_IMAGE_LOAD_ERROR_NONE;
return image->data(image, prop, property_size, pixels, error);
}
EAPI const char *
emile_load_error_str(Emile_Image *source EINA_UNUSED,
Emile_Image_Load_Error error)
{
switch (error)
{
case EMILE_IMAGE_LOAD_ERROR_NONE:
return "No error";
case EMILE_IMAGE_LOAD_ERROR_GENERIC:
return "Generic error encountered while loading image.";
case EMILE_IMAGE_LOAD_ERROR_DOES_NOT_EXIST:
return "File does not exist.";
case EMILE_IMAGE_LOAD_ERROR_PERMISSION_DENIED:
return "Permission to open file denied.";
case EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED:
return "Not enough memory to open file.";
case EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE:
return "File is corrupted.";
case EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT:
return "Unexpected file format.";
case EMILE_IMAGE_LOAD_ERROR_CANCELLED:
return "Loading was stopped by an external request.";
}
return NULL;
}