Commit | Line | Data |
---|---|---|
f873cb01 TC |
1 | /* |
2 | =head1 NAME | |
3 | ||
4 | jpeg.c - implement saving and loading JPEG images | |
5 | ||
6 | =head1 SYNOPSIS | |
7 | ||
8 | io_glue *ig; | |
9 | if (!i_writejpeg_wiol(im, ig, quality)) { | |
10 | .. error .. | |
11 | } | |
12 | im = i_readjpeg_wiol(ig, length, iptc_text, itlength); | |
13 | ||
14 | =head1 DESCRIPTION | |
15 | ||
16 | Reads and writes JPEG images | |
17 | ||
18 | =over | |
19 | ||
20 | =cut | |
21 | */ | |
22 | ||
02d1d628 AMH |
23 | #include <stdio.h> |
24 | #include <sys/stat.h> | |
0a651031 | 25 | #ifndef _MSC_VER |
02d1d628 | 26 | #include <unistd.h> |
0a651031 | 27 | #endif |
02d1d628 AMH |
28 | #include <setjmp.h> |
29 | ||
30 | #include "iolayer.h" | |
92bda632 | 31 | #include "imageri.h" |
02d1d628 | 32 | #include "jpeglib.h" |
f873cb01 TC |
33 | #include "jerror.h" |
34 | #include <errno.h> | |
f7450478 TC |
35 | #ifdef IMEXIF_ENABLE |
36 | #include "imexif.h" | |
37 | #endif | |
02d1d628 | 38 | |
dd55acc8 | 39 | #define JPEG_APP13 0xED /* APP13 marker code */ |
f7450478 | 40 | #define JPEG_APP1 (JPEG_APP0 + 1) |
412e7a35 | 41 | #define JPGS 16384 |
02d1d628 | 42 | |
b33c08f8 | 43 | static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI}; |
02d1d628 | 44 | |
dd55acc8 | 45 | /* Bad design right here */ |
02d1d628 | 46 | |
dd55acc8 AMH |
47 | static int tlength=0; |
48 | static char **iptc_text=NULL; | |
02d1d628 | 49 | |
02d1d628 | 50 | |
dd55acc8 | 51 | /* Source and Destination managers */ |
02d1d628 AMH |
52 | |
53 | ||
dd55acc8 AMH |
54 | typedef struct { |
55 | struct jpeg_source_mgr pub; /* public fields */ | |
56 | io_glue *data; | |
57 | JOCTET *buffer; /* start of buffer */ | |
58 | int length; /* Do I need this? */ | |
59 | boolean start_of_file; /* have we gotten any data yet? */ | |
60 | } wiol_source_mgr; | |
02d1d628 | 61 | |
dd55acc8 AMH |
62 | typedef struct { |
63 | struct jpeg_destination_mgr pub; /* public fields */ | |
64 | io_glue *data; | |
65 | JOCTET *buffer; /* start of buffer */ | |
66 | boolean start_of_file; /* have we gotten any data yet? */ | |
67 | } wiol_destination_mgr; | |
02d1d628 | 68 | |
dd55acc8 AMH |
69 | typedef wiol_source_mgr *wiol_src_ptr; |
70 | typedef wiol_destination_mgr *wiol_dest_ptr; | |
02d1d628 AMH |
71 | |
72 | ||
dd55acc8 AMH |
73 | /* |
74 | * Methods for io manager objects | |
75 | * | |
76 | * Methods for source managers: | |
77 | * | |
78 | * init_source (j_decompress_ptr cinfo); | |
79 | * skip_input_data (j_decompress_ptr cinfo, long num_bytes); | |
80 | * fill_input_buffer (j_decompress_ptr cinfo); | |
81 | * term_source (j_decompress_ptr cinfo); | |
82 | */ | |
83 | ||
02d1d628 AMH |
84 | |
85 | ||
86 | static void | |
dd55acc8 AMH |
87 | wiol_init_source (j_decompress_ptr cinfo) { |
88 | wiol_src_ptr src = (wiol_src_ptr) cinfo->src; | |
02d1d628 | 89 | |
dd55acc8 AMH |
90 | /* We reset the empty-input-file flag for each image, but we don't clear |
91 | * the input buffer. This is correct behavior for reading a series of | |
92 | * images from one source. | |
02d1d628 AMH |
93 | */ |
94 | src->start_of_file = TRUE; | |
95 | } | |
96 | ||
dd55acc8 AMH |
97 | |
98 | ||
02d1d628 | 99 | static boolean |
dd55acc8 AMH |
100 | wiol_fill_input_buffer(j_decompress_ptr cinfo) { |
101 | wiol_src_ptr src = (wiol_src_ptr) cinfo->src; | |
102 | ssize_t nbytes; /* We assume that reads are "small" */ | |
103 | ||
22845485 | 104 | mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo)); |
02d1d628 | 105 | |
dd55acc8 AMH |
106 | nbytes = src->data->readcb(src->data, src->buffer, JPGS); |
107 | ||
108 | if (nbytes <= 0) { /* Insert a fake EOI marker */ | |
109 | src->pub.next_input_byte = fake_eoi; | |
110 | src->pub.bytes_in_buffer = 2; | |
02d1d628 | 111 | } else { |
dd55acc8 AMH |
112 | src->pub.next_input_byte = src->buffer; |
113 | src->pub.bytes_in_buffer = nbytes; | |
02d1d628 | 114 | } |
02d1d628 AMH |
115 | src->start_of_file = FALSE; |
116 | return TRUE; | |
117 | } | |
118 | ||
dd55acc8 | 119 | |
02d1d628 | 120 | static void |
dd55acc8 AMH |
121 | wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) { |
122 | wiol_src_ptr src = (wiol_src_ptr) cinfo->src; | |
02d1d628 AMH |
123 | |
124 | /* Just a dumb implementation for now. Could use fseek() except | |
125 | * it doesn't work on pipes. Not clear that being smart is worth | |
126 | * any trouble anyway --- large skips are infrequent. | |
127 | */ | |
128 | ||
129 | if (num_bytes > 0) { | |
130 | while (num_bytes > (long) src->pub.bytes_in_buffer) { | |
131 | num_bytes -= (long) src->pub.bytes_in_buffer; | |
dd55acc8 | 132 | (void) wiol_fill_input_buffer(cinfo); |
02d1d628 AMH |
133 | /* note we assume that fill_input_buffer will never return FALSE, |
134 | * so suspension need not be handled. | |
135 | */ | |
136 | } | |
137 | src->pub.next_input_byte += (size_t) num_bytes; | |
138 | src->pub.bytes_in_buffer -= (size_t) num_bytes; | |
139 | } | |
140 | } | |
141 | ||
142 | static void | |
dd55acc8 AMH |
143 | wiol_term_source (j_decompress_ptr cinfo) { |
144 | /* no work necessary here */ | |
145 | wiol_src_ptr src; | |
146 | if (cinfo->src != NULL) { | |
147 | src = (wiol_src_ptr) cinfo->src; | |
148 | myfree(src->buffer); | |
149 | } | |
150 | } | |
151 | ||
152 | ||
153 | /* Source manager Constructor */ | |
02d1d628 AMH |
154 | |
155 | static void | |
dd55acc8 AMH |
156 | jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) { |
157 | wiol_src_ptr src; | |
02d1d628 AMH |
158 | |
159 | if (cinfo->src == NULL) { /* first time for this JPEG object? */ | |
dd55acc8 AMH |
160 | cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) |
161 | ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(wiol_source_mgr)); | |
162 | src = (wiol_src_ptr) cinfo->src; | |
02d1d628 | 163 | } |
dd55acc8 AMH |
164 | |
165 | /* put the request method call in here later */ | |
166 | io_glue_commit_types(ig); | |
02d1d628 | 167 | |
dd55acc8 AMH |
168 | src = (wiol_src_ptr) cinfo->src; |
169 | src->data = ig; | |
170 | src->buffer = mymalloc( JPGS ); | |
02d1d628 | 171 | src->length = length; |
dd55acc8 AMH |
172 | |
173 | src->pub.init_source = wiol_init_source; | |
174 | src->pub.fill_input_buffer = wiol_fill_input_buffer; | |
175 | src->pub.skip_input_data = wiol_skip_input_data; | |
02d1d628 | 176 | src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ |
dd55acc8 AMH |
177 | src->pub.term_source = wiol_term_source; |
178 | src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ | |
179 | src->pub.next_input_byte = NULL; /* until buffer loaded */ | |
02d1d628 AMH |
180 | } |
181 | ||
182 | ||
183 | ||
184 | ||
dd55acc8 AMH |
185 | /* |
186 | * Methods for destination managers: | |
187 | * | |
188 | * init_destination (j_compress_ptr cinfo); | |
189 | * empty_output_buffer (j_compress_ptr cinfo); | |
190 | * term_destination (j_compress_ptr cinfo); | |
191 | * | |
192 | */ | |
02d1d628 | 193 | |
dd55acc8 AMH |
194 | static void |
195 | wiol_init_destination (j_compress_ptr cinfo) { | |
196 | wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest; | |
02d1d628 | 197 | |
dd55acc8 AMH |
198 | /* We reset the empty-input-file flag for each image, but we don't clear |
199 | * the input buffer. This is correct behavior for reading a series of | |
200 | * images from one source. | |
02d1d628 | 201 | */ |
dd55acc8 AMH |
202 | dest->start_of_file = TRUE; |
203 | } | |
02d1d628 | 204 | |
dd55acc8 AMH |
205 | static boolean |
206 | wiol_empty_output_buffer(j_compress_ptr cinfo) { | |
207 | wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest; | |
dd55acc8 | 208 | ssize_t rc; |
cf692b64 TC |
209 | /* |
210 | Previously this code was checking free_in_buffer to see how much | |
211 | needed to be written. This does not follow the documentation: | |
212 | ||
213 | "In typical applications, it should write out the | |
214 | *entire* buffer (use the saved start address and buffer length; | |
215 | ignore the current state of next_output_byte and free_in_buffer)." | |
216 | ||
217 | ssize_t nbytes = JPGS - dest->pub.free_in_buffer; | |
218 | */ | |
02d1d628 | 219 | |
66d1414e | 220 | mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo)); |
cf692b64 | 221 | rc = dest->data->writecb(dest->data, dest->buffer, JPGS); |
a73aeb5f | 222 | |
cf692b64 | 223 | if (rc != JPGS) { /* XXX: Should raise some jpeg error */ |
a73aeb5f | 224 | myfree(dest->buffer); |
cf692b64 | 225 | mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc)); |
a73aeb5f | 226 | ERREXIT(cinfo, JERR_FILE_WRITE); |
02d1d628 | 227 | } |
dd55acc8 AMH |
228 | dest->pub.free_in_buffer = JPGS; |
229 | dest->pub.next_output_byte = dest->buffer; | |
230 | return TRUE; | |
231 | } | |
02d1d628 | 232 | |
dd55acc8 AMH |
233 | static void |
234 | wiol_term_destination (j_compress_ptr cinfo) { | |
235 | wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest; | |
f873cb01 | 236 | size_t nbytes = JPGS - dest->pub.free_in_buffer; |
cf692b64 TC |
237 | /* yes, this needs to flush the buffer */ |
238 | /* needs error handling */ | |
a73aeb5f | 239 | |
f873cb01 | 240 | if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) { |
a73aeb5f | 241 | myfree(dest->buffer); |
f873cb01 TC |
242 | ERREXIT(cinfo, JERR_FILE_WRITE); |
243 | } | |
a73aeb5f | 244 | |
dd55acc8 AMH |
245 | if (dest != NULL) myfree(dest->buffer); |
246 | } | |
02d1d628 | 247 | |
02d1d628 | 248 | |
dd55acc8 | 249 | /* Destination manager Constructor */ |
02d1d628 | 250 | |
dd55acc8 AMH |
251 | static void |
252 | jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) { | |
253 | wiol_dest_ptr dest; | |
02d1d628 | 254 | |
dd55acc8 AMH |
255 | if (cinfo->dest == NULL) { /* first time for this JPEG object? */ |
256 | cinfo->dest = | |
257 | (struct jpeg_destination_mgr *) | |
258 | (*cinfo->mem->alloc_small) | |
259 | ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(wiol_destination_mgr)); | |
02d1d628 | 260 | } |
dd55acc8 AMH |
261 | |
262 | dest = (wiol_dest_ptr) cinfo->dest; | |
263 | dest->data = ig; | |
264 | dest->buffer = mymalloc( JPGS ); | |
265 | ||
266 | dest->pub.init_destination = wiol_init_destination; | |
267 | dest->pub.empty_output_buffer = wiol_empty_output_buffer; | |
268 | dest->pub.term_destination = wiol_term_destination; | |
269 | dest->pub.free_in_buffer = JPGS; | |
270 | dest->pub.next_output_byte = dest->buffer; | |
02d1d628 AMH |
271 | } |
272 | ||
02d1d628 AMH |
273 | LOCAL(unsigned int) |
274 | jpeg_getc (j_decompress_ptr cinfo) | |
275 | /* Read next byte */ | |
276 | { | |
277 | struct jpeg_source_mgr * datasrc = cinfo->src; | |
278 | ||
279 | if (datasrc->bytes_in_buffer == 0) { | |
280 | if (! (*datasrc->fill_input_buffer) (cinfo)) | |
281 | { fprintf(stderr,"Jpeglib: cant suspend.\n"); exit(3); } | |
282 | /* ERREXIT(cinfo, JERR_CANT_SUSPEND);*/ | |
283 | } | |
284 | datasrc->bytes_in_buffer--; | |
285 | return GETJOCTET(*datasrc->next_input_byte++); | |
286 | } | |
287 | ||
288 | METHODDEF(boolean) | |
289 | APP13_handler (j_decompress_ptr cinfo) { | |
290 | INT32 length; | |
291 | unsigned int cnt=0; | |
292 | ||
293 | length = jpeg_getc(cinfo) << 8; | |
294 | length += jpeg_getc(cinfo); | |
295 | length -= 2; /* discount the length word itself */ | |
296 | ||
297 | tlength=length; | |
298 | ||
299 | if ( ((*iptc_text)=mymalloc(length)) == NULL ) return FALSE; | |
300 | while (--length >= 0) (*iptc_text)[cnt++] = jpeg_getc(cinfo); | |
301 | ||
302 | return TRUE; | |
303 | } | |
304 | ||
02d1d628 | 305 | METHODDEF(void) |
dd55acc8 | 306 | my_output_message (j_common_ptr cinfo) { |
02d1d628 AMH |
307 | char buffer[JMSG_LENGTH_MAX]; |
308 | ||
309 | /* Create the message */ | |
310 | (*cinfo->err->format_message) (cinfo, buffer); | |
311 | ||
f873cb01 TC |
312 | i_push_error(0, buffer); |
313 | ||
02d1d628 AMH |
314 | /* Send it to stderr, adding a newline */ |
315 | mm_log((1, "%s\n", buffer)); | |
316 | } | |
317 | ||
02d1d628 AMH |
318 | struct my_error_mgr { |
319 | struct jpeg_error_mgr pub; /* "public" fields */ | |
320 | jmp_buf setjmp_buffer; /* for return to caller */ | |
321 | }; | |
322 | ||
323 | typedef struct my_error_mgr * my_error_ptr; | |
324 | ||
325 | /* Here's the routine that will replace the standard error_exit method */ | |
326 | ||
327 | METHODDEF(void) | |
328 | my_error_exit (j_common_ptr cinfo) { | |
329 | /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ | |
330 | my_error_ptr myerr = (my_error_ptr) cinfo->err; | |
331 | ||
332 | /* Always display the message. */ | |
333 | /* We could postpone this until after returning, if we chose. */ | |
334 | (*cinfo->err->output_message) (cinfo); | |
f873cb01 | 335 | |
02d1d628 AMH |
336 | /* Return control to the setjmp point */ |
337 | longjmp(myerr->setjmp_buffer, 1); | |
338 | } | |
339 | ||
02ea5e47 TC |
340 | static void |
341 | transfer_cmyk_inverted(i_color *out, JSAMPARRAY in, int width) { | |
342 | JSAMPROW inrow = *in; | |
343 | while (width--) { | |
344 | /* extract and convert to real CMYK */ | |
345 | /* horribly enough this is correct given cmyk values are inverted */ | |
346 | int c = *inrow++; | |
347 | int m = *inrow++; | |
348 | int y = *inrow++; | |
349 | int k = *inrow++; | |
350 | out->rgba.r = (c * k) / MAXJSAMPLE; | |
351 | out->rgba.g = (m * k) / MAXJSAMPLE; | |
352 | out->rgba.b = (y * k) / MAXJSAMPLE; | |
353 | ++out; | |
354 | } | |
355 | } | |
356 | ||
357 | static void | |
358 | transfer_rgb(i_color *out, JSAMPARRAY in, int width) { | |
359 | JSAMPROW inrow = *in; | |
360 | while (width--) { | |
361 | out->rgba.r = *inrow++; | |
362 | out->rgba.g = *inrow++; | |
363 | out->rgba.b = *inrow++; | |
364 | ++out; | |
365 | } | |
366 | } | |
367 | ||
368 | static void | |
369 | transfer_gray(i_color *out, JSAMPARRAY in, int width) { | |
370 | JSAMPROW inrow = *in; | |
371 | while (width--) { | |
372 | out->gray.gray_color = *inrow++; | |
373 | ++out; | |
374 | } | |
375 | } | |
376 | ||
377 | typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width); | |
378 | ||
f873cb01 TC |
379 | /* |
380 | =item i_readjpeg_wiol(data, length, iptc_itext, itlength) | |
02d1d628 | 381 | |
f873cb01 TC |
382 | =cut |
383 | */ | |
02d1d628 | 384 | i_img* |
dd55acc8 | 385 | i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) { |
02576e8d | 386 | i_img * volatile im = NULL; |
f7450478 TC |
387 | #ifdef IMEXIF_ENABLE |
388 | int seen_exif = 0; | |
389 | #endif | |
02ea5e47 | 390 | i_color *line_buffer = NULL; |
02d1d628 | 391 | struct jpeg_decompress_struct cinfo; |
02d1d628 | 392 | struct my_error_mgr jerr; |
02d1d628 AMH |
393 | JSAMPARRAY buffer; /* Output row buffer */ |
394 | int row_stride; /* physical row width in output buffer */ | |
f7450478 | 395 | jpeg_saved_marker_ptr markerp; |
02ea5e47 TC |
396 | transfer_function_t transfer_f; |
397 | int channels; | |
02576e8d | 398 | volatile int src_set = 0; |
02d1d628 | 399 | |
22845485 | 400 | mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext)); |
02d1d628 | 401 | |
f873cb01 TC |
402 | i_clear_error(); |
403 | ||
dd55acc8 | 404 | iptc_text = iptc_itext; |
02d1d628 | 405 | cinfo.err = jpeg_std_error(&jerr.pub); |
dd55acc8 | 406 | jerr.pub.error_exit = my_error_exit; |
02d1d628 | 407 | jerr.pub.output_message = my_output_message; |
02d1d628 | 408 | |
dd55acc8 | 409 | /* Set error handler */ |
02d1d628 | 410 | if (setjmp(jerr.setjmp_buffer)) { |
02576e8d TC |
411 | if (src_set) |
412 | wiol_term_source(&cinfo); | |
02d1d628 AMH |
413 | jpeg_destroy_decompress(&cinfo); |
414 | *iptc_itext=NULL; | |
415 | *itlength=0; | |
02ea5e47 TC |
416 | if (line_buffer) |
417 | myfree(line_buffer); | |
02576e8d TC |
418 | if (im) |
419 | i_img_destroy(im); | |
02d1d628 AMH |
420 | return NULL; |
421 | } | |
422 | ||
423 | jpeg_create_decompress(&cinfo); | |
424 | jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler); | |
f7450478 TC |
425 | jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF); |
426 | jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); | |
dd55acc8 | 427 | jpeg_wiol_src(&cinfo, data, length); |
02576e8d | 428 | src_set = 1; |
dd55acc8 | 429 | |
02d1d628 AMH |
430 | (void) jpeg_read_header(&cinfo, TRUE); |
431 | (void) jpeg_start_decompress(&cinfo); | |
02ea5e47 TC |
432 | |
433 | channels = cinfo.output_components; | |
434 | switch (cinfo.out_color_space) { | |
435 | case JCS_GRAYSCALE: | |
a840253f TC |
436 | if (cinfo.output_components != 1) { |
437 | mm_log((1, "i_readjpeg: grayscale image with %d channels\n", cinfo.output_components)); | |
438 | i_push_errorf(0, "grayscale image with invalid components %d", cinfo.output_components); | |
439 | wiol_term_source(&cinfo); | |
440 | jpeg_destroy_decompress(&cinfo); | |
441 | return NULL; | |
442 | } | |
02ea5e47 TC |
443 | transfer_f = transfer_gray; |
444 | break; | |
445 | ||
446 | case JCS_RGB: | |
447 | transfer_f = transfer_rgb; | |
a840253f TC |
448 | if (cinfo.output_components != 3) { |
449 | mm_log((1, "i_readjpeg: RGB image with %d channels\n", cinfo.output_components)); | |
450 | i_push_errorf(0, "RGB image with invalid components %d", cinfo.output_components); | |
451 | wiol_term_source(&cinfo); | |
452 | jpeg_destroy_decompress(&cinfo); | |
453 | return NULL; | |
454 | } | |
02ea5e47 TC |
455 | break; |
456 | ||
457 | case JCS_CMYK: | |
458 | if (cinfo.output_components == 4) { | |
459 | /* we treat the CMYK values as inverted, because that's what that | |
460 | buggy photoshop does, and everyone has to follow the gorilla. | |
461 | ||
462 | Is there any app that still produces correct CMYK JPEGs? | |
463 | */ | |
464 | transfer_f = transfer_cmyk_inverted; | |
465 | channels = 3; | |
466 | } | |
467 | else { | |
468 | mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components)); | |
469 | i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components); | |
470 | wiol_term_source(&cinfo); | |
471 | jpeg_destroy_decompress(&cinfo); | |
472 | return NULL; | |
473 | } | |
474 | break; | |
475 | ||
476 | default: | |
477 | mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space)); | |
478 | i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space); | |
479 | wiol_term_source(&cinfo); | |
480 | jpeg_destroy_decompress(&cinfo); | |
481 | return NULL; | |
482 | } | |
483 | ||
77157728 | 484 | if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height, |
02ea5e47 | 485 | channels, sizeof(i_sample_t))) { |
77157728 | 486 | mm_log((1, "i_readjpeg: image size exceeds limits\n")); |
22845485 | 487 | wiol_term_source(&cinfo); |
77157728 TC |
488 | jpeg_destroy_decompress(&cinfo); |
489 | return NULL; | |
490 | } | |
02ea5e47 TC |
491 | |
492 | im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels); | |
2c2c832a | 493 | if (!im) { |
22845485 | 494 | wiol_term_source(&cinfo); |
2c2c832a TC |
495 | jpeg_destroy_decompress(&cinfo); |
496 | return NULL; | |
497 | } | |
02d1d628 AMH |
498 | row_stride = cinfo.output_width * cinfo.output_components; |
499 | buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1); | |
02ea5e47 | 500 | line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width); |
02d1d628 AMH |
501 | while (cinfo.output_scanline < cinfo.output_height) { |
502 | (void) jpeg_read_scanlines(&cinfo, buffer, 1); | |
02ea5e47 TC |
503 | transfer_f(line_buffer, buffer, cinfo.output_width); |
504 | i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer); | |
02d1d628 | 505 | } |
02ea5e47 | 506 | myfree(line_buffer); |
f7450478 TC |
507 | |
508 | /* check for APP1 marker and save */ | |
509 | markerp = cinfo.marker_list; | |
510 | while (markerp != NULL) { | |
511 | if (markerp->marker == JPEG_COM) { | |
c0f79ae6 | 512 | i_tags_add(&im->tags, "jpeg_comment", 0, (const char *)markerp->data, |
f7450478 TC |
513 | markerp->data_length, 0); |
514 | } | |
515 | #ifdef IMEXIF_ENABLE | |
516 | else if (markerp->marker == JPEG_APP1 && !seen_exif) { | |
517 | seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length); | |
518 | } | |
519 | #endif | |
520 | ||
521 | markerp = markerp->next; | |
522 | } | |
523 | ||
02ea5e47 TC |
524 | i_tags_addn(&im->tags, "jpeg_out_color_space", 0, cinfo.out_color_space); |
525 | i_tags_addn(&im->tags, "jpeg_color_space", 0, cinfo.jpeg_color_space); | |
526 | ||
6d54291b TC |
527 | if (cinfo.saw_JFIF_marker) { |
528 | double xres = cinfo.X_density; | |
529 | double yres = cinfo.Y_density; | |
530 | ||
531 | i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit); | |
532 | switch (cinfo.density_unit) { | |
533 | case 0: /* values are just the aspect ratio */ | |
534 | i_tags_addn(&im->tags, "i_aspect_only", 0, 1); | |
535 | i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0); | |
536 | break; | |
537 | ||
538 | case 1: /* per inch */ | |
539 | i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0); | |
540 | break; | |
541 | ||
542 | case 2: /* per cm */ | |
543 | i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0); | |
544 | xres *= 2.54; | |
545 | yres *= 2.54; | |
546 | break; | |
547 | } | |
548 | i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6); | |
549 | i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6); | |
550 | } | |
551 | ||
02d1d628 AMH |
552 | (void) jpeg_finish_decompress(&cinfo); |
553 | jpeg_destroy_decompress(&cinfo); | |
554 | *itlength=tlength; | |
2c2c832a TC |
555 | |
556 | i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0); | |
557 | ||
dd55acc8 | 558 | mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im)); |
02d1d628 AMH |
559 | return im; |
560 | } | |
561 | ||
f873cb01 TC |
562 | /* |
563 | =item i_writejpeg_wiol(im, ig, qfactor) | |
02d1d628 | 564 | |
f873cb01 TC |
565 | =cut |
566 | */ | |
02d1d628 | 567 | |
dd55acc8 AMH |
568 | undef_int |
569 | i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) { | |
dd55acc8 AMH |
570 | JSAMPLE *image_buffer; |
571 | int quality; | |
6d54291b TC |
572 | int got_xres, got_yres, aspect_only, resunit; |
573 | double xres, yres; | |
574 | int comment_entry; | |
6e4af7d4 | 575 | int want_channels = im->channels; |
02d1d628 | 576 | |
dd55acc8 | 577 | struct jpeg_compress_struct cinfo; |
f873cb01 | 578 | struct my_error_mgr jerr; |
02d1d628 | 579 | |
dd55acc8 AMH |
580 | JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */ |
581 | int row_stride; /* physical row width in image buffer */ | |
f873cb01 | 582 | unsigned char * data = NULL; |
6e4af7d4 | 583 | i_color *line_buf = NULL; |
02d1d628 | 584 | |
dd55acc8 | 585 | mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor)); |
02d1d628 | 586 | |
f873cb01 | 587 | i_clear_error(); |
0389bf49 | 588 | io_glue_commit_types(ig); |
02d1d628 | 589 | |
f873cb01 | 590 | if (!(im->channels==1 || im->channels==3)) { |
6e4af7d4 | 591 | want_channels = im->channels - 1; |
f873cb01 TC |
592 | } |
593 | quality = qfactor; | |
02d1d628 | 594 | |
f873cb01 TC |
595 | cinfo.err = jpeg_std_error(&jerr.pub); |
596 | jerr.pub.error_exit = my_error_exit; | |
597 | jerr.pub.output_message = my_output_message; | |
598 | ||
dd55acc8 | 599 | jpeg_create_compress(&cinfo); |
02d1d628 | 600 | |
f873cb01 TC |
601 | if (setjmp(jerr.setjmp_buffer)) { |
602 | jpeg_destroy_compress(&cinfo); | |
603 | if (data) | |
604 | myfree(data); | |
6e4af7d4 TC |
605 | if (line_buf) |
606 | myfree(line_buf); | |
f873cb01 TC |
607 | return 0; |
608 | } | |
609 | ||
dd55acc8 | 610 | jpeg_wiol_dest(&cinfo, ig); |
02d1d628 | 611 | |
dd55acc8 AMH |
612 | cinfo.image_width = im -> xsize; /* image width and height, in pixels */ |
613 | cinfo.image_height = im -> ysize; | |
02d1d628 | 614 | |
6e4af7d4 | 615 | if (want_channels==3) { |
dd55acc8 AMH |
616 | cinfo.input_components = 3; /* # of color components per pixel */ |
617 | cinfo.in_color_space = JCS_RGB; /* colorspace of input image */ | |
618 | } | |
02d1d628 | 619 | |
6e4af7d4 | 620 | if (want_channels==1) { |
dd55acc8 AMH |
621 | cinfo.input_components = 1; /* # of color components per pixel */ |
622 | cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */ | |
623 | } | |
02d1d628 | 624 | |
dd55acc8 AMH |
625 | jpeg_set_defaults(&cinfo); |
626 | jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */ | |
02d1d628 | 627 | |
6d54291b TC |
628 | got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres); |
629 | got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres); | |
630 | if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only)) | |
631 | aspect_only = 0; | |
632 | if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit)) | |
633 | resunit = 1; /* per inch */ | |
634 | if (resunit < 0 || resunit > 2) /* default to inch if invalid */ | |
635 | resunit = 1; | |
636 | if (got_xres || got_yres) { | |
637 | if (!got_xres) | |
638 | xres = yres; | |
639 | else if (!got_yres) | |
640 | yres = xres; | |
641 | if (aspect_only) | |
642 | resunit = 0; /* standard tags override format tags */ | |
643 | if (resunit == 2) { | |
644 | /* convert to per cm */ | |
645 | xres /= 2.54; | |
646 | yres /= 2.54; | |
647 | } | |
648 | cinfo.density_unit = resunit; | |
649 | cinfo.X_density = (int)(xres + 0.5); | |
650 | cinfo.Y_density = (int)(yres + 0.5); | |
651 | } | |
652 | ||
dd55acc8 | 653 | jpeg_start_compress(&cinfo, TRUE); |
02d1d628 | 654 | |
6d54291b | 655 | if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) { |
c0f79ae6 TC |
656 | jpeg_write_marker(&cinfo, JPEG_COM, |
657 | (const JOCTET *)im->tags.tags[comment_entry].data, | |
6d54291b TC |
658 | im->tags.tags[comment_entry].size); |
659 | } | |
660 | ||
dd55acc8 | 661 | row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */ |
02d1d628 | 662 | |
6e4af7d4 TC |
663 | if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits |
664 | && im->channels == want_channels) { | |
faa9b3e7 TC |
665 | image_buffer=im->idata; |
666 | ||
667 | while (cinfo.next_scanline < cinfo.image_height) { | |
668 | /* jpeg_write_scanlines expects an array of pointers to scanlines. | |
669 | * Here the array is only one element long, but you could pass | |
670 | * more than one scanline at a time if that's more convenient. | |
671 | */ | |
672 | row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride]; | |
673 | (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); | |
674 | } | |
675 | } | |
6e4af7d4 | 676 | else if (im->channels == want_channels) { |
f873cb01 | 677 | data = mymalloc(im->xsize * im->channels); |
faa9b3e7 TC |
678 | if (data) { |
679 | while (cinfo.next_scanline < cinfo.image_height) { | |
680 | /* jpeg_write_scanlines expects an array of pointers to scanlines. | |
681 | * Here the array is only one element long, but you could pass | |
682 | * more than one scanline at a time if that's more convenient. | |
683 | */ | |
684 | i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data, | |
685 | NULL, im->channels); | |
686 | row_pointer[0] = data; | |
687 | (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); | |
688 | } | |
02ea5e47 | 689 | myfree(data); |
faa9b3e7 TC |
690 | } |
691 | else { | |
faa9b3e7 | 692 | jpeg_destroy_compress(&cinfo); |
f873cb01 | 693 | i_push_error(0, "out of memory"); |
faa9b3e7 TC |
694 | return 0; /* out of memory? */ |
695 | } | |
dd55acc8 | 696 | } |
6e4af7d4 TC |
697 | else { |
698 | i_color bg; | |
699 | int x; | |
700 | int ch; | |
701 | i_color const *linep; | |
702 | unsigned char * datap; | |
703 | ||
704 | line_buf = mymalloc(sizeof(i_color) * im->xsize); | |
705 | ||
706 | i_get_file_background(im, &bg); | |
707 | ||
708 | data = mymalloc(im->xsize * want_channels); | |
709 | while (cinfo.next_scanline < cinfo.image_height) { | |
710 | i_glin(im, 0, im->xsize, cinfo.next_scanline, line_buf); | |
711 | i_adapt_colors_bg(want_channels, im->channels, line_buf, im->xsize, &bg); | |
712 | datap = data; | |
713 | linep = line_buf; | |
714 | for (x = 0; x < im->xsize; ++x) { | |
715 | for (ch = 0; ch < want_channels; ++ch) { | |
716 | *datap++ = linep->channel[ch]; | |
717 | } | |
718 | ++linep; | |
719 | } | |
720 | row_pointer[0] = data; | |
721 | (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); | |
722 | } | |
723 | myfree(line_buf); | |
724 | myfree(data); | |
725 | } | |
02d1d628 | 726 | |
dd55acc8 | 727 | /* Step 6: Finish compression */ |
02d1d628 | 728 | |
dd55acc8 | 729 | jpeg_finish_compress(&cinfo); |
02d1d628 | 730 | |
dd55acc8 | 731 | jpeg_destroy_compress(&cinfo); |
02d1d628 | 732 | |
10461f9a TC |
733 | ig->closecb(ig); |
734 | ||
dd55acc8 | 735 | return(1); |
02d1d628 | 736 | } |
f873cb01 TC |
737 | |
738 | /* | |
739 | =back | |
740 | ||
741 | =head1 AUTHOR | |
742 | ||
743 | Arnar M. Hrafnkelsson, addi@umich.edu | |
744 | ||
745 | =head1 SEE ALSO | |
746 | ||
747 | Imager(3) | |
748 | ||
749 | =cut | |
750 | */ |