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