1 /*****************************************************************************
2 * This file is part of gfxprim library. *
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. *
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. *
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 *
19 * Copyright (C) 2009-2014 Cyril Hrubis <metan@ucw.cz> *
21 *****************************************************************************/
25 JPG image support using jpeg library.
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
);
60 struct jpeg_error_mgr error_mgr
;
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
) {
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");
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);
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");
140 struct my_source_mgr
{
141 struct jpeg_source_mgr mgr
;
147 static void dummy_src(j_decompress_ptr
GP_UNUSED(cinfo
))
151 static boolean
fill_input_buffer(struct jpeg_decompress_struct
*cinfo
)
154 struct my_source_mgr
* src
= (void*)cinfo
->src
;
156 ret
= GP_IORead(src
->io
, src
->buffer
, src
->size
);
159 GP_WARN("Failed to fill buffer");
163 src
->mgr
.next_input_byte
= src
->buffer
;
164 src
->mgr
.bytes_in_buffer
= ret
;
168 static void skip_input_data(struct jpeg_decompress_struct
*cinfo
, long num_bytes
)
170 struct my_source_mgr
* src
= (void*)cinfo
->src
;
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;
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
;
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
;
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
)) {
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
);
232 switch (cinfo
.out_color_space
) {
234 pixel_type
= GP_PIXEL_G8
;
237 pixel_type
= GP_PIXEL_BGR888
;
240 pixel_type
= GP_PIXEL_CMYK8888
;
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
));
253 ret
= GP_ContextAlloc(cinfo
.image_width
, cinfo
.image_height
,
257 GP_DEBUG(1, "Malloc failed :(");
262 jpeg_start_decompress(&cinfo
);
264 switch (pixel_type
) {
265 case GP_PIXEL_BGR888
:
267 err
= load(&cinfo
, ret
, callback
);
269 case GP_PIXEL_CMYK8888
:
270 err
= load_cmyk(&cinfo
, ret
, callback
);
279 jpeg_finish_decompress(&cinfo
);
280 jpeg_destroy_decompress(&cinfo
);
282 GP_ProgressCallbackDone(callback
);
288 jpeg_destroy_decompress(&cinfo
);
293 #define JPEG_COM_MAX 128
295 static void read_jpg_metadata(struct jpeg_decompress_struct
*cinfo
,
298 jpeg_saved_marker_ptr marker
;
300 for (marker
= cinfo
->marker_list
; marker
!= NULL
; marker
= marker
->next
) {
301 switch (marker
->marker
) {
303 GP_MetaDataCreateString(data
, "comment", (void*)marker
->data
,
304 marker
->data_length
, 1);
310 GP_MetaDataFromExif(data
, marker
->data
, marker
->data_length
);
316 static void save_jpg_markers(struct jpeg_decompress_struct
*cinfo
)
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
;
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
)) {
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
);
364 jpeg_destroy_decompress(&cinfo
);
369 int GP_LoadJPGMetaData(const char *src_path
, GP_MetaData
*data
)
374 io
= GP_IOFile(src_path
, GP_IO_RDONLY
);
378 ret
= GP_ReadJPGMetaData(io
, data
);
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");
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");
435 struct jpeg_destination_mgr mgr
;
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");
454 dest
->mgr
.next_output_byte
= dest
->buffer
;
455 dest
->mgr
.free_in_buffer
= dest
->size
;
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
;
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
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
;
485 dst
->size
= buf_size
;
488 static GP_PixelType out_pixel_types
[] = {
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
;
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
));
515 if (setjmp(my_err
.setjmp_buf
)) {
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
;
533 case GP_PIXEL_BGR888
:
534 cinfo
.input_components
= 3;
535 cinfo
.in_color_space
= JCS_RGB
;
538 cinfo
.input_components
= 1;
539 cinfo
.in_color_space
= JCS_GRAYSCALE
;
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
);
552 err
= save(&cinfo
, src
, callback
);
555 jpeg_destroy_compress(&cinfo
);
560 jpeg_finish_compress(&cinfo
);
561 jpeg_destroy_compress(&cinfo
);
564 GP_ProgressCallbackDone(callback
);
570 GP_Context
*GP_ReadJPG(GP_IO
GP_UNUSED(*io
),
571 GP_ProgressCallback
GP_UNUSED(*callback
))
577 int GP_WriteJPG(const GP_Context
*src
, GP_IO
*io
,
578 GP_ProgressCallback
*callback
)
584 int GP_ReadJPGMetaData(GP_IO
GP_UNUSED(*io
), GP_MetaData
GP_UNUSED(*data
))
590 int GP_LoadJPGMetaData(const char GP_UNUSED(*src_path
),
591 GP_MetaData
GP_UNUSED(*data
))
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
= {
613 .Write
= GP_WriteJPG
,
614 .save_ptypes
= out_pixel_types
,
616 .Match
= GP_MatchJPG
,
619 .extensions
= {"jpg", "jpeg", NULL
},