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