diff options
author | Cedric BAIL <cedric@osg.samsung.com> | 2015-03-17 08:50:15 +0100 |
---|---|---|
committer | Cedric BAIL <cedric@osg.samsung.com> | 2015-03-17 09:58:18 +0100 |
commit | d40dad8f73bb367b7735212f22b4594725069fa0 (patch) | |
tree | d7e38bfaa797985b86795cbbe49716ff5c8d4c51 /src/lib/emile | |
parent | 65248db8d8868e9e6b95f0544b78767a03542376 (diff) |
emile: initial addition of emile image support.
Diffstat (limited to 'src/lib/emile')
-rw-r--r-- | src/lib/emile/Emile.h | 158 | ||||
-rw-r--r-- | src/lib/emile/emile_image.c | 801 |
2 files changed, 959 insertions, 0 deletions
diff --git a/src/lib/emile/Emile.h b/src/lib/emile/Emile.h index 47a9186d3b..ff3ce7e1cf 100644 --- a/src/lib/emile/Emile.h +++ b/src/lib/emile/Emile.h | |||
@@ -174,6 +174,164 @@ EAPI Eina_Bool emile_binbuf_expand(const Eina_Binbuf *in, | |||
174 | Eina_Binbuf *out, | 174 | Eina_Binbuf *out, |
175 | Emile_Compressor_Type t); | 175 | Emile_Compressor_Type t); |
176 | 176 | ||
177 | /* All the value from below enum should be the same as in Evas_Loader.h */ | ||
178 | typedef enum _Emile_Colorspace | ||
179 | { | ||
180 | EMILE_COLORSPACE_ARGB8888,/**< ARGB 32 bits per pixel, high-byte is Alpha, accessed 1 32bit word at a time */ | ||
181 | EMILE_COLORSPACE_YCBCR422P601_PL, /**< YCbCr 4:2:2 Planar, ITU.BT-601 specifications. The data pointed to is just an array of row pointer, pointing to the Y rows, then the Cb, then Cr rows */ | ||
182 | EMILE_COLORSPACE_YCBCR422P709_PL, /**< YCbCr 4:2:2 Planar, ITU.BT-709 specifications. The data pointed to is just an array of row pointer, pointing to the Y rows, then the Cb, then Cr rows */ | ||
183 | EMILE_COLORSPACE_RGB565_A5P, /**< 16bit rgb565 + Alpha plane at end - 5 bits of the 8 being used per alpha byte */ | ||
184 | EMILE_COLORSPACE_GRY8 = 4, | ||
185 | EMILE_COLORSPACE_YCBCR422601_PL, /**< YCbCr 4:2:2, ITU.BT-601 specifications. The data pointed to is just an array of row pointer, pointing to line of Y,Cb,Y,Cr bytes */ | ||
186 | EMILE_COLORSPACE_YCBCR420NV12601_PL, /**< YCbCr 4:2:0, ITU.BT-601 specification. The data pointed to is just an array of row pointer, pointing to the Y rows, then the Cb,Cr rows. */ | ||
187 | EMILE_COLORSPACE_YCBCR420TM12601_PL, /**< YCbCr 4:2:0, ITU.BT-601 specification. The data pointed to is just an array of tiled row pointer, pointing to the Y rows, then the Cb,Cr rows. */ | ||
188 | EMILE_COLORSPACE_AGRY88 = 8, /**< AY 8bits Alpha and 8bits Grey, accessed 1 16bits at a time */ | ||
189 | EMILE_COLORSPACE_ETC1 = 9, /**< OpenGL ETC1 encoding of RGB texture (4 bit per pixel) @since 1.10 */ | ||
190 | EMILE_COLORSPACE_RGB8_ETC2 = 10, /**< OpenGL GL_COMPRESSED_RGB8_ETC2 texture compression format (4 bit per pixel) @since 1.10 */ | ||
191 | EMILE_COLORSPACE_RGBA8_ETC2_EAC = 11, /**< OpenGL GL_COMPRESSED_RGBA8_ETC2_EAC texture compression format, supports alpha (8 bit per pixel) @since 1.10 */ | ||
192 | EMILE_COLORSPACE_ETC1_ALPHA = 12, /**< ETC1 with alpha support using two planes: ETC1 RGB and ETC1 grey for alpha @since 1.11 */ | ||
193 | EMILE_COLORSPACE_RGB_S3TC_DXT1 = 13, /**< OpenGL COMPRESSED_RGB_S3TC_DXT1_EXT format with RGB only. @since 1.11 */ | ||
194 | EMILE_COLORSPACE_RGBA_S3TC_DXT1 = 14, /**< OpenGL COMPRESSED_RGBA_S3TC_DXT1_EXT format with RGBA punchthrough. @since 1.11 */ | ||
195 | EMILE_COLORSPACE_RGBA_S3TC_DXT2 = 15, /**< DirectDraw DXT2 format with premultiplied RGBA. Not supported by OpenGL itself. @since 1.11 */ | ||
196 | EMILE_COLORSPACE_RGBA_S3TC_DXT3 = 16, /**< OpenGL COMPRESSED_RGBA_S3TC_DXT3_EXT format with RGBA. @since 1.11 */ | ||
197 | EMILE_COLORSPACE_RGBA_S3TC_DXT4 = 17, /**< DirectDraw DXT4 format with premultiplied RGBA. Not supported by OpenGL itself. @since 1.11 */ | ||
198 | EMILE_COLORSPACE_RGBA_S3TC_DXT5 = 18 /**< OpenGL COMPRESSED_RGBA_S3TC_DXT5_EXT format with RGBA. @since 1.11 */ | ||
199 | } Emile_Colorspace; | ||
200 | |||
201 | typedef enum _Emile_Image_Encoding | ||
202 | { | ||
203 | EMILE_IMAGE_LOSSLESS = 0, | ||
204 | EMILE_IMAGE_JPEG = 1, | ||
205 | EMILE_IMAGE_ETC1 = 2, | ||
206 | EMILE_IMAGE_ETC2_RGB = 3, | ||
207 | EMILE_IMAGE_ETC2_RGBA = 4, | ||
208 | EMILE_IMAGE_ETC1_ALPHA = 5 | ||
209 | } Emile_Image_Encoding; | ||
210 | |||
211 | typedef enum _Emile_Image_Scale_Hint | ||
212 | { | ||
213 | EMILE_IMAGE_SCALE_HINT_NONE = 0, /**< No scale hint at all */ | ||
214 | EMILE_IMAGE_SCALE_HINT_DYNAMIC = 1, /**< Image is being re-scaled over time, thus turning scaling cache @b off for its data */ | ||
215 | EMILE_IMAGE_SCALE_HINT_STATIC = 2 /**< Image is not being re-scaled over time, thus turning scaling cache @b on for its data */ | ||
216 | } Emile_Image_Scale_Hint; | ||
217 | |||
218 | typedef enum _Emile_Image_Animated_Loop_Hint | ||
219 | { | ||
220 | EMILE_IMAGE_ANIMATED_HINT_NONE = 0, | ||
221 | EMILE_IMAGE_ANIMATED_HINT_LOOP = 1, | ||
222 | EMILE_IMAGE_ANIMATED_HINT_PINGPONG = 2 | ||
223 | } Emile_Image_Animated_Loop_Hint; | ||
224 | |||
225 | typedef enum _Emile_Image_Load_Error | ||
226 | { | ||
227 | EMILE_IMAGE_LOAD_ERROR_NONE = 0, /**< No error on load */ | ||
228 | EMILE_IMAGE_LOAD_ERROR_GENERIC = 1, /**< A non-specific error occurred */ | ||
229 | EMILE_IMAGE_LOAD_ERROR_DOES_NOT_EXIST = 2, /**< File (or file path) does not exist */ | ||
230 | EMILE_IMAGE_LOAD_ERROR_PERMISSION_DENIED = 3, /**< Permission denied to an existing file (or path) */ | ||
231 | EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED = 4, /**< Allocation of resources failure prevented load */ | ||
232 | EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE = 5, /**< File corrupt (but was detected as a known format) */ | ||
233 | EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT = 6 /**< File is not a known format */ | ||
234 | } Emile_Image_Load_Error; /**< Emile image load error codes one can get - see emile_load_error_str() too. */ | ||
235 | |||
236 | typedef struct _Emile_Image Emile_Image; | ||
237 | typedef struct _Emile_Image_Property Emile_Image_Property; | ||
238 | typedef struct _Emile_Image_Load_Opts Emile_Image_Load_Opts; | ||
239 | typedef struct _Emile_Image_Animated Emile_Image_Animated; | ||
240 | |||
241 | struct _Emile_Image_Property | ||
242 | { | ||
243 | struct { | ||
244 | unsigned char l, r, t, b; | ||
245 | } borders; | ||
246 | |||
247 | const Emile_Colorspace *cspaces; | ||
248 | Emile_Colorspace cspace; | ||
249 | |||
250 | Emile_Image_Encoding encoding; | ||
251 | |||
252 | unsigned int w; | ||
253 | unsigned int h; | ||
254 | unsigned int row_stride; | ||
255 | |||
256 | unsigned char scale; | ||
257 | |||
258 | Eina_Bool rotated; | ||
259 | Eina_Bool alpha; | ||
260 | Eina_Bool premul; | ||
261 | Eina_Bool alpha_sparse; | ||
262 | |||
263 | Eina_Bool flipped; | ||
264 | Eina_Bool comp; | ||
265 | }; | ||
266 | |||
267 | struct _Emile_Image_Animated | ||
268 | { | ||
269 | Eina_List *frames; | ||
270 | |||
271 | Emile_Image_Animated_Loop_Hint loop_hint; | ||
272 | |||
273 | int frame_count; | ||
274 | int loop_count; | ||
275 | int cur_frame; | ||
276 | |||
277 | Eina_Bool animated; | ||
278 | }; | ||
279 | |||
280 | struct _Emile_Image_Load_Opts | ||
281 | { | ||
282 | struct { | ||
283 | int x, y, w, h; | ||
284 | } region; | ||
285 | struct { | ||
286 | int src_x, src_y, src_w, src_h; | ||
287 | int dst_w, dst_h; | ||
288 | int smooth; | ||
289 | Emile_Image_Scale_Hint scale_hint; | ||
290 | } scale_load; | ||
291 | double dpi; | ||
292 | unsigned int w, h; | ||
293 | unsigned int degree; | ||
294 | int scale_down_by; | ||
295 | |||
296 | Eina_Bool orientation; | ||
297 | }; | ||
298 | |||
299 | // FIXME: Add enum for error code | ||
300 | // FIXME: should set region at load time, not head time | ||
301 | |||
302 | EAPI Emile_Image *emile_image_tgv_memory_open(Eina_Binbuf *source, | ||
303 | Emile_Image_Load_Opts *opts, | ||
304 | Emile_Image_Animated *animated, | ||
305 | Emile_Image_Load_Error *error); | ||
306 | EAPI Emile_Image *emile_image_tgv_file_open(Eina_File *source, | ||
307 | Emile_Image_Load_Opts *opts, | ||
308 | Emile_Image_Animated *animated, | ||
309 | Emile_Image_Load_Error *error); | ||
310 | |||
311 | EAPI Emile_Image *emile_image_jpeg_memory_open(Eina_Binbuf *source, | ||
312 | Emile_Image_Load_Opts *opts, | ||
313 | Emile_Image_Animated *animated, | ||
314 | Emile_Image_Load_Error *error); | ||
315 | EAPI Emile_Image *emile_image_jpeg_file_open(Eina_File *source, | ||
316 | Emile_Image_Load_Opts *opts, | ||
317 | Emile_Image_Animated *animated, | ||
318 | Emile_Image_Load_Error *error); | ||
319 | |||
320 | EAPI Eina_Bool emile_image_head(Emile_Image *image, | ||
321 | Emile_Image_Property *prop, | ||
322 | unsigned int property_size, | ||
323 | Emile_Image_Load_Error *error); | ||
324 | EAPI Eina_Bool emile_image_data(Emile_Image *image, | ||
325 | Emile_Image_Property *prop, | ||
326 | unsigned int property_size, | ||
327 | void *pixels, | ||
328 | Emile_Image_Load_Error *error); | ||
329 | |||
330 | EAPI void emile_image_close(Emile_Image *source); | ||
331 | |||
332 | EAPI const char *emile_load_error_str(Emile_Image *source, | ||
333 | Emile_Image_Load_Error error); | ||
334 | |||
177 | #ifdef __cplusplus | 335 | #ifdef __cplusplus |
178 | } | 336 | } |
179 | #endif /* ifdef __cplusplus */ | 337 | #endif /* ifdef __cplusplus */ |
diff --git a/src/lib/emile/emile_image.c b/src/lib/emile/emile_image.c new file mode 100644 index 0000000000..9f26a9aa00 --- /dev/null +++ b/src/lib/emile/emile_image.c | |||
@@ -0,0 +1,801 @@ | |||
1 | #ifdef HAVE_CONFIG_H | ||
2 | # include <config.h> | ||
3 | #endif /* ifdef HAVE_CONFIG_H */ | ||
4 | |||
5 | #ifdef HAVE_NETINET_IN_H | ||
6 | # include <netinet/in.h> | ||
7 | #endif | ||
8 | |||
9 | #ifdef _WIN32 | ||
10 | # include <winsock2.h> | ||
11 | #endif /* ifdef _WIN32 */ | ||
12 | |||
13 | #ifdef ENABLE_LIBLZ4 | ||
14 | # include <lz4.h> | ||
15 | #else | ||
16 | # include "lz4.h" | ||
17 | #endif | ||
18 | |||
19 | #include "rg_etc1.h" | ||
20 | #include "Emile.h" | ||
21 | |||
22 | #ifdef BUILD_NEON | ||
23 | #include <arm_neon.h> | ||
24 | #endif | ||
25 | |||
26 | #ifndef WORDS_BIGENDIAN | ||
27 | /* x86 */ | ||
28 | #define A_VAL(p) (((uint8_t *)(p))[3]) | ||
29 | #define R_VAL(p) (((uint8_t *)(p))[2]) | ||
30 | #define G_VAL(p) (((uint8_t *)(p))[1]) | ||
31 | #define B_VAL(p) (((uint8_t *)(p))[0]) | ||
32 | #else | ||
33 | /* ppc */ | ||
34 | #define A_VAL(p) (((uint8_t *)(p))[0]) | ||
35 | #define R_VAL(p) (((uint8_t *)(p))[1]) | ||
36 | #define G_VAL(p) (((uint8_t *)(p))[2]) | ||
37 | #define B_VAL(p) (((uint8_t *)(p))[3]) | ||
38 | #endif | ||
39 | |||
40 | #define OFFSET_BLOCK_SIZE 4 | ||
41 | #define OFFSET_ALGORITHM 5 | ||
42 | #define OFFSET_OPTIONS 6 | ||
43 | #define OFFSET_WIDTH 8 | ||
44 | #define OFFSET_HEIGHT 12 | ||
45 | #define OFFSET_BLOCKS 16 | ||
46 | |||
47 | struct _Emile_Image | ||
48 | { | ||
49 | Emile_Image_Load_Opts opts; | ||
50 | |||
51 | struct { | ||
52 | unsigned int width; | ||
53 | unsigned int height; | ||
54 | } size, block; | ||
55 | |||
56 | Eina_Rectangle region; | ||
57 | |||
58 | union { | ||
59 | Eina_Binbuf *bin; | ||
60 | Eina_File *f; | ||
61 | } source; | ||
62 | |||
63 | const unsigned char *source_data; | ||
64 | |||
65 | Eina_Bool (*bind)(Emile_Image *image, | ||
66 | Emile_Image_Load_Opts *opts, | ||
67 | Emile_Image_Animated *animated, | ||
68 | Emile_Image_Load_Error *error); | ||
69 | Eina_Bool (*head)(Emile_Image *image, | ||
70 | Emile_Image_Property *prop, | ||
71 | unsigned int property_size, | ||
72 | Emile_Image_Load_Error *error); | ||
73 | Eina_Bool (*data)(Emile_Image *image, | ||
74 | Emile_Image_Property *prop, | ||
75 | unsigned int property_size, | ||
76 | void *pixels, | ||
77 | Emile_Image_Load_Error *error); | ||
78 | void (*close)(Emile_Image *image); | ||
79 | |||
80 | Emile_Colorspace cspace; | ||
81 | |||
82 | Eina_Bool bin_source : 1; | ||
83 | |||
84 | /* TGV option */ | ||
85 | Eina_Bool unpremul : 1; | ||
86 | Eina_Bool compress : 1; | ||
87 | Eina_Bool blockless : 1; | ||
88 | Eina_Bool load_opts : 1; | ||
89 | }; | ||
90 | |||
91 | static const unsigned char * | ||
92 | _emile_image_file_source_map(Emile_Image *image, unsigned int *length) | ||
93 | { | ||
94 | if (!image) return NULL; | ||
95 | |||
96 | if (image->bin_source) | ||
97 | { | ||
98 | if (length) *length = eina_binbuf_length_get(image->source.bin); | ||
99 | return eina_binbuf_string_get(image->source.bin); | ||
100 | } | ||
101 | |||
102 | if (length) *length = eina_file_size_get(image->source.f); | ||
103 | if (!image->source_data) | ||
104 | { | ||
105 | image->source_data = eina_file_map_all(image->source.f, | ||
106 | EINA_FILE_SEQUENTIAL); | ||
107 | } | ||
108 | return image->source_data; | ||
109 | } | ||
110 | |||
111 | static void | ||
112 | _emile_image_file_source_unmap(Emile_Image *image) | ||
113 | { | ||
114 | if (!(image && image->source_data)) return ; | ||
115 | eina_file_map_free(image->source.f, | ||
116 | (void*) image->source_data); | ||
117 | image->source_data = NULL; | ||
118 | } | ||
119 | |||
120 | static int | ||
121 | roundup(int val, int rup) | ||
122 | { | ||
123 | if (val >= 0 && rup > 0) | ||
124 | return (val + rup - 1) - ((val + rup - 1) % rup); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | /* TGV Handling */ | ||
129 | |||
130 | static const Emile_Colorspace cspaces_etc1[2] = { | ||
131 | EMILE_COLORSPACE_ETC1, | ||
132 | EMILE_COLORSPACE_ARGB8888 | ||
133 | }; | ||
134 | |||
135 | static const Emile_Colorspace cspaces_rgb8_etc2[2] = { | ||
136 | EMILE_COLORSPACE_RGB8_ETC2, | ||
137 | EMILE_COLORSPACE_ARGB8888 | ||
138 | }; | ||
139 | |||
140 | static const Emile_Colorspace cspaces_rgba8_etc2_eac[2] = { | ||
141 | EMILE_COLORSPACE_RGBA8_ETC2_EAC, | ||
142 | EMILE_COLORSPACE_ARGB8888 | ||
143 | }; | ||
144 | |||
145 | static const Emile_Colorspace cspaces_etc1_alpha[2] = { | ||
146 | EMILE_COLORSPACE_ETC1_ALPHA, | ||
147 | EMILE_COLORSPACE_ARGB8888 | ||
148 | }; | ||
149 | |||
150 | /************************************************************** | ||
151 | * The TGV file format is oriented around compression mecanism | ||
152 | * that hardware are good at decompressing. We do still provide | ||
153 | * a fully software implementation in case your hardware doesn't | ||
154 | * handle it. As OpenGL is pretty bad at handling border of | ||
155 | * texture, we do duplicate the first pixels of every border. | ||
156 | * | ||
157 | * This file format is designed to compress/decompress things | ||
158 | * in block area. Giving opportunity to store really huge file | ||
159 | * and only decompress/compress them as we need. Note that region | ||
160 | * only work with software decompression as we don't have a sane | ||
161 | * way to duplicate border to avoid artifact when scaling texture. | ||
162 | * | ||
163 | * The file format is as follow : | ||
164 | * - char magic[4]: "TGV1" | ||
165 | * - uint8_t block_size (real block size = (4 << bits[0-3], 4 << bits[4-7]) | ||
166 | * - uint8_t algorithm (0 -> ETC1, 1 -> ETC2 RGB, 2 -> ETC2 RGBA, 3 -> ETC1+Alpha) | ||
167 | * - uint8_t options[2] (bitmask: 1 -> lz4, 2 for block-less, 4 -> unpremultiplied) | ||
168 | * - uint32_t width | ||
169 | * - uint32_t height | ||
170 | * - blocks[] | ||
171 | * - 0 length encoded compress size (if length == 64 * block_size => no compression) | ||
172 | * - lzma encoded etc1 block | ||
173 | * | ||
174 | * If the format is ETC1+Alpha (algo = 3), then a second image is encoded | ||
175 | * in ETC1 right after the first one, and it contains grey-scale alpha | ||
176 | * values. | ||
177 | **************************************************************/ | ||
178 | |||
179 | // FIXME: wondering if we should support mipmap | ||
180 | // TODO: support ETC1+ETC2 images (RGB only) | ||
181 | |||
182 | static Eina_Bool | ||
183 | _emile_tgv_bind(Emile_Image *image, | ||
184 | Emile_Image_Load_Opts *opts, | ||
185 | Emile_Image_Animated *animated, | ||
186 | Emile_Image_Load_Error *error) | ||
187 | { | ||
188 | const unsigned char *m; | ||
189 | unsigned int length; | ||
190 | |||
191 | m = _emile_image_file_source_map(image, &length); | ||
192 | if (!m) return EINA_FALSE; | ||
193 | |||
194 | /* Fast check for file characteristic (useful when trying to guess | ||
195 | a file format without extention). */ | ||
196 | if (length < 16 || | ||
197 | strncmp((const char*) m, "TGV1", 4) != 0) | ||
198 | goto on_error; | ||
199 | |||
200 | if ((m[OFFSET_ALGORITHM] & 0xFF) > 3) | ||
201 | goto on_error; | ||
202 | |||
203 | return EINA_TRUE; | ||
204 | |||
205 | on_error: | ||
206 | *error = EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT; | ||
207 | _emile_image_file_source_unmap(image); | ||
208 | return EINA_FALSE; | ||
209 | } | ||
210 | |||
211 | static Eina_Bool | ||
212 | _emile_tgv_head(Emile_Image *image, | ||
213 | Emile_Image_Property *prop, | ||
214 | unsigned int property_size, | ||
215 | Emile_Image_Load_Error *error) | ||
216 | { | ||
217 | const unsigned char *m; | ||
218 | unsigned int length; | ||
219 | |||
220 | m = _emile_image_file_source_map(image, &length); | ||
221 | if (!m) return EINA_FALSE; | ||
222 | |||
223 | switch (m[OFFSET_ALGORITHM] & 0xFF) | ||
224 | { | ||
225 | case 0: | ||
226 | prop->cspaces = cspaces_etc1; | ||
227 | prop->alpha = EINA_FALSE; | ||
228 | image->cspace = EMILE_COLORSPACE_ETC1; | ||
229 | break; | ||
230 | case 1: | ||
231 | prop->cspaces = cspaces_rgb8_etc2; | ||
232 | prop->alpha = EINA_FALSE; | ||
233 | image->cspace = EMILE_COLORSPACE_RGB8_ETC2; | ||
234 | break; | ||
235 | case 2: | ||
236 | prop->cspaces = cspaces_rgba8_etc2_eac; | ||
237 | prop->alpha = EINA_TRUE; | ||
238 | image->cspace = EMILE_COLORSPACE_RGBA8_ETC2_EAC; | ||
239 | break; | ||
240 | case 3: | ||
241 | prop->cspaces = cspaces_etc1_alpha; | ||
242 | prop->alpha = EINA_TRUE; | ||
243 | prop->premul = !!(m[OFFSET_OPTIONS] & 0x4); | ||
244 | image->unpremul = prop->premul; | ||
245 | break; | ||
246 | default: | ||
247 | *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; | ||
248 | return EINA_FALSE; | ||
249 | } | ||
250 | |||
251 | image->compress = m[OFFSET_OPTIONS] & 0x1; | ||
252 | image->blockless = (m[OFFSET_OPTIONS] & 0x2) != 0; | ||
253 | |||
254 | image->size.width = ntohl(*((unsigned int*) &(m[OFFSET_WIDTH]))); | ||
255 | image->size.height = ntohl(*((unsigned int*) &(m[OFFSET_HEIGHT]))); | ||
256 | |||
257 | if (image->blockless) | ||
258 | { | ||
259 | image->block.width = roundup(image->size.width + 2, 4); | ||
260 | image->block.height = roundup(image->size.height + 2, 4); | ||
261 | } | ||
262 | else | ||
263 | { | ||
264 | image->block.width = 4 << (m[OFFSET_BLOCK_SIZE] & 0x0f); | ||
265 | image->block.height = 4 << ((m[OFFSET_BLOCK_SIZE] & 0xf0) >> 4); | ||
266 | } | ||
267 | |||
268 | EINA_RECTANGLE_SET(&image->region, 0, 0, | ||
269 | image->size.width, image->size.height); | ||
270 | if (image->load_opts && | ||
271 | (image->opts.region.w > 0 && image->opts.region.h > 0)) | ||
272 | { | ||
273 | // ETC colorspace doesn't work with region for now | ||
274 | prop->cspaces = NULL; | ||
275 | |||
276 | if (!eina_rectangle_intersection(&image->region, | ||
277 | &image->opts.region)) | ||
278 | { | ||
279 | *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; | ||
280 | return EINA_FALSE; | ||
281 | } | ||
282 | } | ||
283 | |||
284 | prop->w = image->size.width; | ||
285 | prop->h = image->size.height; | ||
286 | prop->borders.l = 1; | ||
287 | prop->borders.t = 1; | ||
288 | prop->borders.r = roundup(prop->w + 2, 4) - prop->w - 1; | ||
289 | prop->borders.b = roundup(prop->h + 2, 4) - prop->h - 1; | ||
290 | |||
291 | return EINA_TRUE; | ||
292 | } | ||
293 | |||
294 | static inline unsigned int | ||
295 | _tgv_length_get(const unsigned char *m, | ||
296 | unsigned int length, | ||
297 | unsigned int *offset) | ||
298 | { | ||
299 | unsigned int r = 0; | ||
300 | unsigned int shift = 0; | ||
301 | |||
302 | while (*offset < length && ((*m) & 0x80)) | ||
303 | { | ||
304 | r = r | (((*m) & 0x7F) << shift); | ||
305 | shift += 7; | ||
306 | m++; | ||
307 | (*offset)++; | ||
308 | } | ||
309 | if (*offset < length) | ||
310 | { | ||
311 | r = r | (((*m) & 0x7F) << shift); | ||
312 | (*offset)++; | ||
313 | } | ||
314 | |||
315 | return r; | ||
316 | } | ||
317 | |||
318 | static Eina_Bool | ||
319 | _emile_tgv_data(Emile_Image *image, | ||
320 | Emile_Image_Property *prop, | ||
321 | unsigned int property_size, | ||
322 | void *pixels, | ||
323 | Emile_Image_Load_Error *error) | ||
324 | { | ||
325 | const unsigned char *m; | ||
326 | unsigned int *p = pixels; | ||
327 | unsigned char *p_etc = pixels; | ||
328 | Eina_Binbuf *buffer = NULL; | ||
329 | Eina_Rectangle master; | ||
330 | unsigned int block_length; | ||
331 | unsigned int length, offset; | ||
332 | unsigned int x, y; | ||
333 | unsigned int block_count; | ||
334 | unsigned int etc_width = 0; | ||
335 | unsigned int etc_block_size; | ||
336 | int num_planes = 1, plane, alpha_offset = 0; | ||
337 | Eina_Bool r = EINA_FALSE; | ||
338 | |||
339 | m = _emile_image_file_source_map(image, &length); | ||
340 | if (!m) | ||
341 | { | ||
342 | *error = EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED; | ||
343 | return EINA_FALSE; | ||
344 | } | ||
345 | |||
346 | offset = OFFSET_BLOCKS; | ||
347 | |||
348 | *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; | ||
349 | |||
350 | // By definition, prop{.w, .h} == region{.w, .h} | ||
351 | EINA_RECTANGLE_SET(&master, | ||
352 | image->region.x, image->region.y, | ||
353 | prop->w, prop->h); | ||
354 | |||
355 | switch (image->cspace) | ||
356 | { | ||
357 | case EMILE_COLORSPACE_ETC1: | ||
358 | case EMILE_COLORSPACE_RGB8_ETC2: | ||
359 | etc_block_size = 8; | ||
360 | break; | ||
361 | case EMILE_COLORSPACE_RGBA8_ETC2_EAC: | ||
362 | etc_block_size = 16; | ||
363 | break; | ||
364 | case EMILE_COLORSPACE_ETC1_ALPHA: | ||
365 | etc_block_size = 8; | ||
366 | num_planes = 2; | ||
367 | alpha_offset = ((prop->w + 2 + 3) / 4) * ((prop->h + 2 + 3) / 4) * 8 / sizeof(*p_etc); | ||
368 | break; | ||
369 | default: abort(); | ||
370 | } | ||
371 | etc_width = ((prop->w + 2 + 3) / 4) * etc_block_size; | ||
372 | |||
373 | switch (prop->cspace) | ||
374 | { | ||
375 | case EMILE_COLORSPACE_ETC1: | ||
376 | case EMILE_COLORSPACE_RGB8_ETC2: | ||
377 | case EMILE_COLORSPACE_RGBA8_ETC2_EAC: | ||
378 | case EMILE_COLORSPACE_ETC1_ALPHA: | ||
379 | if (master.x % 4 || master.y % 4) | ||
380 | // FIXME: Should we really abort here ? Seems like a late check for me | ||
381 | abort(); | ||
382 | break; | ||
383 | case EMILE_COLORSPACE_ARGB8888: | ||
384 | // Offset to take duplicated pixels into account | ||
385 | master.x += 1; | ||
386 | master.y += 1; | ||
387 | break; | ||
388 | default: abort(); | ||
389 | } | ||
390 | |||
391 | |||
392 | if (prop->cspace != EMILE_COLORSPACE_ARGB8888 && prop->cspace != image->cspace) | ||
393 | { | ||
394 | if (!((prop->cspace == EMILE_COLORSPACE_RGB8_ETC2) && | ||
395 | (image->cspace == EMILE_COLORSPACE_ETC1))) | ||
396 | { | ||
397 | *error = EMILE_IMAGE_LOAD_ERROR_GENERIC; | ||
398 | return EINA_FALSE; | ||
399 | } | ||
400 | // else: ETC2 is compatible with ETC1 and is preferred | ||
401 | } | ||
402 | |||
403 | // Allocate space for each ETC block (8 or 16 bytes per 4 * 4 pixels group) | ||
404 | block_count = image->block.width * image->block.height / (4 * 4); | ||
405 | if (image->compress) | ||
406 | buffer = eina_binbuf_manage_read_only_new_length(alloca(etc_block_size * block_count), etc_block_size * block_count); | ||
407 | |||
408 | for (plane = 0; plane < num_planes; plane++) | ||
409 | for (y = 0; y < image->size.height + 2; y += image->block.height) | ||
410 | for (x = 0; x < image->size.width + 2; x += image->block.width) | ||
411 | { | ||
412 | Eina_Rectangle current; | ||
413 | Eina_Binbuf *data_start; | ||
414 | const unsigned char *it; | ||
415 | unsigned int expand_length; | ||
416 | unsigned int i, j; | ||
417 | |||
418 | block_length = _tgv_length_get(m + offset, length, &offset); | ||
419 | |||
420 | if (block_length == 0) | ||
421 | { | ||
422 | *error = EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE; | ||
423 | return EINA_FALSE; | ||
424 | } | ||
425 | |||
426 | data_start = eina_binbuf_manage_read_only_new_length(m + offset, block_length); | ||
427 | offset += block_length; | ||
428 | |||
429 | EINA_RECTANGLE_SET(¤t, x, y, | ||
430 | image->block.width, image->block.height); | ||
431 | |||
432 | if (!eina_rectangle_intersection(¤t, &master)) | ||
433 | continue; | ||
434 | |||
435 | if (image->compress) | ||
436 | { | ||
437 | if (!emile_binbuf_expand(data_start, buffer, EMILE_LZ4HC)) | ||
438 | goto on_error; | ||
439 | } | ||
440 | else | ||
441 | { | ||
442 | buffer = data_start; | ||
443 | if (block_count * etc_block_size != block_length) | ||
444 | goto on_error; | ||
445 | } | ||
446 | it = eina_binbuf_string_get(buffer); | ||
447 | |||
448 | for (i = 0; i < image->block.height; i += 4) | ||
449 | for (j = 0; j < image->block.width; j += 4, it += etc_block_size) | ||
450 | { | ||
451 | Eina_Rectangle current_etc; | ||
452 | unsigned int temporary[4 * 4]; | ||
453 | unsigned int offset_x, offset_y; | ||
454 | int k, l; | ||
455 | |||
456 | EINA_RECTANGLE_SET(¤t_etc, x + j, y + i, 4, 4); | ||
457 | |||
458 | if (!eina_rectangle_intersection(¤t_etc, ¤t)) | ||
459 | continue; | ||
460 | |||
461 | switch (prop->cspace) | ||
462 | { | ||
463 | case EMILE_COLORSPACE_ARGB8888: | ||
464 | switch (image->cspace) | ||
465 | { | ||
466 | case EMILE_COLORSPACE_ETC1: | ||
467 | case EMILE_COLORSPACE_ETC1_ALPHA: | ||
468 | if (!rg_etc1_unpack_block(it, temporary, 0)) | ||
469 | { | ||
470 | // TODO: Should we decode as RGB8_ETC2? | ||
471 | fprintf(stderr, "ETC1: Block starting at {%i, %i} is corrupted!\n", x + j, y + i); | ||
472 | continue; | ||
473 | } | ||
474 | break; | ||
475 | case EMILE_COLORSPACE_RGB8_ETC2: | ||
476 | rg_etc2_rgb8_decode_block((uint8_t *) it, temporary); | ||
477 | break; | ||
478 | case EMILE_COLORSPACE_RGBA8_ETC2_EAC: | ||
479 | rg_etc2_rgba8_decode_block((uint8_t *) it, temporary); | ||
480 | break; | ||
481 | default: abort(); | ||
482 | } | ||
483 | |||
484 | offset_x = current_etc.x - x - j; | ||
485 | offset_y = current_etc.y - y - i; | ||
486 | |||
487 | if (!plane) | ||
488 | { | ||
489 | #ifdef BUILD_NEON | ||
490 | if (eina_cpu_features_get() & EINA_CPU_NEON) | ||
491 | { | ||
492 | uint32_t *dst = &p[current_etc.x - 1 + (current_etc.y - 1) * master.w]; | ||
493 | uint32_t *src = &temporary[offset_x + offset_y * 4]; | ||
494 | for (k = 0; k < current_etc.h; k++) | ||
495 | { | ||
496 | if (current_etc.w == 4) | ||
497 | vst1q_u32(dst, vld1q_u32(src)); | ||
498 | else if (current_etc.w == 3) | ||
499 | { | ||
500 | vst1_u32(dst, vld1_u32(src)); | ||
501 | *(dst + 2) = *(src + 2); | ||
502 | } | ||
503 | else if (current_etc.w == 2) | ||
504 | vst1_u32(dst, vld1_u32(src)); | ||
505 | else | ||
506 | *dst = *src; | ||
507 | dst += master.w; | ||
508 | src += 4; | ||
509 | } | ||
510 | } | ||
511 | else | ||
512 | #endif | ||
513 | for (k = 0; k < current_etc.h; k++) | ||
514 | { | ||
515 | memcpy(&p[current_etc.x - 1 + (current_etc.y - 1 + k) * master.w], | ||
516 | &temporary[offset_x + (offset_y + k) * 4], | ||
517 | current_etc.w * sizeof (unsigned int)); | ||
518 | } | ||
519 | } | ||
520 | else | ||
521 | { | ||
522 | for (k = 0; k < current_etc.h; k++) | ||
523 | for (l = 0; l < current_etc.w; l++) | ||
524 | { | ||
525 | unsigned int *rgbdata = | ||
526 | &p[current_etc.x - 1 + (current_etc.y - 1 + k) * master.w + l]; | ||
527 | unsigned int *adata = | ||
528 | &temporary[offset_x + (offset_y + k) * 4 + l]; | ||
529 | A_VAL(rgbdata) = G_VAL(adata); | ||
530 | } | ||
531 | } | ||
532 | break; | ||
533 | case EMILE_COLORSPACE_ETC1: | ||
534 | case EMILE_COLORSPACE_RGB8_ETC2: | ||
535 | case EMILE_COLORSPACE_RGBA8_ETC2_EAC: | ||
536 | memcpy(&p_etc[(current_etc.x / 4) * etc_block_size + | ||
537 | (current_etc.y / 4) * etc_width], | ||
538 | it, etc_block_size); | ||
539 | break; | ||
540 | case EMILE_COLORSPACE_ETC1_ALPHA: | ||
541 | memcpy(&p_etc[(current_etc.x / 4) * etc_block_size + | ||
542 | (current_etc.y / 4) * etc_width + | ||
543 | plane * alpha_offset], | ||
544 | it, etc_block_size); | ||
545 | break; | ||
546 | default: | ||
547 | abort(); | ||
548 | } | ||
549 | } // bx,by inside blocks | ||
550 | } // x,y macroblocks | ||
551 | |||
552 | // TODO: Add support for more unpremultiplied modes (ETC2) | ||
553 | if (prop->cspace == EMILE_COLORSPACE_ARGB8888) | ||
554 | prop->premul = image->unpremul; // call premul if unpremul data | ||
555 | |||
556 | r = EINA_TRUE; | ||
557 | |||
558 | on_error: | ||
559 | return r; | ||
560 | } | ||
561 | |||
562 | static void | ||
563 | _emile_tgv_close(Emile_Image *image EINA_UNUSED) | ||
564 | { | ||
565 | // TGV file loader doesn't keep any data allocated around | ||
566 | } | ||
567 | |||
568 | /* JPEG Handling */ | ||
569 | static Eina_Bool | ||
570 | _emile_jpeg_bind(Emile_Image *image, | ||
571 | Emile_Image_Load_Opts *opts, | ||
572 | Emile_Image_Animated *animated, | ||
573 | Emile_Image_Load_Error *error) | ||
574 | { | ||
575 | return EINA_FALSE; | ||
576 | } | ||
577 | |||
578 | static Eina_Bool | ||
579 | _emile_jpeg_head(Emile_Image *image, | ||
580 | Emile_Image_Property *prop, | ||
581 | unsigned int property_size, | ||
582 | Emile_Image_Load_Error *error) | ||
583 | { | ||
584 | return EINA_FALSE; | ||
585 | } | ||
586 | |||
587 | static Eina_Bool | ||
588 | _emile_jpeg_data(Emile_Image *image, | ||
589 | Emile_Image_Property *prop, | ||
590 | unsigned int property_size, | ||
591 | void *pixels, | ||
592 | Emile_Image_Load_Error *error) | ||
593 | { | ||
594 | return EINA_FALSE; | ||
595 | } | ||
596 | |||
597 | static void | ||
598 | _emile_jpeg_close(Emile_Image *image) | ||
599 | { | ||
600 | } | ||
601 | |||
602 | /* Generic helper to instantiate a new Emile_Image */ | ||
603 | |||
604 | static Emile_Image * | ||
605 | _emile_image_new(Eina_Bool (*bind) (Emile_Image *image, | ||
606 | Emile_Image_Load_Opts *opts, | ||
607 | Emile_Image_Animated *animated, | ||
608 | Emile_Image_Load_Error *error), | ||
609 | Eina_Bool (*head)(Emile_Image *image, | ||
610 | Emile_Image_Property *prop, | ||
611 | unsigned int property_size, | ||
612 | Emile_Image_Load_Error *error), | ||
613 | Eina_Bool (*data)(Emile_Image *image, | ||
614 | Emile_Image_Property *prop, | ||
615 | unsigned int property_size, | ||
616 | void *pixels, | ||
617 | Emile_Image_Load_Error *error), | ||
618 | void (*close)(Emile_Image *image)) | ||
619 | { | ||
620 | Emile_Image *ei; | ||
621 | |||
622 | ei = calloc(1, sizeof (Emile_Image)); | ||
623 | if (!ei) return NULL; | ||
624 | |||
625 | ei->bind = bind; | ||
626 | ei->head = head; | ||
627 | ei->data = data; | ||
628 | ei->close = close; | ||
629 | |||
630 | return ei; | ||
631 | } | ||
632 | |||
633 | static void | ||
634 | _emile_image_binbuf_set(Emile_Image *ei, Eina_Binbuf *source) | ||
635 | { | ||
636 | ei->source.bin = source; | ||
637 | ei->bin_source = EINA_TRUE; | ||
638 | } | ||
639 | |||
640 | static void | ||
641 | _emile_image_file_set(Emile_Image *ei, Eina_File *source) | ||
642 | { | ||
643 | ei->source.f = eina_file_dup(source); | ||
644 | ei->bin_source = EINA_FALSE; | ||
645 | } | ||
646 | |||
647 | static Emile_Image * | ||
648 | _emile_image_bind(Emile_Image *ei, | ||
649 | Emile_Image_Load_Opts *opts, | ||
650 | Emile_Image_Animated *animated, | ||
651 | Emile_Image_Load_Error *error) | ||
652 | { | ||
653 | if (opts) | ||
654 | { | ||
655 | ei->opts = *opts; | ||
656 | ei->load_opts = 1; | ||
657 | } | ||
658 | |||
659 | *error = EMILE_IMAGE_LOAD_ERROR_NONE; | ||
660 | if (ei->bind(ei, opts, animated, error)) | ||
661 | return ei; | ||
662 | |||
663 | /* File is not of that format */ | ||
664 | free(ei); | ||
665 | return NULL; | ||
666 | } | ||
667 | |||
668 | /* Public API to manipulate Emile_Image */ | ||
669 | |||
670 | EAPI Emile_Image * | ||
671 | emile_image_tgv_memory_open(Eina_Binbuf *source, | ||
672 | Emile_Image_Load_Opts *opts, | ||
673 | Emile_Image_Animated *animated, | ||
674 | Emile_Image_Load_Error *error) | ||
675 | { | ||
676 | Emile_Image *ei; | ||
677 | |||
678 | ei = _emile_image_new(_emile_tgv_bind, | ||
679 | _emile_tgv_head, | ||
680 | _emile_tgv_data, | ||
681 | _emile_tgv_close); | ||
682 | if (!ei) return NULL; | ||
683 | |||
684 | _emile_image_binbuf_set(ei, source); | ||
685 | return _emile_image_bind(ei, opts, animated, error); | ||
686 | } | ||
687 | |||
688 | EAPI Emile_Image * | ||
689 | emile_image_tgv_file_open(Eina_File *source, | ||
690 | Emile_Image_Load_Opts *opts, | ||
691 | Emile_Image_Animated *animated, | ||
692 | Emile_Image_Load_Error *error) | ||
693 | { | ||
694 | Emile_Image *ei; | ||
695 | |||
696 | ei = _emile_image_new(_emile_tgv_bind, | ||
697 | _emile_tgv_head, | ||
698 | _emile_tgv_data, | ||
699 | _emile_tgv_close); | ||
700 | if (!ei) return NULL; | ||
701 | |||
702 | _emile_image_file_set(ei, source); | ||
703 | return _emile_image_bind(ei, opts, animated, error); | ||
704 | } | ||
705 | |||
706 | EAPI Emile_Image * | ||
707 | emile_image_jpeg_memory_open(Eina_Binbuf *source, | ||
708 | Emile_Image_Load_Opts *opts, | ||
709 | Emile_Image_Animated *animated, | ||
710 | Emile_Image_Load_Error *error) | ||
711 | { | ||
712 | Emile_Image *ei; | ||
713 | |||
714 | ei = _emile_image_new(_emile_jpeg_bind, | ||
715 | _emile_jpeg_head, | ||
716 | _emile_jpeg_data, | ||
717 | _emile_jpeg_close); | ||
718 | if (!ei) return NULL; | ||
719 | |||
720 | _emile_image_binbuf_set(ei, source); | ||
721 | return _emile_image_bind(ei, opts, animated, error); | ||
722 | } | ||
723 | |||
724 | EAPI Emile_Image * | ||
725 | emile_image_jpeg_file_open(Eina_File *source, | ||
726 | Emile_Image_Load_Opts *opts, | ||
727 | Emile_Image_Animated *animated, | ||
728 | Emile_Image_Load_Error *error) | ||
729 | { | ||
730 | Emile_Image *ei; | ||
731 | |||
732 | ei = _emile_image_new(_emile_jpeg_bind, | ||
733 | _emile_jpeg_head, | ||
734 | _emile_jpeg_data, | ||
735 | _emile_jpeg_close); | ||
736 | if (!ei) return NULL; | ||
737 | |||
738 | _emile_image_file_set(ei, source); | ||
739 | return _emile_image_bind(ei, opts, animated, error); | ||
740 | } | ||
741 | |||
742 | EAPI void | ||
743 | emile_image_close(Emile_Image *image) | ||
744 | { | ||
745 | if (!image) return ; | ||
746 | |||
747 | _emile_image_file_source_unmap(image); | ||
748 | if (!image->bin_source) | ||
749 | eina_file_close(image->source.f); | ||
750 | image->close(image); | ||
751 | free(image); | ||
752 | } | ||
753 | |||
754 | EAPI Eina_Bool | ||
755 | emile_image_head(Emile_Image *image, | ||
756 | Emile_Image_Property *prop, | ||
757 | unsigned int property_size, | ||
758 | Emile_Image_Load_Error *error) | ||
759 | { | ||
760 | if (!image) return EINA_FALSE; | ||
761 | |||
762 | *error = EMILE_IMAGE_LOAD_ERROR_NONE; | ||
763 | return image->head(image, prop, property_size, error); | ||
764 | } | ||
765 | |||
766 | EAPI Eina_Bool | ||
767 | emile_image_data(Emile_Image *image, | ||
768 | Emile_Image_Property *prop, | ||
769 | unsigned int property_size, | ||
770 | void *pixels, | ||
771 | Emile_Image_Load_Error *error) | ||
772 | { | ||
773 | if (!image) return EINA_FALSE; | ||
774 | |||
775 | *error = EMILE_IMAGE_LOAD_ERROR_NONE; | ||
776 | return image->data(image, prop, property_size, pixels, error); | ||
777 | } | ||
778 | |||
779 | EAPI const char * | ||
780 | emile_load_error_str(Emile_Image *source EINA_UNUSED, | ||
781 | Emile_Image_Load_Error error) | ||
782 | { | ||
783 | switch (error) | ||
784 | { | ||
785 | case EMILE_IMAGE_LOAD_ERROR_NONE: | ||
786 | return "No error"; | ||
787 | case EMILE_IMAGE_LOAD_ERROR_GENERIC: | ||
788 | return "Generic error encountered while loading image."; | ||
789 | case EMILE_IMAGE_LOAD_ERROR_DOES_NOT_EXIST: | ||
790 | return "File does not exist."; | ||
791 | case EMILE_IMAGE_LOAD_ERROR_PERMISSION_DENIED: | ||
792 | return "Permission to open file denied."; | ||
793 | case EMILE_IMAGE_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED: | ||
794 | return "Not enough memory to open file."; | ||
795 | case EMILE_IMAGE_LOAD_ERROR_CORRUPT_FILE: | ||
796 | return "File is corrupted."; | ||
797 | case EMILE_IMAGE_LOAD_ERROR_UNKNOWN_FORMAT: | ||
798 | return "Unexpected file format."; | ||
799 | } | ||
800 | return NULL; | ||
801 | } | ||