hide or rename any symbols that are likely to conflict with other
[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"
31#include "image.h"
32#include "jpeglib.h"
f873cb01
TC
33#include "jerror.h"
34#include <errno.h>
02d1d628 35
dd55acc8 36#define JPEG_APP13 0xED /* APP13 marker code */
412e7a35 37#define JPGS 16384
02d1d628 38
b33c08f8 39static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
02d1d628 40
dd55acc8 41/* Bad design right here */
02d1d628 42
dd55acc8
AMH
43static int tlength=0;
44static char **iptc_text=NULL;
02d1d628 45
02d1d628 46
dd55acc8 47/* Source and Destination managers */
02d1d628
AMH
48
49
dd55acc8
AMH
50typedef struct {
51 struct jpeg_source_mgr pub; /* public fields */
52 io_glue *data;
53 JOCTET *buffer; /* start of buffer */
54 int length; /* Do I need this? */
55 boolean start_of_file; /* have we gotten any data yet? */
56} wiol_source_mgr;
02d1d628 57
dd55acc8
AMH
58typedef struct {
59 struct jpeg_destination_mgr pub; /* public fields */
60 io_glue *data;
61 JOCTET *buffer; /* start of buffer */
62 boolean start_of_file; /* have we gotten any data yet? */
63} wiol_destination_mgr;
02d1d628 64
dd55acc8
AMH
65typedef wiol_source_mgr *wiol_src_ptr;
66typedef wiol_destination_mgr *wiol_dest_ptr;
02d1d628
AMH
67
68
dd55acc8
AMH
69/*
70 * Methods for io manager objects
71 *
72 * Methods for source managers:
73 *
74 * init_source (j_decompress_ptr cinfo);
75 * skip_input_data (j_decompress_ptr cinfo, long num_bytes);
76 * fill_input_buffer (j_decompress_ptr cinfo);
77 * term_source (j_decompress_ptr cinfo);
78 */
79
02d1d628
AMH
80
81
82static void
dd55acc8
AMH
83wiol_init_source (j_decompress_ptr cinfo) {
84 wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
02d1d628 85
dd55acc8
AMH
86 /* We reset the empty-input-file flag for each image, but we don't clear
87 * the input buffer. This is correct behavior for reading a series of
88 * images from one source.
02d1d628
AMH
89 */
90 src->start_of_file = TRUE;
91}
92
dd55acc8
AMH
93
94
02d1d628 95static boolean
dd55acc8
AMH
96wiol_fill_input_buffer(j_decompress_ptr cinfo) {
97 wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
98 ssize_t nbytes; /* We assume that reads are "small" */
99
100 mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n"));
02d1d628 101
dd55acc8
AMH
102 nbytes = src->data->readcb(src->data, src->buffer, JPGS);
103
104 if (nbytes <= 0) { /* Insert a fake EOI marker */
105 src->pub.next_input_byte = fake_eoi;
106 src->pub.bytes_in_buffer = 2;
02d1d628 107 } else {
dd55acc8
AMH
108 src->pub.next_input_byte = src->buffer;
109 src->pub.bytes_in_buffer = nbytes;
02d1d628 110 }
02d1d628
AMH
111 src->start_of_file = FALSE;
112 return TRUE;
113}
114
dd55acc8 115
02d1d628 116static void
dd55acc8
AMH
117wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
118 wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
02d1d628
AMH
119
120 /* Just a dumb implementation for now. Could use fseek() except
121 * it doesn't work on pipes. Not clear that being smart is worth
122 * any trouble anyway --- large skips are infrequent.
123 */
124
125 if (num_bytes > 0) {
126 while (num_bytes > (long) src->pub.bytes_in_buffer) {
127 num_bytes -= (long) src->pub.bytes_in_buffer;
dd55acc8 128 (void) wiol_fill_input_buffer(cinfo);
02d1d628
AMH
129 /* note we assume that fill_input_buffer will never return FALSE,
130 * so suspension need not be handled.
131 */
132 }
133 src->pub.next_input_byte += (size_t) num_bytes;
134 src->pub.bytes_in_buffer -= (size_t) num_bytes;
135 }
136}
137
138static void
dd55acc8
AMH
139wiol_term_source (j_decompress_ptr cinfo) {
140 /* no work necessary here */
141 wiol_src_ptr src;
142 if (cinfo->src != NULL) {
143 src = (wiol_src_ptr) cinfo->src;
144 myfree(src->buffer);
145 }
146}
147
148
149/* Source manager Constructor */
02d1d628
AMH
150
151static void
dd55acc8
AMH
152jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
153 wiol_src_ptr src;
02d1d628
AMH
154
155 if (cinfo->src == NULL) { /* first time for this JPEG object? */
dd55acc8
AMH
156 cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
157 ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(wiol_source_mgr));
158 src = (wiol_src_ptr) cinfo->src;
02d1d628 159 }
dd55acc8
AMH
160
161 /* put the request method call in here later */
162 io_glue_commit_types(ig);
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
f873cb01 216 mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n"));
cf692b64 217 rc = dest->data->writecb(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
f873cb01 236 if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
a73aeb5f 237 myfree(dest->buffer);
f873cb01
TC
238 ERREXIT(cinfo, JERR_FILE_WRITE);
239 }
a73aeb5f 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
AMH
269LOCAL(unsigned int)
270jpeg_getc (j_decompress_ptr cinfo)
271/* Read next byte */
272{
273 struct jpeg_source_mgr * datasrc = cinfo->src;
274
275 if (datasrc->bytes_in_buffer == 0) {
276 if (! (*datasrc->fill_input_buffer) (cinfo))
277 { fprintf(stderr,"Jpeglib: cant suspend.\n"); exit(3); }
278 /* ERREXIT(cinfo, JERR_CANT_SUSPEND);*/
279 }
280 datasrc->bytes_in_buffer--;
281 return GETJOCTET(*datasrc->next_input_byte++);
282}
283
284METHODDEF(boolean)
285APP13_handler (j_decompress_ptr cinfo) {
286 INT32 length;
287 unsigned int cnt=0;
288
289 length = jpeg_getc(cinfo) << 8;
290 length += jpeg_getc(cinfo);
291 length -= 2; /* discount the length word itself */
292
293 tlength=length;
294
295 if ( ((*iptc_text)=mymalloc(length)) == NULL ) return FALSE;
296 while (--length >= 0) (*iptc_text)[cnt++] = jpeg_getc(cinfo);
297
298 return TRUE;
299}
300
02d1d628 301METHODDEF(void)
dd55acc8 302my_output_message (j_common_ptr cinfo) {
02d1d628
AMH
303 char buffer[JMSG_LENGTH_MAX];
304
305 /* Create the message */
306 (*cinfo->err->format_message) (cinfo, buffer);
307
f873cb01
TC
308 i_push_error(0, buffer);
309
02d1d628
AMH
310 /* Send it to stderr, adding a newline */
311 mm_log((1, "%s\n", buffer));
312}
313
02d1d628
AMH
314struct my_error_mgr {
315 struct jpeg_error_mgr pub; /* "public" fields */
316 jmp_buf setjmp_buffer; /* for return to caller */
317};
318
319typedef struct my_error_mgr * my_error_ptr;
320
321/* Here's the routine that will replace the standard error_exit method */
322
323METHODDEF(void)
324my_error_exit (j_common_ptr cinfo) {
325 /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
326 my_error_ptr myerr = (my_error_ptr) cinfo->err;
327
328 /* Always display the message. */
329 /* We could postpone this until after returning, if we chose. */
330 (*cinfo->err->output_message) (cinfo);
f873cb01 331
02d1d628
AMH
332 /* Return control to the setjmp point */
333 longjmp(myerr->setjmp_buffer, 1);
334}
335
f873cb01
TC
336/*
337=item i_readjpeg_wiol(data, length, iptc_itext, itlength)
02d1d628 338
f873cb01
TC
339=cut
340*/
02d1d628 341i_img*
dd55acc8 342i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
02d1d628
AMH
343 i_img *im;
344
345 struct jpeg_decompress_struct cinfo;
02d1d628 346 struct my_error_mgr jerr;
02d1d628
AMH
347 JSAMPARRAY buffer; /* Output row buffer */
348 int row_stride; /* physical row width in output buffer */
349
dd55acc8 350 mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, iptc_itext));
02d1d628 351
f873cb01
TC
352 i_clear_error();
353
dd55acc8 354 iptc_text = iptc_itext;
02d1d628 355 cinfo.err = jpeg_std_error(&jerr.pub);
dd55acc8 356 jerr.pub.error_exit = my_error_exit;
02d1d628 357 jerr.pub.output_message = my_output_message;
02d1d628 358
dd55acc8 359 /* Set error handler */
02d1d628
AMH
360 if (setjmp(jerr.setjmp_buffer)) {
361 jpeg_destroy_decompress(&cinfo);
362 *iptc_itext=NULL;
363 *itlength=0;
364 return NULL;
365 }
366
367 jpeg_create_decompress(&cinfo);
368 jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
dd55acc8
AMH
369 jpeg_wiol_src(&cinfo, data, length);
370
02d1d628
AMH
371 (void) jpeg_read_header(&cinfo, TRUE);
372 (void) jpeg_start_decompress(&cinfo);
373 im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
374 row_stride = cinfo.output_width * cinfo.output_components;
375 buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
376 while (cinfo.output_scanline < cinfo.output_height) {
377 (void) jpeg_read_scanlines(&cinfo, buffer, 1);
faa9b3e7 378 memcpy(im->idata+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
02d1d628
AMH
379 }
380 (void) jpeg_finish_decompress(&cinfo);
381 jpeg_destroy_decompress(&cinfo);
382 *itlength=tlength;
dd55acc8 383 mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
02d1d628
AMH
384 return im;
385}
386
f873cb01
TC
387/*
388=item i_writejpeg_wiol(im, ig, qfactor)
02d1d628 389
f873cb01
TC
390=cut
391*/
02d1d628 392
dd55acc8
AMH
393undef_int
394i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
395 struct stat stbuf;
396 JSAMPLE *image_buffer;
397 int quality;
02d1d628 398
dd55acc8 399 struct jpeg_compress_struct cinfo;
f873cb01 400 struct my_error_mgr jerr;
02d1d628 401
dd55acc8
AMH
402 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
403 int row_stride; /* physical row width in image buffer */
f873cb01 404 unsigned char * data = NULL;
02d1d628 405
dd55acc8 406 mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
02d1d628 407
f873cb01 408 i_clear_error();
02d1d628 409
f873cb01
TC
410 if (!(im->channels==1 || im->channels==3)) {
411 i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
412 return 0;
413 }
414 quality = qfactor;
02d1d628 415
f873cb01
TC
416 cinfo.err = jpeg_std_error(&jerr.pub);
417 jerr.pub.error_exit = my_error_exit;
418 jerr.pub.output_message = my_output_message;
419
dd55acc8 420 jpeg_create_compress(&cinfo);
02d1d628 421
f873cb01
TC
422 if (setjmp(jerr.setjmp_buffer)) {
423 jpeg_destroy_compress(&cinfo);
424 if (data)
425 myfree(data);
426 return 0;
427 }
428
02d1d628 429 io_glue_commit_types(ig);
dd55acc8 430 jpeg_wiol_dest(&cinfo, ig);
02d1d628 431
dd55acc8
AMH
432 cinfo.image_width = im -> xsize; /* image width and height, in pixels */
433 cinfo.image_height = im -> ysize;
02d1d628 434
dd55acc8
AMH
435 if (im->channels==3) {
436 cinfo.input_components = 3; /* # of color components per pixel */
437 cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
438 }
02d1d628 439
dd55acc8
AMH
440 if (im->channels==1) {
441 cinfo.input_components = 1; /* # of color components per pixel */
442 cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
443 }
02d1d628 444
dd55acc8
AMH
445 jpeg_set_defaults(&cinfo);
446 jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
02d1d628 447
dd55acc8 448 jpeg_start_compress(&cinfo, TRUE);
02d1d628 449
dd55acc8 450 row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
02d1d628 451
faa9b3e7
TC
452 if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
453 image_buffer=im->idata;
454
455 while (cinfo.next_scanline < cinfo.image_height) {
456 /* jpeg_write_scanlines expects an array of pointers to scanlines.
457 * Here the array is only one element long, but you could pass
458 * more than one scanline at a time if that's more convenient.
459 */
460 row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
461 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
462 }
463 }
464 else {
f873cb01 465 data = mymalloc(im->xsize * im->channels);
faa9b3e7
TC
466 if (data) {
467 while (cinfo.next_scanline < cinfo.image_height) {
468 /* jpeg_write_scanlines expects an array of pointers to scanlines.
469 * Here the array is only one element long, but you could pass
470 * more than one scanline at a time if that's more convenient.
471 */
472 i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data,
473 NULL, im->channels);
474 row_pointer[0] = data;
475 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
476 }
477 }
478 else {
faa9b3e7 479 jpeg_destroy_compress(&cinfo);
f873cb01 480 i_push_error(0, "out of memory");
faa9b3e7
TC
481 return 0; /* out of memory? */
482 }
dd55acc8 483 }
02d1d628 484
dd55acc8 485 /* Step 6: Finish compression */
02d1d628 486
dd55acc8 487 jpeg_finish_compress(&cinfo);
02d1d628 488
dd55acc8 489 jpeg_destroy_compress(&cinfo);
02d1d628 490
10461f9a
TC
491 ig->closecb(ig);
492
dd55acc8 493 return(1);
02d1d628 494}
f873cb01
TC
495
496/*
497=back
498
499=head1 AUTHOR
500
501Arnar M. Hrafnkelsson, addi@umich.edu
502
503=head1 SEE ALSO
504
505Imager(3)
506
507=cut
508*/