f3d5fc7f917ea84cb523e728ff7ae31da4566471
[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 * volatile im = NULL;
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   volatile int src_set = 0;
399
400   mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
401
402   i_clear_error();
403
404   iptc_text = iptc_itext;
405   cinfo.err = jpeg_std_error(&jerr.pub);
406   jerr.pub.error_exit     = my_error_exit;
407   jerr.pub.output_message = my_output_message;
408
409   /* Set error handler */
410   if (setjmp(jerr.setjmp_buffer)) {
411     if (src_set)
412       wiol_term_source(&cinfo);
413     jpeg_destroy_decompress(&cinfo); 
414     *iptc_itext=NULL;
415     *itlength=0;
416     if (line_buffer)
417       myfree(line_buffer);
418     if (im)
419       i_img_destroy(im);
420     return NULL;
421   }
422   
423   jpeg_create_decompress(&cinfo);
424   jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
425   jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
426   jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
427   jpeg_wiol_src(&cinfo, data, length);
428   src_set = 1;
429
430   (void) jpeg_read_header(&cinfo, TRUE);
431   (void) jpeg_start_decompress(&cinfo);
432
433   channels = cinfo.output_components;
434   switch (cinfo.out_color_space) {
435   case JCS_GRAYSCALE:
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     }
443     transfer_f = transfer_gray;
444     break;
445   
446   case JCS_RGB:
447     transfer_f = transfer_rgb;
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     }
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
484   if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
485                                      channels, sizeof(i_sample_t))) {
486     mm_log((1, "i_readjpeg: image size exceeds limits\n"));
487     wiol_term_source(&cinfo);
488     jpeg_destroy_decompress(&cinfo);
489     return NULL;
490   }
491
492   im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels);
493   if (!im) {
494     wiol_term_source(&cinfo);
495     jpeg_destroy_decompress(&cinfo);
496     return NULL;
497   }
498   row_stride = cinfo.output_width * cinfo.output_components;
499   buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
500   line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
501   while (cinfo.output_scanline < cinfo.output_height) {
502     (void) jpeg_read_scanlines(&cinfo, buffer, 1);
503     transfer_f(line_buffer, buffer, cinfo.output_width);
504     i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer);
505   }
506   myfree(line_buffer);
507
508   /* check for APP1 marker and save */
509   markerp = cinfo.marker_list;
510   while (markerp != NULL) {
511     if (markerp->marker == JPEG_COM) {
512       i_tags_add(&im->tags, "jpeg_comment", 0, (const char *)markerp->data,
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
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
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
552   (void) jpeg_finish_decompress(&cinfo);
553   jpeg_destroy_decompress(&cinfo);
554   *itlength=tlength;
555
556   i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0);
557
558   mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
559   return im;
560 }
561
562 /*
563 =item i_writejpeg_wiol(im, ig, qfactor)
564
565 =cut
566 */
567
568 undef_int
569 i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
570   JSAMPLE *image_buffer;
571   int quality;
572   int got_xres, got_yres, aspect_only, resunit;
573   double xres, yres;
574   int comment_entry;
575
576   struct jpeg_compress_struct cinfo;
577   struct my_error_mgr jerr;
578
579   JSAMPROW row_pointer[1];      /* pointer to JSAMPLE row[s] */
580   int row_stride;               /* physical row width in image buffer */
581   unsigned char * data = NULL;
582
583   mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
584   
585   i_clear_error();
586   io_glue_commit_types(ig);
587
588   if (!(im->channels==1 || im->channels==3)) { 
589     i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
590     return 0;
591   }
592   quality = qfactor;
593
594   cinfo.err = jpeg_std_error(&jerr.pub);
595   jerr.pub.error_exit = my_error_exit;
596   jerr.pub.output_message = my_output_message;
597   
598   jpeg_create_compress(&cinfo);
599
600   if (setjmp(jerr.setjmp_buffer)) {
601     jpeg_destroy_compress(&cinfo);
602     if (data)
603       myfree(data);
604     return 0;
605   }
606
607   jpeg_wiol_dest(&cinfo, ig);
608
609   cinfo.image_width  = im -> xsize;     /* image width and height, in pixels */
610   cinfo.image_height = im -> ysize;
611
612   if (im->channels==3) {
613     cinfo.input_components = 3;         /* # of color components per pixel */
614     cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */
615   }
616
617   if (im->channels==1) {
618     cinfo.input_components = 1;         /* # of color components per pixel */
619     cinfo.in_color_space = JCS_GRAYSCALE;       /* colorspace of input image */
620   }
621
622   jpeg_set_defaults(&cinfo);
623   jpeg_set_quality(&cinfo, quality, TRUE);  /* limit to baseline-JPEG values */
624
625   got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
626   got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
627   if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
628     aspect_only = 0;
629   if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
630     resunit = 1; /* per inch */
631   if (resunit < 0 || resunit > 2) /* default to inch if invalid */
632     resunit = 1;
633   if (got_xres || got_yres) {
634     if (!got_xres)
635       xres = yres;
636     else if (!got_yres)
637       yres = xres;
638     if (aspect_only)
639       resunit = 0; /* standard tags override format tags */
640     if (resunit == 2) {
641       /* convert to per cm */
642       xres /= 2.54;
643       yres /= 2.54;
644     }
645     cinfo.density_unit = resunit;
646     cinfo.X_density = (int)(xres + 0.5);
647     cinfo.Y_density = (int)(yres + 0.5);
648   }
649
650   jpeg_start_compress(&cinfo, TRUE);
651
652   if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
653     jpeg_write_marker(&cinfo, JPEG_COM, 
654                       (const JOCTET *)im->tags.tags[comment_entry].data,
655                       im->tags.tags[comment_entry].size);
656   }
657
658   row_stride = im->xsize * im->channels;        /* JSAMPLEs per row in image_buffer */
659
660   if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
661     image_buffer=im->idata;
662
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       row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
669       (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
670     }
671   }
672   else {
673     data = mymalloc(im->xsize * im->channels);
674     if (data) {
675       while (cinfo.next_scanline < cinfo.image_height) {
676         /* jpeg_write_scanlines expects an array of pointers to scanlines.
677          * Here the array is only one element long, but you could pass
678          * more than one scanline at a time if that's more convenient.
679          */
680         i_gsamp(im, 0, im->xsize, cinfo.next_scanline, data, 
681                 NULL, im->channels);
682         row_pointer[0] = data;
683         (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
684       }
685       myfree(data);
686     }
687     else {
688       jpeg_destroy_compress(&cinfo);
689       i_push_error(0, "out of memory");
690       return 0; /* out of memory? */
691     }
692   }
693
694   /* Step 6: Finish compression */
695
696   jpeg_finish_compress(&cinfo);
697
698   jpeg_destroy_compress(&cinfo);
699
700   ig->closecb(ig);
701
702   return(1);
703 }
704
705 /*
706 =back
707
708 =head1 AUTHOR
709
710 Arnar M. Hrafnkelsson, addi@umich.edu
711
712 =head1 SEE ALSO
713
714 Imager(3)
715
716 =cut
717 */