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