- start of external Imager API access:
[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
104 mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n"));
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
f873cb01 220 mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n"));
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
f873cb01
TC
340/*
341=item i_readjpeg_wiol(data, length, iptc_itext, itlength)
02d1d628 342
f873cb01
TC
343=cut
344*/
02d1d628 345i_img*
dd55acc8 346i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
02d1d628 347 i_img *im;
f7450478
TC
348#ifdef IMEXIF_ENABLE
349 int seen_exif = 0;
350#endif
02d1d628
AMH
351
352 struct jpeg_decompress_struct cinfo;
02d1d628 353 struct my_error_mgr jerr;
02d1d628
AMH
354 JSAMPARRAY buffer; /* Output row buffer */
355 int row_stride; /* physical row width in output buffer */
f7450478 356 jpeg_saved_marker_ptr markerp;
02d1d628 357
dd55acc8 358 mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, iptc_itext));
02d1d628 359
f873cb01
TC
360 i_clear_error();
361
dd55acc8 362 iptc_text = iptc_itext;
02d1d628 363 cinfo.err = jpeg_std_error(&jerr.pub);
dd55acc8 364 jerr.pub.error_exit = my_error_exit;
02d1d628 365 jerr.pub.output_message = my_output_message;
02d1d628 366
dd55acc8 367 /* Set error handler */
02d1d628
AMH
368 if (setjmp(jerr.setjmp_buffer)) {
369 jpeg_destroy_decompress(&cinfo);
370 *iptc_itext=NULL;
371 *itlength=0;
372 return NULL;
373 }
374
375 jpeg_create_decompress(&cinfo);
376 jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
f7450478
TC
377 jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
378 jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
dd55acc8
AMH
379 jpeg_wiol_src(&cinfo, data, length);
380
02d1d628
AMH
381 (void) jpeg_read_header(&cinfo, TRUE);
382 (void) jpeg_start_decompress(&cinfo);
77157728
TC
383 if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
384 cinfo.output_components, sizeof(i_sample_t))) {
385 mm_log((1, "i_readjpeg: image size exceeds limits\n"));
386
387 jpeg_destroy_decompress(&cinfo);
388 return NULL;
389 }
02d1d628 390 im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
2c2c832a
TC
391 if (!im) {
392 jpeg_destroy_decompress(&cinfo);
393 return NULL;
394 }
02d1d628
AMH
395 row_stride = cinfo.output_width * cinfo.output_components;
396 buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
397 while (cinfo.output_scanline < cinfo.output_height) {
398 (void) jpeg_read_scanlines(&cinfo, buffer, 1);
faa9b3e7 399 memcpy(im->idata+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
02d1d628 400 }
f7450478
TC
401
402 /* check for APP1 marker and save */
403 markerp = cinfo.marker_list;
404 while (markerp != NULL) {
405 if (markerp->marker == JPEG_COM) {
406 i_tags_add(&im->tags, "jpeg_comment", 0, markerp->data,
407 markerp->data_length, 0);
408 }
409#ifdef IMEXIF_ENABLE
410 else if (markerp->marker == JPEG_APP1 && !seen_exif) {
411 seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
412 }
413#endif
414
415 markerp = markerp->next;
416 }
417
6d54291b
TC
418 if (cinfo.saw_JFIF_marker) {
419 double xres = cinfo.X_density;
420 double yres = cinfo.Y_density;
421
422 i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit);
423 switch (cinfo.density_unit) {
424 case 0: /* values are just the aspect ratio */
425 i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
426 i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0);
427 break;
428
429 case 1: /* per inch */
430 i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0);
431 break;
432
433 case 2: /* per cm */
434 i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0);
435 xres *= 2.54;
436 yres *= 2.54;
437 break;
438 }
439 i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
440 i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
441 }
442
02d1d628
AMH
443 (void) jpeg_finish_decompress(&cinfo);
444 jpeg_destroy_decompress(&cinfo);
445 *itlength=tlength;
2c2c832a
TC
446
447 i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0);
448
dd55acc8 449 mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
02d1d628
AMH
450 return im;
451}
452
f873cb01
TC
453/*
454=item i_writejpeg_wiol(im, ig, qfactor)
02d1d628 455
f873cb01
TC
456=cut
457*/
02d1d628 458
dd55acc8
AMH
459undef_int
460i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
dd55acc8
AMH
461 JSAMPLE *image_buffer;
462 int quality;
6d54291b
TC
463 int got_xres, got_yres, aspect_only, resunit;
464 double xres, yres;
465 int comment_entry;
02d1d628 466
dd55acc8 467 struct jpeg_compress_struct cinfo;
f873cb01 468 struct my_error_mgr jerr;
02d1d628 469
dd55acc8
AMH
470 JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
471 int row_stride; /* physical row width in image buffer */
f873cb01 472 unsigned char * data = NULL;
02d1d628 473
dd55acc8 474 mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
02d1d628 475
f873cb01 476 i_clear_error();
02d1d628 477
f873cb01
TC
478 if (!(im->channels==1 || im->channels==3)) {
479 i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
480 return 0;
481 }
482 quality = qfactor;
02d1d628 483
f873cb01
TC
484 cinfo.err = jpeg_std_error(&jerr.pub);
485 jerr.pub.error_exit = my_error_exit;
486 jerr.pub.output_message = my_output_message;
487
dd55acc8 488 jpeg_create_compress(&cinfo);
02d1d628 489
f873cb01
TC
490 if (setjmp(jerr.setjmp_buffer)) {
491 jpeg_destroy_compress(&cinfo);
492 if (data)
493 myfree(data);
494 return 0;
495 }
496
02d1d628 497 io_glue_commit_types(ig);
dd55acc8 498 jpeg_wiol_dest(&cinfo, ig);
02d1d628 499
dd55acc8
AMH
500 cinfo.image_width = im -> xsize; /* image width and height, in pixels */
501 cinfo.image_height = im -> ysize;
02d1d628 502
dd55acc8
AMH
503 if (im->channels==3) {
504 cinfo.input_components = 3; /* # of color components per pixel */
505 cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
506 }
02d1d628 507
dd55acc8
AMH
508 if (im->channels==1) {
509 cinfo.input_components = 1; /* # of color components per pixel */
510 cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
511 }
02d1d628 512
dd55acc8
AMH
513 jpeg_set_defaults(&cinfo);
514 jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
02d1d628 515
6d54291b
TC
516 got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
517 got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
518 if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
519 aspect_only = 0;
520 if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
521 resunit = 1; /* per inch */
522 if (resunit < 0 || resunit > 2) /* default to inch if invalid */
523 resunit = 1;
524 if (got_xres || got_yres) {
525 if (!got_xres)
526 xres = yres;
527 else if (!got_yres)
528 yres = xres;
529 if (aspect_only)
530 resunit = 0; /* standard tags override format tags */
531 if (resunit == 2) {
532 /* convert to per cm */
533 xres /= 2.54;
534 yres /= 2.54;
535 }
536 cinfo.density_unit = resunit;
537 cinfo.X_density = (int)(xres + 0.5);
538 cinfo.Y_density = (int)(yres + 0.5);
539 }
540
dd55acc8 541 jpeg_start_compress(&cinfo, TRUE);
02d1d628 542
6d54291b
TC
543 if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
544 jpeg_write_marker(&cinfo, JPEG_COM, im->tags.tags[comment_entry].data,
545 im->tags.tags[comment_entry].size);
546 }
547
dd55acc8 548 row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
02d1d628 549
faa9b3e7
TC
550 if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
551 image_buffer=im->idata;
552
553 while (cinfo.next_scanline < cinfo.image_height) {
554 /* jpeg_write_scanlines expects an array of pointers to scanlines.
555 * Here the array is only one element long, but you could pass
556 * more than one scanline at a time if that's more convenient.
557 */
558 row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
559 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
560 }
561 }
562 else {
f873cb01 563 data = mymalloc(im->xsize * im->channels);
faa9b3e7
TC
564 if (data) {
565 while (cinfo.next_scanline < cinfo.image_height) {
566 /* jpeg_write_scanlines expects an array of pointers to scanlines.
567 * Here the array is only one element long, but you could pass
568 * more than one scanline at a time if that's more convenient.
569 */
570 i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data,
571 NULL, im->channels);
572 row_pointer[0] = data;
573 (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
574 }
575 }
576 else {
faa9b3e7 577 jpeg_destroy_compress(&cinfo);
f873cb01 578 i_push_error(0, "out of memory");
faa9b3e7
TC
579 return 0; /* out of memory? */
580 }
dd55acc8 581 }
02d1d628 582
dd55acc8 583 /* Step 6: Finish compression */
02d1d628 584
dd55acc8 585 jpeg_finish_compress(&cinfo);
02d1d628 586
dd55acc8 587 jpeg_destroy_compress(&cinfo);
02d1d628 588
10461f9a
TC
589 ig->closecb(ig);
590
dd55acc8 591 return(1);
02d1d628 592}
f873cb01
TC
593
594/*
595=back
596
597=head1 AUTHOR
598
599Arnar M. Hrafnkelsson, addi@umich.edu
600
601=head1 SEE ALSO
602
603Imager(3)
604
605=cut
606*/