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