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