Rename GP_Context -> GP_Pixmap
[gfxprim.git] / libs / loaders / GP_JPG.c
blob9cd7178c5375095c0dfc89450e5af363f6dab12d
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-2014 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 "loaders/GP_Exif.h"
41 #include "loaders/GP_LineConvert.h"
42 #include "loaders/GP_JPG.h"
45 * 0xff 0xd8 - start of image
46 * 0xff 0x.. - start of frame
48 #define JPEG_SIGNATURE "\xff\xd8\xff"
49 #define JPEG_SIGNATURE_LEN 3
51 int GP_MatchJPG(const void *buf)
53 return !memcmp(buf, JPEG_SIGNATURE, JPEG_SIGNATURE_LEN);
56 #ifdef HAVE_JPEG
58 #include <jpeglib.h>
60 struct my_jpg_err {
61 struct jpeg_error_mgr error_mgr;
62 jmp_buf setjmp_buf;
65 static void my_error_exit(j_common_ptr cinfo)
67 struct my_jpg_err *my_err = (struct my_jpg_err*) cinfo->err;
69 GP_DEBUG(1, "ERROR reading/writing jpeg file");
71 longjmp(my_err->setjmp_buf, 1);
74 static const char *get_colorspace(J_COLOR_SPACE color_space)
76 switch (color_space) {
77 case JCS_GRAYSCALE:
78 return "Grayscale";
79 case JCS_RGB:
80 return "RGB";
81 case JCS_YCbCr:
82 return "YCbCr";
83 case JCS_CMYK:
84 return "CMYK";
85 case JCS_YCCK:
86 return "YCCK";
87 default:
88 return "Unknown";
92 static int load(struct jpeg_decompress_struct *cinfo, GP_Pixmap *ret,
93 GP_ProgressCallback *callback)
95 while (cinfo->output_scanline < cinfo->output_height) {
96 uint32_t y = cinfo->output_scanline;
97 JSAMPROW addr = (void*)GP_PIXEL_ADDR(ret, 0, y);
99 jpeg_read_scanlines(cinfo, &addr, 1);
101 if (GP_ProgressCallbackReport(callback, y, ret->h, ret->w)) {
102 GP_DEBUG(1, "Operation aborted");
103 return ECANCELED;
107 return 0;
110 static int load_cmyk(struct jpeg_decompress_struct *cinfo, GP_Pixmap *ret,
111 GP_ProgressCallback *callback)
113 while (cinfo->output_scanline < cinfo->output_height) {
114 uint32_t y = cinfo->output_scanline;
116 JSAMPROW addr = (void*)GP_PIXEL_ADDR(ret, 0, y);
117 jpeg_read_scanlines(cinfo, &addr, 1);
119 unsigned int i;
120 uint8_t *buf = GP_PIXEL_ADDR(ret, 0, y);
122 for (i = 0; i < ret->w; i++) {
123 unsigned int j = 4 * i;
125 buf[j] = 0xff - buf[j];
126 buf[j+1] = 0xff - buf[j+1];
127 buf[j+2] = 0xff - buf[j+2];
128 buf[j+3] = 0xff - buf[j+3];
131 if (GP_ProgressCallbackReport(callback, y, ret->h, ret->w)) {
132 GP_DEBUG(1, "Operation aborted");
133 return ECANCELED;
137 return 0;
141 struct my_source_mgr {
142 struct jpeg_source_mgr mgr;
143 void *buffer;
144 size_t size;
145 GP_IO *io;
148 static void dummy_src(j_decompress_ptr GP_UNUSED(cinfo))
152 static boolean fill_input_buffer(struct jpeg_decompress_struct *cinfo)
154 int ret;
155 struct my_source_mgr* src = (void*)cinfo->src;
157 ret = GP_IORead(src->io, src->buffer, src->size);
159 if (ret <= 0) {
160 GP_WARN("Failed to fill buffer, IORead returned %i", ret);
161 return FALSE;
164 src->mgr.next_input_byte = src->buffer;
165 src->mgr.bytes_in_buffer = ret;
166 return TRUE;
169 static void skip_input_data(struct jpeg_decompress_struct *cinfo, long num_bytes)
171 struct my_source_mgr* src = (void*)cinfo->src;
172 off_t ret;
174 GP_DEBUG(3, "Skipping %li bytes", num_bytes);
176 if (src->mgr.bytes_in_buffer < (unsigned long)num_bytes) {
177 ret = GP_IOSeek(src->io, num_bytes - src->mgr.bytes_in_buffer, GP_IO_SEEK_CUR);
178 //TODO: Call jpeg error
179 if (ret == (off_t)-1)
180 GP_FATAL("Failed to skip data: %s", strerror(errno));
181 src->mgr.bytes_in_buffer = 0;
182 } else {
183 src->mgr.bytes_in_buffer -= num_bytes;
184 src->mgr.next_input_byte += num_bytes;
188 static inline void init_source_mgr(struct my_source_mgr *src, GP_IO *io,
189 void *buf, size_t buf_size)
191 src->mgr.init_source = dummy_src;
192 src->mgr.resync_to_restart = jpeg_resync_to_restart;
193 src->mgr.term_source = dummy_src;
194 src->mgr.fill_input_buffer = fill_input_buffer;
195 src->mgr.skip_input_data = skip_input_data;
196 src->mgr.bytes_in_buffer = 0;
197 src->mgr.next_input_byte = NULL;
198 src->io = io;
199 src->buffer = buf;
200 src->size = buf_size;
203 #define JPEG_COM_MAX 128
205 static void read_jpg_metadata(struct jpeg_decompress_struct *cinfo,
206 GP_DataStorage *storage)
208 jpeg_saved_marker_ptr marker;
210 GP_DataStorageAddInt(storage, NULL, "Width", cinfo->image_width);
211 GP_DataStorageAddInt(storage, NULL, "Height", cinfo->image_height);
212 GP_DataStorageAddInt(storage, NULL, "Channels", cinfo->num_components);
213 GP_DataStorageAddString(storage, NULL, "Color Space",
214 get_colorspace(cinfo->out_color_space));
216 for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
217 switch (marker->marker) {
218 case JPEG_COM: {
219 //TODO: Is comment NULL terminated?
220 char buf[marker->data_length+1];
222 GP_DEBUG(3, "JPEG_COM comment block, size %u",
223 (unsigned int)marker->data_length);
225 memcpy(buf, marker->data, marker->data_length);
226 buf[marker->data_length] = '\0';
227 GP_DataStorageAddString(storage, NULL, "Comment", buf);
228 //GP_MetaDataCreateString(data, "comment", (void*)marker->data,
229 // marker->data_length, 1);
230 } break;
231 case JPEG_APP0:
232 GP_TODO("JFIF");
233 break;
234 case JPEG_APP0 + 1: {
235 GP_IO *io = GP_IOMem(marker->data, marker->data_length, NULL);
236 if (!io) {
237 GP_WARN("Failed to create MemIO");
238 return;
240 GP_ReadExif(io, storage);
241 GP_IOClose(io);
242 } break;
247 static void save_jpg_markers(struct jpeg_decompress_struct *cinfo)
249 /* Comment marker */
250 jpeg_save_markers(cinfo, JPEG_COM, JPEG_COM_MAX);
252 /* APP0 marker = JFIF data */
253 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
255 /* APP1 marker = Exif data */
256 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
259 int GP_ReadJPGEx(GP_IO *io, GP_Pixmap **img,
260 GP_DataStorage *storage, GP_ProgressCallback *callback)
262 struct jpeg_decompress_struct cinfo;
263 struct my_source_mgr src;
264 struct my_jpg_err my_err;
265 GP_Pixmap *ret = NULL;
266 uint8_t buf[1024];
267 int err;
269 cinfo.err = jpeg_std_error(&my_err.error_mgr);
270 my_err.error_mgr.error_exit = my_error_exit;
272 if (setjmp(my_err.setjmp_buf)) {
273 err = EIO;
274 goto err2;
277 jpeg_create_decompress(&cinfo);
278 init_source_mgr(&src, io, buf, sizeof(buf));
279 cinfo.src = (void*)&src;
281 if (storage)
282 save_jpg_markers(&cinfo);
284 jpeg_read_header(&cinfo, TRUE);
286 GP_DEBUG(1, "Have %s JPEG size %ux%u %i channels",
287 get_colorspace(cinfo.jpeg_color_space),
288 cinfo.image_width, cinfo.image_height,
289 cinfo.num_components);
291 //TODO: Propagate failure?
292 if (storage)
293 read_jpg_metadata(&cinfo, storage);
295 if (!img)
296 goto exit;
298 GP_Pixel pixel_type;
300 switch (cinfo.out_color_space) {
301 case JCS_GRAYSCALE:
302 pixel_type = GP_PIXEL_G8;
303 break;
304 case JCS_RGB:
305 pixel_type = GP_PIXEL_BGR888;
306 break;
307 case JCS_CMYK:
308 pixel_type = GP_PIXEL_CMYK8888;
309 break;
310 default:
311 pixel_type = GP_PIXEL_UNKNOWN;
314 if (pixel_type == GP_PIXEL_UNKNOWN) {
315 GP_DEBUG(1, "Can't handle %s JPEG output format",
316 get_colorspace(cinfo.out_color_space));
317 err = ENOSYS;
318 goto err1;
321 ret = GP_PixmapAlloc(cinfo.image_width, cinfo.image_height,
322 pixel_type);
324 if (ret == NULL) {
325 GP_DEBUG(1, "Malloc failed :(");
326 err = ENOMEM;
327 goto err1;
330 jpeg_start_decompress(&cinfo);
332 switch (pixel_type) {
333 case GP_PIXEL_BGR888:
334 case GP_PIXEL_G8:
335 err = load(&cinfo, ret, callback);
336 break;
337 case GP_PIXEL_CMYK8888:
338 err = load_cmyk(&cinfo, ret, callback);
339 break;
340 default:
341 err = EINVAL;
344 if (err)
345 goto err2;
347 jpeg_finish_decompress(&cinfo);
348 exit:
349 jpeg_destroy_decompress(&cinfo);
351 GP_ProgressCallbackDone(callback);
353 if (img)
354 *img = ret;
356 return 0;
357 err2:
358 GP_PixmapFree(ret);
359 err1:
360 jpeg_destroy_decompress(&cinfo);
361 errno = err;
362 return 1;
365 static int save_convert(struct jpeg_compress_struct *cinfo,
366 const GP_Pixmap *src,
367 GP_PixelType out_pix,
368 GP_ProgressCallback *callback)
370 uint8_t tmp[(src->w * GP_PixelSize(out_pix)) / 8 + 1];
371 GP_LineConvert Convert;
373 Convert = GP_LineConvertGet(src->pixel_type, out_pix);
375 while (cinfo->next_scanline < cinfo->image_height) {
376 uint32_t y = cinfo->next_scanline;
377 void *in = GP_PIXEL_ADDR(src, 0, y);
379 Convert(in, tmp, src->w);
381 JSAMPROW row = (void*)tmp;
382 jpeg_write_scanlines(cinfo, &row, 1);
384 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
385 GP_DEBUG(1, "Operation aborted");
386 return ECANCELED;
390 return 0;
393 static int save(struct jpeg_compress_struct *cinfo,
394 const GP_Pixmap *src,
395 GP_ProgressCallback *callback)
397 while (cinfo->next_scanline < cinfo->image_height) {
398 uint32_t y = cinfo->next_scanline;
400 JSAMPROW row = (void*)GP_PIXEL_ADDR(src, 0, y);
401 jpeg_write_scanlines(cinfo, &row, 1);
403 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
404 GP_DEBUG(1, "Operation aborted");
405 return ECANCELED;
409 return 0;
412 struct my_dest_mgr {
413 struct jpeg_destination_mgr mgr;
414 void *buffer;
415 ssize_t size;
416 GP_IO *io;
419 static void dummy_dst(j_compress_ptr GP_UNUSED(cinfo))
423 static boolean empty_output_buffer(j_compress_ptr cinfo)
425 struct my_dest_mgr *dest = (void*)cinfo->dest;
427 if (GP_IOWrite(dest->io, dest->buffer, dest->size) != dest->size) {
428 GP_DEBUG(1, "Failed to write JPEG buffer");
429 return FALSE;
432 dest->mgr.next_output_byte = dest->buffer;
433 dest->mgr.free_in_buffer = dest->size;
435 return TRUE;
438 static void term_destination(j_compress_ptr cinfo)
440 struct my_dest_mgr *dest = (void*)cinfo->dest;
441 ssize_t to_write = dest->size - dest->mgr.free_in_buffer;
443 if (to_write > 0) {
444 if (GP_IOWrite(dest->io, dest->buffer, to_write) != to_write) {
445 GP_DEBUG(1, "Failed to write JPEG buffer");
446 //TODO: Error handling
447 return;
452 static inline void init_dest_mgr(struct my_dest_mgr *dst, GP_IO *io,
453 void *buf, size_t buf_size)
455 dst->mgr.init_destination = dummy_dst;
456 dst->mgr.empty_output_buffer = empty_output_buffer;
457 dst->mgr.term_destination = term_destination;
458 dst->mgr.next_output_byte = buf;
459 dst->mgr.free_in_buffer = buf_size;
461 dst->io = io;
462 dst->buffer = buf;
463 dst->size = buf_size;
466 static GP_PixelType out_pixel_types[] = {
467 GP_PIXEL_BGR888,
468 GP_PIXEL_G8,
469 GP_PIXEL_UNKNOWN
472 int GP_WriteJPG(const GP_Pixmap *src, GP_IO *io,
473 GP_ProgressCallback *callback)
475 struct jpeg_compress_struct cinfo;
476 GP_PixelType out_pix;
477 struct my_jpg_err my_err;
478 struct my_dest_mgr dst;
479 uint8_t buf[1024];
480 int err;
482 GP_DEBUG(1, "Writing JPG Image to I/O (%p)", io);
484 out_pix = GP_LineConvertible(src->pixel_type, out_pixel_types);
486 if (out_pix == GP_PIXEL_UNKNOWN) {
487 GP_DEBUG(1, "Unsupported pixel type %s",
488 GP_PixelTypeName(src->pixel_type));
489 errno = ENOSYS;
490 return 1;
493 if (setjmp(my_err.setjmp_buf)) {
494 errno = EIO;
495 return 1;
499 cinfo.err = jpeg_std_error(&my_err.error_mgr);
500 my_err.error_mgr.error_exit = my_error_exit;
502 jpeg_create_compress(&cinfo);
504 init_dest_mgr(&dst, io, buf, sizeof(buf));
505 cinfo.dest = (void*)&dst;
507 cinfo.image_width = src->w;
508 cinfo.image_height = src->h;
510 switch (out_pix) {
511 case GP_PIXEL_BGR888:
512 cinfo.input_components = 3;
513 cinfo.in_color_space = JCS_RGB;
514 break;
515 case GP_PIXEL_G8:
516 cinfo.input_components = 1;
517 cinfo.in_color_space = JCS_GRAYSCALE;
518 break;
519 default:
520 GP_BUG("Don't know how to set color_space and compoments");
523 jpeg_set_defaults(&cinfo);
525 jpeg_start_compress(&cinfo, TRUE);
527 if (out_pix != src->pixel_type)
528 err = save_convert(&cinfo, src, out_pix, callback);
529 else
530 err = save(&cinfo, src, callback);
532 if (err) {
533 jpeg_destroy_compress(&cinfo);
534 errno = err;
535 return 1;
538 jpeg_finish_compress(&cinfo);
539 jpeg_destroy_compress(&cinfo);
541 GP_ProgressCallbackDone(callback);
542 return 0;
545 #else
547 int GP_ReadJPGEx(GP_IO GP_UNUSED(*io), GP_Pixmap GP_UNUSED(**img),
548 GP_DataStorage GP_UNUSED(*storage),
549 GP_ProgressCallback GP_UNUSED(*callback))
551 errno = ENOSYS;
552 return 1;
555 int GP_WriteJPG(const GP_Pixmap GP_UNUSED(*src), GP_IO GP_UNUSED(*io),
556 GP_ProgressCallback GP_UNUSED(*callback))
558 errno = ENOSYS;
559 return 1;
562 #endif /* HAVE_JPEG */
564 GP_Pixmap *GP_ReadJPG(GP_IO *io, GP_ProgressCallback *callback)
566 return GP_LoaderReadImage(&GP_JPG, io, callback);
569 GP_Pixmap *GP_LoadJPG(const char *src_path, GP_ProgressCallback *callback)
571 return GP_LoaderLoadImage(&GP_JPG, src_path, callback);
574 int GP_LoadJPGEx(const char *src_path, GP_Pixmap **img,
575 GP_DataStorage *storage, GP_ProgressCallback *callback)
577 return GP_LoaderLoadImageEx(&GP_JPG, src_path, img, storage, callback);
580 int GP_SaveJPG(const GP_Pixmap *src, const char *dst_path,
581 GP_ProgressCallback *callback)
583 return GP_LoaderSaveImage(&GP_JPG, src, dst_path, callback);
586 struct GP_Loader GP_JPG = {
587 #ifdef HAVE_JPEG
588 .Read = GP_ReadJPGEx,
589 .Write = GP_WriteJPG,
590 .save_ptypes = out_pixel_types,
591 #endif
592 .Match = GP_MatchJPG,
594 .fmt_name = "JPEG",
595 .extensions = {"jpg", "jpeg", NULL},