ab32e3df2c674e3cdeeff584d5780c9332cc8689
[imager.git] / jpeg.c
1 /*
2 =head1 NAME
3
4 jpeg.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
16 Reads and writes JPEG images
17
18 =over
19
20 =cut
21 */
22
23 #include <stdio.h>
24 #include <sys/stat.h>
25 #ifndef _MSC_VER
26 #include <unistd.h>
27 #endif
28 #include <setjmp.h>
29
30 #include "iolayer.h"
31 #include "imageri.h"
32 #include "jpeglib.h"
33 #include "jerror.h"
34 #include <errno.h>
35 #ifdef IMEXIF_ENABLE
36 #include "imexif.h"
37 #endif
38
39 #define JPEG_APP13       0xED    /* APP13 marker code */
40 #define JPEG_APP1 (JPEG_APP0 + 1)
41 #define JPGS 16384
42
43 static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
44
45 /* Bad design right here */
46
47 static int tlength=0;
48 static char **iptc_text=NULL;
49
50
51 /* Source and Destination managers */
52
53
54 typedef 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;
61
62 typedef 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;
68
69 typedef wiol_source_mgr *wiol_src_ptr;
70 typedef wiol_destination_mgr *wiol_dest_ptr;
71
72
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
84
85
86 static void
87 wiol_init_source (j_decompress_ptr cinfo) {
88   wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
89   
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.
93    */
94   src->start_of_file = TRUE;
95 }
96
97
98
99 static boolean
100 wiol_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", cinfo));
105   
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;
111   } else {
112     src->pub.next_input_byte = src->buffer;
113     src->pub.bytes_in_buffer = nbytes;
114   }
115   src->start_of_file = FALSE;
116   return TRUE;
117 }
118
119
120 static void
121 wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
122   wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
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;
132       (void) wiol_fill_input_buffer(cinfo);
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
142 static void
143 wiol_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 */
154
155 static void
156 jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
157   wiol_src_ptr src;
158   
159   if (cinfo->src == NULL) {     /* first time for this JPEG object? */
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;
163   }
164
165   /* put the request method call in here later */
166   io_glue_commit_types(ig);
167   
168   src         = (wiol_src_ptr) cinfo->src;
169   src->data   = ig;
170   src->buffer = mymalloc( JPGS );
171   src->length = length;
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;
176   src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
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 */
180 }
181
182
183
184
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  */
193
194 static void
195 wiol_init_destination (j_compress_ptr cinfo) {
196   wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
197   
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.
201    */
202   dest->start_of_file = TRUE;
203 }
204
205 static boolean
206 wiol_empty_output_buffer(j_compress_ptr cinfo) {
207   wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
208   ssize_t rc;
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   */
219
220   mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo));
221   rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
222
223   if (rc != JPGS) { /* XXX: Should raise some jpeg error */
224     myfree(dest->buffer);
225     mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc));
226     ERREXIT(cinfo, JERR_FILE_WRITE);
227   }
228   dest->pub.free_in_buffer = JPGS;
229   dest->pub.next_output_byte = dest->buffer;
230   return TRUE;
231 }
232
233 static void
234 wiol_term_destination (j_compress_ptr cinfo) {
235   wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
236   size_t nbytes = JPGS - dest->pub.free_in_buffer;
237   /* yes, this needs to flush the buffer */
238   /* needs error handling */
239
240   if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
241     myfree(dest->buffer);
242     ERREXIT(cinfo, JERR_FILE_WRITE);
243   }
244   
245   if (dest != NULL) myfree(dest->buffer);
246 }
247
248
249 /* Destination manager Constructor */
250
251 static void
252 jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) {
253   wiol_dest_ptr dest;
254   
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));
260   }
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;
271 }
272
273 LOCAL(unsigned int)
274 jpeg_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
288 METHODDEF(boolean)
289 APP13_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
305 METHODDEF(void)
306 my_output_message (j_common_ptr cinfo) {
307   char buffer[JMSG_LENGTH_MAX];
308
309   /* Create the message */
310   (*cinfo->err->format_message) (cinfo, buffer);
311
312   i_push_error(0, buffer);
313
314   /* Send it to stderr, adding a newline */
315   mm_log((1, "%s\n", buffer));
316 }
317
318 struct my_error_mgr {
319   struct jpeg_error_mgr pub;    /* "public" fields */
320   jmp_buf setjmp_buffer;        /* for return to caller */
321 };
322
323 typedef struct my_error_mgr * my_error_ptr;
324
325 /* Here's the routine that will replace the standard error_exit method */
326
327 METHODDEF(void)
328 my_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);
335
336   /* Return control to the setjmp point */
337   longjmp(myerr->setjmp_buffer, 1);
338 }
339
340 static void 
341 transfer_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
357 static void
358 transfer_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
368 static void
369 transfer_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
377 typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width);
378
379 /*
380 =item i_readjpeg_wiol(data, length, iptc_itext, itlength)
381
382 =cut
383 */
384 i_img*
385 i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
386   i_img *im;
387 #ifdef IMEXIF_ENABLE
388   int seen_exif = 0;
389 #endif
390   i_color *line_buffer = NULL;
391   struct jpeg_decompress_struct cinfo;
392   struct my_error_mgr jerr;
393   JSAMPARRAY buffer;            /* Output row buffer */
394   int row_stride;               /* physical row width in output buffer */
395   jpeg_saved_marker_ptr markerp;
396   transfer_function_t transfer_f;
397   int channels;
398
399   mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
400
401   i_clear_error();
402
403   iptc_text = iptc_itext;
404   cinfo.err = jpeg_std_error(&jerr.pub);
405   jerr.pub.error_exit     = my_error_exit;
406   jerr.pub.output_message = my_output_message;
407
408   /* Set error handler */
409   if (setjmp(jerr.setjmp_buffer)) {
410     jpeg_destroy_decompress(&cinfo); 
411     *iptc_itext=NULL;
412     *itlength=0;
413     if (line_buffer)
414       myfree(line_buffer);
415     return NULL;
416   }
417   
418   jpeg_create_decompress(&cinfo);
419   jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
420   jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
421   jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
422   jpeg_wiol_src(&cinfo, data, length);
423
424   (void) jpeg_read_header(&cinfo, TRUE);
425   (void) jpeg_start_decompress(&cinfo);
426
427   channels = cinfo.output_components;
428   switch (cinfo.out_color_space) {
429   case JCS_GRAYSCALE:
430     transfer_f = transfer_gray;
431     break;
432   
433   case JCS_RGB:
434     transfer_f = transfer_rgb;
435     break;
436
437   case JCS_CMYK:
438     if (cinfo.output_components == 4) {
439       /* we treat the CMYK values as inverted, because that's what that
440          buggy photoshop does, and everyone has to follow the gorilla.
441
442          Is there any app that still produces correct CMYK JPEGs?
443       */
444       transfer_f = transfer_cmyk_inverted;
445       channels = 3;
446     }
447     else {
448       mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components));
449       i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components);
450       wiol_term_source(&cinfo);
451       jpeg_destroy_decompress(&cinfo);
452       return NULL;
453     }
454     break;
455
456   default:
457     mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space));
458     i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space);
459     wiol_term_source(&cinfo);
460     jpeg_destroy_decompress(&cinfo);
461     return NULL;
462   }
463
464   if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
465                                      channels, sizeof(i_sample_t))) {
466     mm_log((1, "i_readjpeg: image size exceeds limits\n"));
467     wiol_term_source(&cinfo);
468     jpeg_destroy_decompress(&cinfo);
469     return NULL;
470   }
471
472   im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels);
473   if (!im) {
474     wiol_term_source(&cinfo);
475     jpeg_destroy_decompress(&cinfo);
476     return NULL;
477   }
478   row_stride = cinfo.output_width * cinfo.output_components;
479   buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
480   line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
481   while (cinfo.output_scanline < cinfo.output_height) {
482     (void) jpeg_read_scanlines(&cinfo, buffer, 1);
483     transfer_f(line_buffer, buffer, cinfo.output_width);
484     i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer);
485   }
486   myfree(line_buffer);
487
488   /* check for APP1 marker and save */
489   markerp = cinfo.marker_list;
490   while (markerp != NULL) {
491     if (markerp->marker == JPEG_COM) {
492       i_tags_add(&im->tags, "jpeg_comment", 0, markerp->data,
493                  markerp->data_length, 0);
494     }
495 #ifdef IMEXIF_ENABLE
496     else if (markerp->marker == JPEG_APP1 && !seen_exif) {
497       seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
498     }
499 #endif      
500
501     markerp = markerp->next;
502   }
503
504   i_tags_addn(&im->tags, "jpeg_out_color_space", 0, cinfo.out_color_space);
505   i_tags_addn(&im->tags, "jpeg_color_space", 0, cinfo.jpeg_color_space);
506
507   if (cinfo.saw_JFIF_marker) {
508     double xres = cinfo.X_density;
509     double yres = cinfo.Y_density;
510     
511     i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit);
512     switch (cinfo.density_unit) {
513     case 0: /* values are just the aspect ratio */
514       i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
515       i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0);
516       break;
517
518     case 1: /* per inch */
519       i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0);
520       break;
521
522     case 2: /* per cm */
523       i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0);
524       xres *= 2.54;
525       yres *= 2.54;
526       break;
527     }
528     i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
529     i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
530   }
531
532   (void) jpeg_finish_decompress(&cinfo);
533   jpeg_destroy_decompress(&cinfo);
534   *itlength=tlength;
535
536   i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0);
537
538   mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
539   return im;
540 }
541
542 /*
543 =item i_writejpeg_wiol(im, ig, qfactor)
544
545 =cut
546 */
547
548 undef_int
549 i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
550   JSAMPLE *image_buffer;
551   int quality;
552   int got_xres, got_yres, aspect_only, resunit;
553   double xres, yres;
554   int comment_entry;
555
556   struct jpeg_compress_struct cinfo;
557   struct my_error_mgr jerr;
558
559   JSAMPROW row_pointer[1];      /* pointer to JSAMPLE row[s] */
560   int row_stride;               /* physical row width in image buffer */
561   unsigned char * data = NULL;
562
563   mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
564   
565   i_clear_error();
566   io_glue_commit_types(ig);
567
568   if (!(im->channels==1 || im->channels==3)) { 
569     i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
570     return 0;
571   }
572   quality = qfactor;
573
574   cinfo.err = jpeg_std_error(&jerr.pub);
575   jerr.pub.error_exit = my_error_exit;
576   jerr.pub.output_message = my_output_message;
577   
578   jpeg_create_compress(&cinfo);
579
580   if (setjmp(jerr.setjmp_buffer)) {
581     jpeg_destroy_compress(&cinfo);
582     if (data)
583       myfree(data);
584     return 0;
585   }
586
587   jpeg_wiol_dest(&cinfo, ig);
588
589   cinfo.image_width  = im -> xsize;     /* image width and height, in pixels */
590   cinfo.image_height = im -> ysize;
591
592   if (im->channels==3) {
593     cinfo.input_components = 3;         /* # of color components per pixel */
594     cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */
595   }
596
597   if (im->channels==1) {
598     cinfo.input_components = 1;         /* # of color components per pixel */
599     cinfo.in_color_space = JCS_GRAYSCALE;       /* colorspace of input image */
600   }
601
602   jpeg_set_defaults(&cinfo);
603   jpeg_set_quality(&cinfo, quality, TRUE);  /* limit to baseline-JPEG values */
604
605   got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
606   got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
607   if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
608     aspect_only = 0;
609   if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
610     resunit = 1; /* per inch */
611   if (resunit < 0 || resunit > 2) /* default to inch if invalid */
612     resunit = 1;
613   if (got_xres || got_yres) {
614     if (!got_xres)
615       xres = yres;
616     else if (!got_yres)
617       yres = xres;
618     if (aspect_only)
619       resunit = 0; /* standard tags override format tags */
620     if (resunit == 2) {
621       /* convert to per cm */
622       xres /= 2.54;
623       yres /= 2.54;
624     }
625     cinfo.density_unit = resunit;
626     cinfo.X_density = (int)(xres + 0.5);
627     cinfo.Y_density = (int)(yres + 0.5);
628   }
629
630   jpeg_start_compress(&cinfo, TRUE);
631
632   if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
633     jpeg_write_marker(&cinfo, JPEG_COM, im->tags.tags[comment_entry].data,
634                       im->tags.tags[comment_entry].size);
635   }
636
637   row_stride = im->xsize * im->channels;        /* JSAMPLEs per row in image_buffer */
638
639   if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
640     image_buffer=im->idata;
641
642     while (cinfo.next_scanline < cinfo.image_height) {
643       /* jpeg_write_scanlines expects an array of pointers to scanlines.
644        * Here the array is only one element long, but you could pass
645        * more than one scanline at a time if that's more convenient.
646        */
647       row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
648       (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
649     }
650   }
651   else {
652     data = mymalloc(im->xsize * im->channels);
653     if (data) {
654       while (cinfo.next_scanline < cinfo.image_height) {
655         /* jpeg_write_scanlines expects an array of pointers to scanlines.
656          * Here the array is only one element long, but you could pass
657          * more than one scanline at a time if that's more convenient.
658          */
659         i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data, 
660                 NULL, im->channels);
661         row_pointer[0] = data;
662         (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
663       }
664       myfree(data);
665     }
666     else {
667       jpeg_destroy_compress(&cinfo);
668       i_push_error(0, "out of memory");
669       return 0; /* out of memory? */
670     }
671   }
672
673   /* Step 6: Finish compression */
674
675   jpeg_finish_compress(&cinfo);
676
677   jpeg_destroy_compress(&cinfo);
678
679   ig->closecb(ig);
680
681   return(1);
682 }
683
684 /*
685 =back
686
687 =head1 AUTHOR
688
689 Arnar M. Hrafnkelsson, addi@umich.edu
690
691 =head1 SEE ALSO
692
693 Imager(3)
694
695 =cut
696 */