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