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_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
);
61 struct jpeg_error_mgr error_mgr
;
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
) {
92 static int load(struct jpeg_decompress_struct
*cinfo
, GP_Context
*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");
110 static int load_cmyk(struct jpeg_decompress_struct
*cinfo
, GP_Context
*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);
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");
141 struct my_source_mgr
{
142 struct jpeg_source_mgr mgr
;
148 static void dummy_src(j_decompress_ptr
GP_UNUSED(cinfo
))
152 static boolean
fill_input_buffer(struct jpeg_decompress_struct
*cinfo
)
155 struct my_source_mgr
* src
= (void*)cinfo
->src
;
157 ret
= GP_IORead(src
->io
, src
->buffer
, src
->size
);
160 GP_WARN("Failed to fill buffer, IORead returned %i", ret
);
164 src
->mgr
.next_input_byte
= src
->buffer
;
165 src
->mgr
.bytes_in_buffer
= ret
;
169 static void skip_input_data(struct jpeg_decompress_struct
*cinfo
, long num_bytes
)
171 struct my_source_mgr
* src
= (void*)cinfo
->src
;
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;
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
;
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
) {
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);
234 case JPEG_APP0
+ 1: {
235 GP_IO
*io
= GP_IOMem(marker
->data
, marker
->data_length
, NULL
);
237 GP_WARN("Failed to create MemIO");
240 GP_ReadExif(io
, storage
);
247 static void save_jpg_markers(struct jpeg_decompress_struct
*cinfo
)
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_Context
**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_Context
*ret
= NULL
;
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
)) {
277 jpeg_create_decompress(&cinfo
);
278 init_source_mgr(&src
, io
, buf
, sizeof(buf
));
279 cinfo
.src
= (void*)&src
;
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?
293 read_jpg_metadata(&cinfo
, storage
);
300 switch (cinfo
.out_color_space
) {
302 pixel_type
= GP_PIXEL_G8
;
305 pixel_type
= GP_PIXEL_BGR888
;
308 pixel_type
= GP_PIXEL_CMYK8888
;
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
));
321 ret
= GP_ContextAlloc(cinfo
.image_width
, cinfo
.image_height
,
325 GP_DEBUG(1, "Malloc failed :(");
330 jpeg_start_decompress(&cinfo
);
332 switch (pixel_type
) {
333 case GP_PIXEL_BGR888
:
335 err
= load(&cinfo
, ret
, callback
);
337 case GP_PIXEL_CMYK8888
:
338 err
= load_cmyk(&cinfo
, ret
, callback
);
347 jpeg_finish_decompress(&cinfo
);
349 jpeg_destroy_decompress(&cinfo
);
351 GP_ProgressCallbackDone(callback
);
360 jpeg_destroy_decompress(&cinfo
);
365 static int save_convert(struct jpeg_compress_struct
*cinfo
,
366 const GP_Context
*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");
393 static int save(struct jpeg_compress_struct
*cinfo
,
394 const GP_Context
*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");
413 struct jpeg_destination_mgr mgr
;
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");
432 dest
->mgr
.next_output_byte
= dest
->buffer
;
433 dest
->mgr
.free_in_buffer
= dest
->size
;
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
;
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
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
;
463 dst
->size
= buf_size
;
466 static GP_PixelType out_pixel_types
[] = {
472 int GP_WriteJPG(const GP_Context
*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
;
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
));
493 if (setjmp(my_err
.setjmp_buf
)) {
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
;
511 case GP_PIXEL_BGR888
:
512 cinfo
.input_components
= 3;
513 cinfo
.in_color_space
= JCS_RGB
;
516 cinfo
.input_components
= 1;
517 cinfo
.in_color_space
= JCS_GRAYSCALE
;
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
);
530 err
= save(&cinfo
, src
, callback
);
533 jpeg_destroy_compress(&cinfo
);
538 jpeg_finish_compress(&cinfo
);
539 jpeg_destroy_compress(&cinfo
);
541 GP_ProgressCallbackDone(callback
);
547 int GP_ReadJPGEx(GP_IO
GP_UNUSED(*io
), GP_Context
GP_UNUSED(**img
),
548 GP_DataStorage
GP_UNUSED(*storage
),
549 GP_ProgressCallback
GP_UNUSED(*callback
))
555 int GP_WriteJPG(const GP_Context
GP_UNUSED(*src
), GP_IO
GP_UNUSED(*io
),
556 GP_ProgressCallback
GP_UNUSED(*callback
))
562 #endif /* HAVE_JPEG */
564 GP_Context
*GP_ReadJPG(GP_IO
*io
, GP_ProgressCallback
*callback
)
566 return GP_LoaderReadImage(&GP_JPG
, io
, callback
);
569 GP_Context
*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_Context
**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_Context
*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
= {
588 .Read
= GP_ReadJPGEx
,
589 .Write
= GP_WriteJPG
,
590 .save_ptypes
= out_pixel_types
,
592 .Match
= GP_MatchJPG
,
595 .extensions
= {"jpg", "jpeg", NULL
},