loaders: Added Exif to MetaData parser.
[gfxprim.git] / libs / loaders / GP_JPG.c
blob72e4c86c9c8e7301124fada8001adb1b911d5c7a
1 /*****************************************************************************
2 * This file is part of gfxprim library. *
3 * *
4 * Gfxprim is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU Lesser General Public *
6 * License as published by the Free Software Foundation; either *
7 * version 2.1 of the License, or (at your option) any later version. *
8 * *
9 * Gfxprim is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
12 * Lesser General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU Lesser General Public *
15 * License along with gfxprim; if not, write to the Free Software *
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
17 * Boston, MA 02110-1301 USA *
18 * *
19 * Copyright (C) 2009-2012 Cyril Hrubis <metan@ucw.cz> *
20 * *
21 *****************************************************************************/
25 JPG image support using jpeg library.
29 #include <stdint.h>
30 #include <inttypes.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <stdio.h>
35 #include <setjmp.h>
37 #include "../../config.h"
38 #include "core/GP_Debug.h"
40 #include "GP_JPG.h"
42 #ifdef HAVE_JPEG
44 #include <jpeglib.h>
46 int GP_OpenJPG(const char *src_path, FILE **f)
48 int err;
50 *f = fopen(src_path, "rb");
52 if (*f == NULL) {
53 err = errno;
54 GP_DEBUG(1, "Failed to open '%s' : %s",
55 src_path, strerror(errno));
56 errno = err;
57 return 1;
60 //TODO: check signature and rewind the stream
62 return 0;
65 struct my_jpg_err {
66 struct jpeg_error_mgr error_mgr;
67 jmp_buf setjmp_buf;
70 static void my_error_exit(j_common_ptr cinfo)
72 struct my_jpg_err *my_err = (struct my_jpg_err*) cinfo->err;
74 GP_DEBUG(1, "ERROR reading/writing jpeg file");
76 longjmp(my_err->setjmp_buf, 1);
79 static const char *get_colorspace(J_COLOR_SPACE color_space)
81 switch (color_space) {
82 case JCS_GRAYSCALE:
83 return "Grayscale";
84 case JCS_RGB:
85 return "RGB";
86 case JCS_YCbCr:
87 return "YCbCr";
88 case JCS_CMYK:
89 return "CMYK";
90 case JCS_YCCK:
91 return "YCCK";
92 default:
93 return "Unknown";
97 GP_Context *GP_ReadJPG(FILE *f, GP_ProgressCallback *callback)
99 struct jpeg_decompress_struct cinfo;
100 struct my_jpg_err my_err;
101 GP_Context *ret = NULL;
102 int err;
104 cinfo.err = jpeg_std_error(&my_err.error_mgr);
105 my_err.error_mgr.error_exit = my_error_exit;
107 if (setjmp(my_err.setjmp_buf)) {
108 err = EIO;
109 goto err2;
112 jpeg_create_decompress(&cinfo);
113 jpeg_stdio_src(&cinfo, f);
115 jpeg_read_header(&cinfo, TRUE);
117 GP_DEBUG(1, "Have %s JPEG size %ux%u %i channels",
118 get_colorspace(cinfo.jpeg_color_space),
119 cinfo.image_width, cinfo.image_height,
120 cinfo.num_components);
122 GP_Pixel pixel_type;
124 switch (cinfo.out_color_space) {
125 case JCS_GRAYSCALE:
126 pixel_type = GP_PIXEL_G8;
127 break;
128 case JCS_RGB:
129 pixel_type = GP_PIXEL_RGB888;
130 break;
131 default:
132 pixel_type = GP_PIXEL_UNKNOWN;
135 if (pixel_type == GP_PIXEL_UNKNOWN) {
136 GP_DEBUG(1, "Can't handle %s JPEG output format",
137 get_colorspace(cinfo.out_color_space));
138 err = ENOSYS;
139 goto err1;
142 ret = GP_ContextAlloc(cinfo.image_width, cinfo.image_height,
143 pixel_type);
145 if (ret == NULL) {
146 GP_DEBUG(1, "Malloc failed :(");
147 err = ENOMEM;
148 goto err1;
151 jpeg_start_decompress(&cinfo);
153 while (cinfo.output_scanline < cinfo.output_height) {
154 uint32_t y = cinfo.output_scanline;
156 JSAMPROW addr = (void*)GP_PIXEL_ADDR(ret, 0, y);
157 jpeg_read_scanlines(&cinfo, &addr, 1);
159 if (pixel_type != GP_PIXEL_RGB888)
160 continue;
162 //TODO: fixme bigendian?
163 /* fix the pixel, as we want in fact BGR */
164 uint32_t i;
166 for (i = 0; i < ret->w; i++) {
167 uint8_t *pix = GP_PIXEL_ADDR(ret, i, y);
168 GP_SWAP(pix[0], pix[2]);
171 if (GP_ProgressCallbackReport(callback, y, ret->h, ret->w)) {
172 GP_DEBUG(1, "Operation aborted");
173 err = ECANCELED;
174 goto err2;
178 jpeg_finish_decompress(&cinfo);
179 jpeg_destroy_decompress(&cinfo);
181 GP_ProgressCallbackDone(callback);
183 return ret;
184 err2:
185 GP_ContextFree(ret);
186 err1:
187 jpeg_destroy_decompress(&cinfo);
188 errno = err;
189 return NULL;
192 GP_Context *GP_LoadJPG(const char *src_path, GP_ProgressCallback *callback)
194 FILE *f;
195 GP_Context *res;
197 if (GP_OpenJPG(src_path, &f))
198 return NULL;
201 res = GP_ReadJPG(f, callback);
203 fclose(f);
205 return res;
208 #define JPEG_COM_MAX 128
210 static void read_jpg_metadata(struct jpeg_decompress_struct *cinfo,
211 GP_MetaData *data)
213 jpeg_saved_marker_ptr marker;
215 for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
216 switch (marker->marker) {
217 case JPEG_COM:
218 GP_MetaDataCreateString(data, "comment", (void*)marker->data,
219 marker->data_length, 1);
220 break;
221 case JPEG_APP0:
222 GP_DEBUG(0, "TODO: JFIF");
223 break;
224 case JPEG_APP0 + 1:
225 GP_MetaDataFromExif(data, marker->data, marker->data_length);
226 break;
231 static void save_jpg_markers(struct jpeg_decompress_struct *cinfo)
233 /* Comment marker */
234 jpeg_save_markers(cinfo, JPEG_COM, JPEG_COM_MAX);
236 /* APP0 marker = JFIF data */
237 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
239 /* APP1 marker = Exif data */
240 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
243 int GP_ReadJPGMetaData(FILE *f, GP_MetaData *data)
245 struct jpeg_decompress_struct cinfo;
246 struct my_jpg_err my_err;
247 int err;
249 cinfo.err = jpeg_std_error(&my_err.error_mgr);
250 my_err.error_mgr.error_exit = my_error_exit;
252 if (setjmp(my_err.setjmp_buf)) {
253 err = EIO;
254 goto err1;
257 jpeg_create_decompress(&cinfo);
258 jpeg_stdio_src(&cinfo, f);
260 save_jpg_markers(&cinfo);
262 jpeg_read_header(&cinfo, TRUE);
264 GP_DEBUG(1, "Have %s JPEG size %ux%u %i channels",
265 get_colorspace(cinfo.jpeg_color_space),
266 cinfo.image_width, cinfo.image_height,
267 cinfo.num_components);
269 read_jpg_metadata(&cinfo, data);
271 // jpeg_finish_decompress(&cinfo);
272 jpeg_destroy_decompress(&cinfo);
274 return 0;
275 err1:
276 jpeg_destroy_decompress(&cinfo);
277 errno = err;
278 return 1;
281 int GP_LoadJPGMetaData(const char *src_path, GP_MetaData *data)
283 FILE *f;
284 int ret;
286 if (GP_OpenJPG(src_path, &f))
287 return 1;
289 ret = GP_ReadJPGMetaData(f, data);
291 fclose(f);
293 return ret;
296 int GP_SaveJPG(const GP_Context *src, const char *dst_path,
297 GP_ProgressCallback *callback)
299 FILE *f;
300 struct jpeg_compress_struct cinfo;
301 struct my_jpg_err my_err;
302 int err;
304 GP_DEBUG(1, "Saving JPG Image '%s'", dst_path);
306 if (src->pixel_type != GP_PIXEL_RGB888 &&
307 src->pixel_type != GP_PIXEL_G8) {
308 GP_DEBUG(1, "Can't save png with pixel type %s",
309 GP_PixelTypeName(src->pixel_type));
310 errno = ENOSYS;
311 return 1;
314 f = fopen(dst_path, "wb");
316 if (f == NULL) {
317 err = errno;
318 GP_DEBUG(1, "Failed to open '%s' for writing: %s",
319 dst_path, strerror(errno));
320 goto err0;
323 if (setjmp(my_err.setjmp_buf)) {
324 err = EIO;
325 goto err2;
328 cinfo.err = jpeg_std_error(&my_err.error_mgr);
329 my_err.error_mgr.error_exit = my_error_exit;
331 jpeg_create_compress(&cinfo);
333 jpeg_stdio_dest(&cinfo, f);
335 cinfo.image_width = src->w;
336 cinfo.image_height = src->h;
337 cinfo.input_components = src->pixel_type == GP_PIXEL_RGB888 ? 3 : 1;
338 cinfo.in_color_space = JCS_RGB;
340 jpeg_set_defaults(&cinfo);
342 jpeg_start_compress(&cinfo, TRUE);
344 while (cinfo.next_scanline < cinfo.image_height) {
345 uint32_t y = cinfo.next_scanline;
347 if (src->pixel_type == GP_PIXEL_RGB888) {
348 uint32_t i;
349 uint8_t tmp[3 * src->w];
351 memcpy(tmp, GP_PIXEL_ADDR(src, 0, y), 3 * src->w);
353 /* fix the pixels as we want in fact BGR */
354 for (i = 0; i < src->w; i++) {
355 uint8_t *pix = tmp + 3 * i;
356 GP_SWAP(pix[0], pix[2]);
359 JSAMPROW row = (void*)tmp;
360 jpeg_write_scanlines(&cinfo, &row, 1);
361 } else {
362 JSAMPROW row = (void*)GP_PIXEL_ADDR(src, 0, y);
363 jpeg_write_scanlines(&cinfo, &row, 1);
366 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
367 GP_DEBUG(1, "Operation aborted");
368 err = ECANCELED;
369 goto err2;
373 jpeg_finish_compress(&cinfo);
375 if (fclose(f)) {
376 err = errno;
377 GP_DEBUG(1, "Failed to close file '%s': %s",
378 dst_path, strerror(errno));
379 goto err1;
382 GP_ProgressCallbackDone(callback);
383 return 0;
384 //TODO: is cinfo allocated?
385 err2:
386 jpeg_destroy_compress(&cinfo);
387 fclose(f);
388 err1:
389 unlink(dst_path);
390 err0:
391 errno = err;
392 return 1;
395 #else
397 int GP_OpenJPG(const char GP_UNUSED(*src_path), FILE GP_UNUSED(**f))
399 errno = ENOSYS;
400 return 1;
403 GP_Context *GP_ReadJPG(FILE GP_UNUSED(*f),
404 GP_ProgressCallback GP_UNUSED(*callback))
406 errno = ENOSYS;
407 return NULL;
410 GP_Context *GP_LoadJPG(const char GP_UNUSED(*src_path),
411 GP_ProgressCallback GP_UNUSED(*callback))
413 errno = ENOSYS;
414 return NULL;
417 int GP_ReadJPGMetaData(FILE GP_UNUSED(*f), GP_MetaData GP_UNUSED(*data))
419 errno = ENOSYS;
420 return NULL;
423 int GP_LoadJPGMetaData(const char GP_UNUSED(*src_path),
424 GP_MetaData GP_UNUSED(*data))
426 errno = ENOSYS;
427 return NULL;
430 int GP_SaveJPG(const GP_Context GP_UNUSED(*src),
431 const char GP_UNUSED(*dst_path),
432 GP_ProgressCallback GP_UNUSED(*callback))
434 errno = ENOSYS;
435 return 1;
438 #endif /* HAVE_JPEG */