loaders: Implement Write() instead of Save()
[gfxprim.git] / libs / loaders / GP_JPG.c
blobfa2713a93b58ca3f46fd1f63cc974b23fd596aae
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_LineConvert.h"
41 #include "loaders/GP_JPG.h"
44 * 0xff 0xd8 - start of image
45 * 0xff 0x.. - start of frame
47 #define JPEG_SIGNATURE "\xff\xd8\xff"
48 #define JPEG_SIGNATURE_LEN 3
50 int GP_MatchJPG(const void *buf)
52 return !memcmp(buf, JPEG_SIGNATURE, JPEG_SIGNATURE_LEN);
55 #ifdef HAVE_JPEG
57 #include <jpeglib.h>
59 struct my_jpg_err {
60 struct jpeg_error_mgr error_mgr;
61 jmp_buf setjmp_buf;
64 static void my_error_exit(j_common_ptr cinfo)
66 struct my_jpg_err *my_err = (struct my_jpg_err*) cinfo->err;
68 GP_DEBUG(1, "ERROR reading/writing jpeg file");
70 longjmp(my_err->setjmp_buf, 1);
73 static const char *get_colorspace(J_COLOR_SPACE color_space)
75 switch (color_space) {
76 case JCS_GRAYSCALE:
77 return "Grayscale";
78 case JCS_RGB:
79 return "RGB";
80 case JCS_YCbCr:
81 return "YCbCr";
82 case JCS_CMYK:
83 return "CMYK";
84 case JCS_YCCK:
85 return "YCCK";
86 default:
87 return "Unknown";
91 static int load(struct jpeg_decompress_struct *cinfo, GP_Context *ret,
92 GP_ProgressCallback *callback)
94 while (cinfo->output_scanline < cinfo->output_height) {
95 uint32_t y = cinfo->output_scanline;
96 JSAMPROW addr = (void*)GP_PIXEL_ADDR(ret, 0, y);
98 jpeg_read_scanlines(cinfo, &addr, 1);
100 if (GP_ProgressCallbackReport(callback, y, ret->h, ret->w)) {
101 GP_DEBUG(1, "Operation aborted");
102 return ECANCELED;
106 return 0;
109 static int load_cmyk(struct jpeg_decompress_struct *cinfo, GP_Context *ret,
110 GP_ProgressCallback *callback)
112 while (cinfo->output_scanline < cinfo->output_height) {
113 uint32_t y = cinfo->output_scanline;
115 JSAMPROW addr = (void*)GP_PIXEL_ADDR(ret, 0, y);
116 jpeg_read_scanlines(cinfo, &addr, 1);
118 unsigned int i;
119 uint8_t *buf = GP_PIXEL_ADDR(ret, 0, y);
121 for (i = 0; i < ret->w; i++) {
122 unsigned int j = 4 * i;
124 buf[j] = 0xff - buf[j];
125 buf[j+1] = 0xff - buf[j+1];
126 buf[j+2] = 0xff - buf[j+2];
127 buf[j+3] = 0xff - buf[j+3];
130 if (GP_ProgressCallbackReport(callback, y, ret->h, ret->w)) {
131 GP_DEBUG(1, "Operation aborted");
132 return ECANCELED;
136 return 0;
140 struct my_source_mgr {
141 struct jpeg_source_mgr mgr;
142 void *buffer;
143 size_t size;
144 GP_IO *io;
147 static void dummy_src(j_decompress_ptr GP_UNUSED(cinfo))
151 static boolean fill_input_buffer(struct jpeg_decompress_struct *cinfo)
153 int ret;
154 struct my_source_mgr* src = (void*)cinfo->src;
156 ret = GP_IORead(src->io, src->buffer, src->size);
158 if (ret < 0) {
159 GP_WARN("Failed to fill buffer");
160 return FALSE;
163 src->mgr.next_input_byte = src->buffer;
164 src->mgr.bytes_in_buffer = ret;
165 return TRUE;
168 static void skip_input_data(struct jpeg_decompress_struct *cinfo, long num_bytes)
170 struct my_source_mgr* src = (void*)cinfo->src;
171 off_t ret;
173 GP_DEBUG(3, "Skipping %li bytes", num_bytes);
175 if (src->mgr.bytes_in_buffer < (unsigned long)num_bytes) {
176 ret = GP_IOSeek(src->io, num_bytes - src->mgr.bytes_in_buffer, GP_IO_SEEK_CUR);
177 //TODO: Call jpeg error
178 if (ret == (off_t)-1)
179 GP_FATAL("Failed to skip data: %s", strerror(errno));
180 src->mgr.bytes_in_buffer = 0;
181 } else {
182 src->mgr.bytes_in_buffer -= num_bytes;
183 src->mgr.next_input_byte += num_bytes;
187 static inline void init_source_mgr(struct my_source_mgr *src, GP_IO *io,
188 void *buf, size_t buf_size)
190 src->mgr.init_source = dummy_src;
191 src->mgr.resync_to_restart = jpeg_resync_to_restart;
192 src->mgr.term_source = dummy_src;
193 src->mgr.fill_input_buffer = fill_input_buffer;
194 src->mgr.skip_input_data = skip_input_data;
195 src->mgr.bytes_in_buffer = 0;
196 src->mgr.next_input_byte = NULL;
197 src->io = io;
198 src->buffer = buf;
199 src->size = buf_size;
202 GP_Context *GP_ReadJPG(GP_IO *io, GP_ProgressCallback *callback)
204 struct jpeg_decompress_struct cinfo;
205 struct my_source_mgr src;
206 struct my_jpg_err my_err;
207 GP_Context *ret = NULL;
208 uint8_t buf[1024];
209 int err;
211 cinfo.err = jpeg_std_error(&my_err.error_mgr);
212 my_err.error_mgr.error_exit = my_error_exit;
214 if (setjmp(my_err.setjmp_buf)) {
215 err = EIO;
216 goto err2;
219 jpeg_create_decompress(&cinfo);
220 init_source_mgr(&src, io, buf, sizeof(buf));
221 cinfo.src = (void*)&src;
223 jpeg_read_header(&cinfo, TRUE);
225 GP_DEBUG(1, "Have %s JPEG size %ux%u %i channels",
226 get_colorspace(cinfo.jpeg_color_space),
227 cinfo.image_width, cinfo.image_height,
228 cinfo.num_components);
230 GP_Pixel pixel_type;
232 switch (cinfo.out_color_space) {
233 case JCS_GRAYSCALE:
234 pixel_type = GP_PIXEL_G8;
235 break;
236 case JCS_RGB:
237 pixel_type = GP_PIXEL_BGR888;
238 break;
239 case JCS_CMYK:
240 pixel_type = GP_PIXEL_CMYK8888;
241 break;
242 default:
243 pixel_type = GP_PIXEL_UNKNOWN;
246 if (pixel_type == GP_PIXEL_UNKNOWN) {
247 GP_DEBUG(1, "Can't handle %s JPEG output format",
248 get_colorspace(cinfo.out_color_space));
249 err = ENOSYS;
250 goto err1;
253 ret = GP_ContextAlloc(cinfo.image_width, cinfo.image_height,
254 pixel_type);
256 if (ret == NULL) {
257 GP_DEBUG(1, "Malloc failed :(");
258 err = ENOMEM;
259 goto err1;
262 jpeg_start_decompress(&cinfo);
264 switch (pixel_type) {
265 case GP_PIXEL_BGR888:
266 case GP_PIXEL_G8:
267 err = load(&cinfo, ret, callback);
268 break;
269 case GP_PIXEL_CMYK8888:
270 err = load_cmyk(&cinfo, ret, callback);
271 break;
272 default:
273 err = EINVAL;
276 if (err)
277 goto err2;
279 jpeg_finish_decompress(&cinfo);
280 jpeg_destroy_decompress(&cinfo);
282 GP_ProgressCallbackDone(callback);
284 return ret;
285 err2:
286 GP_ContextFree(ret);
287 err1:
288 jpeg_destroy_decompress(&cinfo);
289 errno = err;
290 return NULL;
293 #define JPEG_COM_MAX 128
295 static void read_jpg_metadata(struct jpeg_decompress_struct *cinfo,
296 GP_MetaData *data)
298 jpeg_saved_marker_ptr marker;
300 for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
301 switch (marker->marker) {
302 case JPEG_COM:
303 GP_MetaDataCreateString(data, "comment", (void*)marker->data,
304 marker->data_length, 1);
305 break;
306 case JPEG_APP0:
307 GP_TODO("JFIF");
308 break;
309 case JPEG_APP0 + 1:
310 GP_MetaDataFromExif(data, marker->data, marker->data_length);
311 break;
316 static void save_jpg_markers(struct jpeg_decompress_struct *cinfo)
318 /* Comment marker */
319 jpeg_save_markers(cinfo, JPEG_COM, JPEG_COM_MAX);
321 /* APP0 marker = JFIF data */
322 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
324 /* APP1 marker = Exif data */
325 jpeg_save_markers(cinfo, JPEG_APP0 + 1, 0xffff);
328 int GP_ReadJPGMetaData(GP_IO *io, GP_MetaData *data)
330 struct jpeg_decompress_struct cinfo;
331 struct my_source_mgr src;
332 struct my_jpg_err my_err;
333 uint8_t buf[1024];
334 int err;
336 cinfo.err = jpeg_std_error(&my_err.error_mgr);
337 my_err.error_mgr.error_exit = my_error_exit;
339 if (setjmp(my_err.setjmp_buf)) {
340 err = EIO;
341 goto err1;
344 jpeg_create_decompress(&cinfo);
345 init_source_mgr(&src, io, buf, sizeof(buf));
346 cinfo.src = (void*)&src;
348 save_jpg_markers(&cinfo);
350 jpeg_read_header(&cinfo, TRUE);
352 GP_DEBUG(1, "Have %s JPEG size %ux%u %i channels",
353 get_colorspace(cinfo.jpeg_color_space),
354 cinfo.image_width, cinfo.image_height,
355 cinfo.num_components);
357 read_jpg_metadata(&cinfo, data);
359 // jpeg_finish_decompress(&cinfo);
360 jpeg_destroy_decompress(&cinfo);
362 return 0;
363 err1:
364 jpeg_destroy_decompress(&cinfo);
365 errno = err;
366 return 1;
369 int GP_LoadJPGMetaData(const char *src_path, GP_MetaData *data)
371 GP_IO *io;
372 int err, ret;
374 io = GP_IOFile(src_path, GP_IO_RDONLY);
375 if (!io)
376 return 1;
378 ret = GP_ReadJPGMetaData(io, data);
380 err = errno;
381 GP_IOClose(io);
382 errno = err;
384 return ret;
387 static int save_convert(struct jpeg_compress_struct *cinfo,
388 const GP_Context *src,
389 GP_PixelType out_pix,
390 GP_ProgressCallback *callback)
392 uint8_t tmp[(src->w * GP_PixelSize(out_pix)) / 8 + 1];
393 GP_LineConvert Convert;
395 Convert = GP_LineConvertGet(src->pixel_type, out_pix);
397 while (cinfo->next_scanline < cinfo->image_height) {
398 uint32_t y = cinfo->next_scanline;
399 void *in = GP_PIXEL_ADDR(src, 0, y);
401 Convert(in, tmp, src->w);
403 JSAMPROW row = (void*)tmp;
404 jpeg_write_scanlines(cinfo, &row, 1);
406 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
407 GP_DEBUG(1, "Operation aborted");
408 return ECANCELED;
412 return 0;
415 static int save(struct jpeg_compress_struct *cinfo,
416 const GP_Context *src,
417 GP_ProgressCallback *callback)
419 while (cinfo->next_scanline < cinfo->image_height) {
420 uint32_t y = cinfo->next_scanline;
422 JSAMPROW row = (void*)GP_PIXEL_ADDR(src, 0, y);
423 jpeg_write_scanlines(cinfo, &row, 1);
425 if (GP_ProgressCallbackReport(callback, y, src->h, src->w)) {
426 GP_DEBUG(1, "Operation aborted");
427 return ECANCELED;
431 return 0;
434 struct my_dest_mgr {
435 struct jpeg_destination_mgr mgr;
436 void *buffer;
437 ssize_t size;
438 GP_IO *io;
441 static void dummy_dst(j_compress_ptr GP_UNUSED(cinfo))
445 static boolean empty_output_buffer(j_compress_ptr cinfo)
447 struct my_dest_mgr *dest = (void*)cinfo->dest;
449 if (GP_IOWrite(dest->io, dest->buffer, dest->size) != dest->size) {
450 GP_DEBUG(1, "Failed to write JPEG buffer");
451 return FALSE;
454 dest->mgr.next_output_byte = dest->buffer;
455 dest->mgr.free_in_buffer = dest->size;
457 return TRUE;
460 static void term_destination(j_compress_ptr cinfo)
462 struct my_dest_mgr *dest = (void*)cinfo->dest;
463 ssize_t to_write = dest->size - dest->mgr.free_in_buffer;
465 if (to_write > 0) {
466 if (GP_IOWrite(dest->io, dest->buffer, to_write) != to_write) {
467 GP_DEBUG(1, "Failed to write JPEG buffer");
468 //TODO: Error handling
469 return;
474 static inline void init_dest_mgr(struct my_dest_mgr *dst, GP_IO *io,
475 void *buf, size_t buf_size)
477 dst->mgr.init_destination = dummy_dst;
478 dst->mgr.empty_output_buffer = empty_output_buffer;
479 dst->mgr.term_destination = term_destination;
480 dst->mgr.next_output_byte = buf;
481 dst->mgr.free_in_buffer = buf_size;
483 dst->io = io;
484 dst->buffer = buf;
485 dst->size = buf_size;
488 static GP_PixelType out_pixel_types[] = {
489 GP_PIXEL_BGR888,
490 GP_PIXEL_G8,
491 GP_PIXEL_UNKNOWN
494 int GP_WriteJPG(const GP_Context *src, GP_IO *io,
495 GP_ProgressCallback *callback)
497 struct jpeg_compress_struct cinfo;
498 GP_PixelType out_pix;
499 struct my_jpg_err my_err;
500 struct my_dest_mgr dst;
501 uint8_t buf[1024];
502 int err;
504 GP_DEBUG(1, "Writing JPG Image to I/O (%p)", io);
506 out_pix = GP_LineConvertible(src->pixel_type, out_pixel_types);
508 if (out_pix == GP_PIXEL_UNKNOWN) {
509 GP_DEBUG(1, "Unsupported pixel type %s",
510 GP_PixelTypeName(src->pixel_type));
511 errno = ENOSYS;
512 return 1;
515 if (setjmp(my_err.setjmp_buf)) {
516 errno = EIO;
517 return 1;
521 cinfo.err = jpeg_std_error(&my_err.error_mgr);
522 my_err.error_mgr.error_exit = my_error_exit;
524 jpeg_create_compress(&cinfo);
526 init_dest_mgr(&dst, io, buf, sizeof(buf));
527 cinfo.dest = (void*)&dst;
529 cinfo.image_width = src->w;
530 cinfo.image_height = src->h;
532 switch (out_pix) {
533 case GP_PIXEL_BGR888:
534 cinfo.input_components = 3;
535 cinfo.in_color_space = JCS_RGB;
536 break;
537 case GP_PIXEL_G8:
538 cinfo.input_components = 1;
539 cinfo.in_color_space = JCS_GRAYSCALE;
540 break;
541 default:
542 GP_BUG("Don't know how to set color_space and compoments");
545 jpeg_set_defaults(&cinfo);
547 jpeg_start_compress(&cinfo, TRUE);
549 if (out_pix != src->pixel_type)
550 err = save_convert(&cinfo, src, out_pix, callback);
551 else
552 err = save(&cinfo, src, callback);
554 if (err) {
555 jpeg_destroy_compress(&cinfo);
556 errno = err;
557 return 1;
560 jpeg_finish_compress(&cinfo);
561 jpeg_destroy_compress(&cinfo);
564 GP_ProgressCallbackDone(callback);
565 return 0;
568 #else
570 GP_Context *GP_ReadJPG(GP_IO GP_UNUSED(*io),
571 GP_ProgressCallback GP_UNUSED(*callback))
573 errno = ENOSYS;
574 return NULL;
577 int GP_WriteJPG(const GP_Context *src, GP_IO *io,
578 GP_ProgressCallback *callback)
580 errno = ENOSYS;
581 return 1;
584 int GP_ReadJPGMetaData(GP_IO GP_UNUSED(*io), GP_MetaData GP_UNUSED(*data))
586 errno = ENOSYS;
587 return 1;
590 int GP_LoadJPGMetaData(const char GP_UNUSED(*src_path),
591 GP_MetaData GP_UNUSED(*data))
593 errno = ENOSYS;
594 return 1;
597 #endif /* HAVE_JPEG */
599 GP_Context *GP_LoadJPG(const char *src_path, GP_ProgressCallback *callback)
601 return GP_LoaderLoadImage(&GP_JPG, src_path, callback);
604 int GP_SaveJPG(const GP_Context *src, const char *dst_path,
605 GP_ProgressCallback *callback)
607 return GP_LoaderSaveImage(&GP_JPG, src, dst_path, callback);
610 struct GP_Loader GP_JPG = {
611 #ifdef HAVE_JPEG
612 .Read = GP_ReadJPG,
613 .Write = GP_WriteJPG,
614 .save_ptypes = out_pixel_types,
615 #endif
616 .Match = GP_MatchJPG,
618 .fmt_name = "JPEG",
619 .extensions = {"jpg", "jpeg", NULL},