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