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