allow JPEG/imexif.c to build on C89 compilers
[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>
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 45static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
02d1d628 46
dd55acc8 47/* Bad design right here */
02d1d628 48
dd55acc8
AMH
49static int tlength=0;
50static char **iptc_text=NULL;
02d1d628 51
02d1d628 52
dd55acc8 53/* Source and Destination managers */
02d1d628
AMH
54
55
dd55acc8
AMH
56typedef 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
64typedef 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
71typedef wiol_source_mgr *wiol_src_ptr;
72typedef 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
88static void
dd55acc8
AMH
89wiol_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 101static boolean
dd55acc8
AMH
102wiol_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
6d5c85a2 108 nbytes = i_io_read(src->data, src->buffer, JPGS);
dd55acc8
AMH
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 122static void
dd55acc8
AMH
123wiol_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
144static void
dd55acc8
AMH
145wiol_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
157static void
dd55acc8
AMH
158jpeg_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
195static void
196wiol_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
206static boolean
207wiol_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));
6d5c85a2 222 rc = i_io_write(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
234static void
235wiol_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
6d5c85a2 241 if (i_io_write(dest->data, dest->buffer, nbytes) != nbytes) {
a73aeb5f 242 myfree(dest->buffer);
f873cb01
TC
243 ERREXIT(cinfo, JERR_FILE_WRITE);
244 }
6d5c85a2 245
dd55acc8
AMH
246 if (dest != NULL) myfree(dest->buffer);
247}
02d1d628 248
02d1d628 249
dd55acc8 250/* Destination manager Constructor */
02d1d628 251
dd55acc8
AMH
252static void
253jpeg_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
274LOCAL(unsigned int)
275jpeg_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
289METHODDEF(boolean)
290APP13_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 306METHODDEF(void)
dd55acc8 307my_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
319struct my_error_mgr {
320 struct jpeg_error_mgr pub; /* "public" fields */
321 jmp_buf setjmp_buffer; /* for return to caller */
322};
323
324typedef struct my_error_mgr * my_error_ptr;
325
326/* Here's the routine that will replace the standard error_exit method */
327
328METHODDEF(void)
329my_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
341static void
342transfer_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
358static void
359transfer_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
369static void
370transfer_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
378typedef 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 385i_img*
dd55acc8 386i_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
574undef_int
575i_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
6d5c85a2
TC
724 if (i_io_close(ig))
725 return 0;
10461f9a 726
dd55acc8 727 return(1);
02d1d628 728}
f873cb01
TC
729
730/*
731=back
732
733=head1 AUTHOR
734
735Arnar M. Hrafnkelsson, addi@umich.edu
736
737=head1 SEE ALSO
738
739Imager(3)
740
741=cut
742*/