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