]> Pileus Git - ~andy/gtk/blob - gdk-pixbuf/io-jpeg.c
Add shift-clicking to extend the selection. (Roughly based on patch from
[~andy/gtk] / gdk-pixbuf / io-jpeg.c
1 /* GdkPixbuf library - JPEG image loader
2  *
3  * Copyright (C) 1999 Michael Zucchi
4  * Copyright (C) 1999 The Free Software Foundation
5  * 
6  * Progressive loading code Copyright (C) 1999 Red Hat, Inc.
7  *
8  * Authors: Michael Zucchi <zucchi@zedzone.mmc.com.au>
9  *          Federico Mena-Quintero <federico@gimp.org>
10  *          Michael Fulbright <drmike@redhat.com>
11  *
12  * This library is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public
14  * License as published by the Free Software Foundation; either
15  * version 2 of the License, or (at your option) any later version.
16  *
17  * This library is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with this library; if not, write to the
24  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25  * Boston, MA 02111-1307, USA.
26  */
27
28
29 /*
30   Progressive file loading notes (11/03/1999) <drmike@redhat.com>...
31
32   These are issues I know of and will be dealing with shortly:
33
34     -  Currently does not handle progressive jpegs - this
35        requires a change in the way image_load_increment () calls
36        libjpeg. Progressive jpegs are rarer but I will add this
37        support asap.
38
39     - error handling is not as good as it should be
40
41  */
42
43
44 #include <config.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <setjmp.h>
49 #include <jpeglib.h>
50 #include "gdk-pixbuf-private.h"
51 #include "gdk-pixbuf-io.h"
52
53 #ifndef HAVE_SIGSETJMP
54 #define sigjmp_buf jmp_buf
55 #define sigsetjmp(jb, x) setjmp(jb)
56 #define siglongjmp longjmp
57 #endif
58 \f
59
60 /* we are a "source manager" as far as libjpeg is concerned */
61 #define JPEG_PROG_BUF_SIZE 4096
62
63 typedef struct {
64         struct jpeg_source_mgr pub;   /* public fields */
65
66         JOCTET buffer[JPEG_PROG_BUF_SIZE];              /* start of buffer */
67         long  skip_next;              /* number of bytes to skip next read */
68
69 } my_source_mgr;
70
71 typedef my_source_mgr * my_src_ptr;
72
73 /* error handler data */
74 struct error_handler_data {
75         struct jpeg_error_mgr pub;
76         sigjmp_buf setjmp_buffer;
77         GError **error;
78 };
79
80 /* progressive loader context */
81 typedef struct {
82         ModuleUpdatedNotifyFunc  updated_func;
83         ModulePreparedNotifyFunc prepared_func;
84         gpointer                 user_data;
85         
86         GdkPixbuf                *pixbuf;
87         guchar                   *dptr;   /* current position in pixbuf */
88
89         gboolean                 did_prescan;  /* are we in image data yet? */
90         gboolean                 got_header;  /* have we loaded jpeg header? */
91         gboolean                 src_initialized;/* TRUE when jpeg lib initialized */
92         struct jpeg_decompress_struct cinfo;
93         struct error_handler_data     jerr;
94 } JpegProgContext;
95
96 static GdkPixbuf *gdk_pixbuf__jpeg_image_load (FILE *f, GError **error);
97 static gpointer gdk_pixbuf__jpeg_image_begin_load (ModulePreparedNotifyFunc func, 
98                                                    ModuleUpdatedNotifyFunc func2,
99                                                    ModuleFrameDoneNotifyFunc func3,
100                                                    ModuleAnimationDoneNotifyFunc func4,
101                                                    gpointer user_data,
102                                                    GError **error);
103 static gboolean gdk_pixbuf__jpeg_image_stop_load (gpointer context, GError **error);
104 static gboolean gdk_pixbuf__jpeg_image_load_increment(gpointer context,
105                                                       const guchar *buf, guint size,
106                                                       GError **error);
107
108
109 static void
110 fatal_error_handler (j_common_ptr cinfo)
111 {
112         struct error_handler_data *errmgr;
113         char buffer[JMSG_LENGTH_MAX];
114         
115         errmgr = (struct error_handler_data *) cinfo->err;
116         
117         /* Create the message */
118         (* cinfo->err->format_message) (cinfo, buffer);
119
120         /* broken check for *error == NULL for robustness against
121          * crappy JPEG library
122          */
123         if (errmgr->error && *errmgr->error == NULL) {
124                 g_set_error (errmgr->error,
125                              GDK_PIXBUF_ERROR,
126                              GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
127                              _("Error interpreting JPEG image file (%s)"),
128                              buffer);
129         }
130         
131         siglongjmp (errmgr->setjmp_buffer, 1);
132
133         g_assert_not_reached ();
134 }
135
136 static void
137 output_message_handler (j_common_ptr cinfo)
138 {
139   /* This method keeps libjpeg from dumping crap to stderr */
140
141   /* do nothing */
142 }
143
144 /* Destroy notification function for the pixbuf */
145 static void
146 free_buffer (guchar *pixels, gpointer data)
147 {
148         g_free (pixels);
149 }
150
151
152 /* explode gray image data from jpeg library into rgb components in pixbuf */
153 static void
154 explode_gray_into_buf (struct jpeg_decompress_struct *cinfo,
155                        guchar **lines) 
156 {
157         gint i, j;
158         guint w;
159
160         g_return_if_fail (cinfo != NULL);
161         g_return_if_fail (cinfo->output_components == 1);
162
163         /* Expand grey->colour.  Expand from the end of the
164          * memory down, so we can use the same buffer.
165          */
166         w = cinfo->image_width;
167         for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) {
168                 guchar *from, *to;
169                 
170                 from = lines[i] + w - 1;
171                 to = lines[i] + (w - 1) * 3;
172                 for (j = w - 1; j >= 0; j--) {
173                         to[0] = from[0];
174                         to[1] = from[0];
175                         to[2] = from[0];
176                         to -= 3;
177                         from--;
178                 }
179         }
180 }
181
182 /* Shared library entry point */
183 static GdkPixbuf *
184 gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
185 {
186         gint w, h, i;
187         guchar *pixels = NULL;
188         guchar *dptr;
189         guchar *lines[4]; /* Used to expand rows, via rec_outbuf_height, 
190                            * from the header file: 
191                            * " Usually rec_outbuf_height will be 1 or 2, 
192                            * at most 4."
193                            */
194         guchar **lptr;
195         struct jpeg_decompress_struct cinfo;
196         struct error_handler_data jerr;
197
198         /* setup error handler */
199         cinfo.err = jpeg_std_error (&jerr.pub);
200         jerr.pub.error_exit = fatal_error_handler;
201         jerr.pub.output_message = output_message_handler;
202
203         jerr.error = error;
204         
205         if (sigsetjmp (jerr.setjmp_buffer, 1)) {
206                 /* Whoops there was a jpeg error */
207                 if (pixels)
208                         free (pixels);
209
210                 jpeg_destroy_decompress (&cinfo);
211                 return NULL;
212         }
213
214         /* load header, setup */
215         jpeg_create_decompress (&cinfo);
216         jpeg_stdio_src (&cinfo, f);
217         jpeg_read_header (&cinfo, TRUE);
218         jpeg_start_decompress (&cinfo);
219         cinfo.do_fancy_upsampling = FALSE;
220         cinfo.do_block_smoothing = FALSE;
221
222         w = cinfo.output_width;
223         h = cinfo.output_height;
224
225         pixels = g_try_malloc (h * w * 3);
226         if (!pixels) {
227                 jpeg_destroy_decompress (&cinfo);
228
229                 /* broken check for *error == NULL for robustness against
230                  * crappy JPEG library
231                  */
232                 if (error && *error == NULL) {
233                         g_set_error (error,
234                                      GDK_PIXBUF_ERROR,
235                                      GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
236                                      _("Insufficient memory to load image, try exiting some applications to free memory"));
237                 }
238                 
239                 return NULL;
240         }
241
242         dptr = pixels;
243
244         /* decompress all the lines, a few at a time */
245
246         while (cinfo.output_scanline < cinfo.output_height) {
247                 lptr = lines;
248                 for (i = 0; i < cinfo.rec_outbuf_height; i++) {
249                         *lptr++ = dptr;
250                         dptr += w * 3;
251                 }
252
253                 jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height);
254
255                 if (cinfo.output_components == 1)
256                         explode_gray_into_buf (&cinfo, lines);
257         }
258
259         jpeg_finish_decompress (&cinfo);
260         jpeg_destroy_decompress (&cinfo);
261
262         return gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8,
263                                          w, h, w * 3,
264                                          free_buffer, NULL);
265 }
266
267
268 /**** Progressive image loading handling *****/
269
270 /* these routines required because we are acting as a source manager for */
271 /* libjpeg. */
272 static void
273 init_source (j_decompress_ptr cinfo)
274 {
275         my_src_ptr src = (my_src_ptr) cinfo->src;
276
277         src->skip_next = 0;
278 }
279
280
281 static void
282 term_source (j_decompress_ptr cinfo)
283 {
284         /* XXXX - probably should scream something has happened */
285 }
286
287
288 /* for progressive loading (called "I/O Suspension" by libjpeg docs) */
289 /* we do nothing except return "FALSE"                               */
290 static boolean
291 fill_input_buffer (j_decompress_ptr cinfo)
292 {
293         return FALSE;
294 }
295
296
297 static void
298 skip_input_data (j_decompress_ptr cinfo, long num_bytes)
299 {
300         my_src_ptr src = (my_src_ptr) cinfo->src;
301         long   num_can_do;
302
303         /* move as far as we can into current buffer */
304         /* then set skip_next to catch the rest      */
305         if (num_bytes > 0) {
306                 num_can_do = MIN (src->pub.bytes_in_buffer, num_bytes);
307                 src->pub.next_input_byte += (size_t) num_can_do;
308                 src->pub.bytes_in_buffer -= (size_t) num_can_do;
309
310                 src->skip_next = num_bytes - num_can_do;
311         }
312 }
313
314  
315 /* 
316  * func - called when we have pixmap created (but no image data)
317  * user_data - passed as arg 1 to func
318  * return context (opaque to user)
319  */
320
321 gpointer
322 gdk_pixbuf__jpeg_image_begin_load (ModulePreparedNotifyFunc prepared_func, 
323                                    ModuleUpdatedNotifyFunc  updated_func,
324                                    ModuleFrameDoneNotifyFunc frame_func,
325                                    ModuleAnimationDoneNotifyFunc anim_done_func,
326                                    gpointer user_data,
327                                    GError **error)
328 {
329         JpegProgContext *context;
330         my_source_mgr   *src;
331
332         context = g_new0 (JpegProgContext, 1);
333         context->prepared_func = prepared_func;
334         context->updated_func  = updated_func;
335         context->user_data = user_data;
336         context->pixbuf = NULL;
337         context->got_header = FALSE;
338         context->did_prescan = FALSE;
339         context->src_initialized = FALSE;
340
341         /* create libjpeg structures */
342         jpeg_create_decompress (&context->cinfo);
343
344         context->cinfo.src = (struct jpeg_source_mgr *) g_new0 (my_source_mgr, 1);
345         src = (my_src_ptr) context->cinfo.src;
346
347         context->cinfo.err = jpeg_std_error (&context->jerr.pub);
348         context->jerr.pub.error_exit = fatal_error_handler;
349         context->jerr.pub.output_message = output_message_handler;
350         context->jerr.error = error;
351         
352         src = (my_src_ptr) context->cinfo.src;
353         src->pub.init_source = init_source;
354         src->pub.fill_input_buffer = fill_input_buffer;
355         src->pub.skip_input_data = skip_input_data;
356         src->pub.resync_to_restart = jpeg_resync_to_restart;
357         src->pub.term_source = term_source;
358         src->pub.bytes_in_buffer = 0;
359         src->pub.next_input_byte = NULL;
360
361         context->jerr.error = NULL;
362         
363         return (gpointer) context;
364 }
365
366 /*
367  * context - returned from image_begin_load
368  *
369  * free context, unref gdk_pixbuf
370  */
371 static gboolean
372 gdk_pixbuf__jpeg_image_stop_load (gpointer data, GError **error)
373 {
374         JpegProgContext *context = (JpegProgContext *) data;
375
376         g_return_val_if_fail (context != NULL, TRUE);
377
378         /* FIXME this thing needs to report errors if
379          * we have unused image data
380          */
381         
382         if (context->pixbuf)
383                 gdk_pixbuf_unref (context->pixbuf);
384
385         /* if we have an error? */
386         if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
387                 jpeg_destroy_decompress (&context->cinfo);
388         } else {
389                 jpeg_finish_decompress(&context->cinfo);
390                 jpeg_destroy_decompress(&context->cinfo);
391         }
392
393         if (context->cinfo.src) {
394                 my_src_ptr src = (my_src_ptr) context->cinfo.src;
395                 
396                 g_free (src);
397         }
398
399         g_free (context);
400
401         return TRUE;
402 }
403
404
405
406
407 /*
408  * context - from image_begin_load
409  * buf - new image data
410  * size - length of new image data
411  *
412  * append image data onto inrecrementally built output image
413  */
414 static gboolean
415 gdk_pixbuf__jpeg_image_load_increment (gpointer data,
416                                        const guchar *buf, guint size,
417                                        GError **error)
418 {
419         JpegProgContext *context = (JpegProgContext *)data;
420         struct jpeg_decompress_struct *cinfo;
421         my_src_ptr  src;
422         guint       num_left, num_copy;
423         guint       last_bytes_left;
424         guint       spinguard;
425         gboolean    first;
426         const guchar *bufhd;
427
428         g_return_val_if_fail (context != NULL, FALSE);
429         g_return_val_if_fail (buf != NULL, FALSE);
430
431         src = (my_src_ptr) context->cinfo.src;
432
433         cinfo = &context->cinfo;
434
435         context->jerr.error = error;
436         
437         /* XXXXXXX (drmike) - loop(s) below need to be recoded now I
438          *                    have a grasp of what the flow needs to be!
439          */
440
441         /* check for fatal error */
442         if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
443                 return FALSE;
444         }
445
446         /* skip over data if requested, handle unsigned int sizes cleanly */
447         /* only can happen if we've already called jpeg_get_header once   */
448         if (context->src_initialized && src->skip_next) {
449                 if (src->skip_next > size) {
450                         src->skip_next -= size;
451                         return TRUE;
452                 } else {
453                         num_left = size - src->skip_next;
454                         bufhd = buf + src->skip_next;
455                         src->skip_next = 0;
456                 }
457         } else {
458                 num_left = size;
459                 bufhd = buf;
460         }
461
462         if (num_left == 0)
463                 return TRUE;
464
465         last_bytes_left = 0;
466         spinguard = 0;
467         first = TRUE;
468         while (TRUE) {
469
470                 /* handle any data from caller we haven't processed yet */
471                 if (num_left > 0) {
472                         if(src->pub.bytes_in_buffer && 
473                            src->pub.next_input_byte != src->buffer)
474                                 memmove(src->buffer, src->pub.next_input_byte,
475                                         src->pub.bytes_in_buffer);
476
477
478                         num_copy = MIN (JPEG_PROG_BUF_SIZE - src->pub.bytes_in_buffer,
479                                         num_left);
480
481 /*                      if (num_copy == 0) 
482                                 g_error ("Buffer overflow!");
483 */
484                         memcpy(src->buffer + src->pub.bytes_in_buffer, bufhd,num_copy);
485                         src->pub.next_input_byte = src->buffer;
486                         src->pub.bytes_in_buffer += num_copy;
487                         bufhd += num_copy;
488                         num_left -= num_copy;
489                 } else {
490                 /* did anything change from last pass, if not return */
491                         if (first) {
492                                 last_bytes_left = src->pub.bytes_in_buffer;
493                                 first = FALSE;
494                         } else if (src->pub.bytes_in_buffer == last_bytes_left)
495                                 spinguard++;
496                         else
497                                 last_bytes_left = src->pub.bytes_in_buffer;
498                 }
499
500                 /* should not go through twice and not pull bytes out of buf */
501                 if (spinguard > 2)
502                         return TRUE;
503
504                 /* try to load jpeg header */
505                 if (!context->got_header) {
506                         int rc;
507
508                         rc = jpeg_read_header (cinfo, TRUE);
509                         context->src_initialized = TRUE;
510
511                         if (rc == JPEG_SUSPENDED)
512                                 continue;
513
514                         context->got_header = TRUE;
515
516 #if 0
517                         if (jpeg_has_multiple_scans (cinfo)) {
518                                 g_print ("io-jpeg.c: Does not currently "
519                                          "support progressive jpeg files.\n");
520                                 return FALSE;
521                         }
522 #endif
523                         context->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, 
524                                                          FALSE,
525                                                          8, 
526                                                          cinfo->image_width,
527                                                          cinfo->image_height);
528
529                         if (context->pixbuf == NULL) {
530                                 g_set_error (error,
531                                              GDK_PIXBUF_ERROR,
532                                              GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
533                                              _("Couldn't allocate memory for loading JPEG file"));
534                                 return FALSE;
535                         }
536
537                         /* Use pixbuf buffer to store decompressed data */
538                         context->dptr = context->pixbuf->pixels;
539
540                         /* Notify the client that we are ready to go */
541                         (* context->prepared_func) (context->pixbuf,
542                                                     context->user_data);
543
544                 } else if (!context->did_prescan) {
545                         int rc;
546
547                         /* start decompression */
548                         rc = jpeg_start_decompress (cinfo);
549                         cinfo->do_fancy_upsampling = FALSE;
550                         cinfo->do_block_smoothing = FALSE;
551
552                         if (rc == JPEG_SUSPENDED)
553                                 continue;
554
555                         context->did_prescan = TRUE;
556                 } else {
557
558                         /* we're decompressing so feed jpeg lib scanlines */
559                         guchar *lines[4];
560                         guchar **lptr;
561                         guchar *rowptr;
562                         gint   nlines, i;
563                         gint   start_scanline;
564
565                         /* keep going until we've done all scanlines */
566                         while (cinfo->output_scanline < cinfo->output_height) {
567                                 start_scanline = cinfo->output_scanline;
568                                 lptr = lines;
569                                 rowptr = context->dptr;
570                                 for (i=0; i < cinfo->rec_outbuf_height; i++) {
571                                         *lptr++ = rowptr;
572                                         rowptr += context->pixbuf->rowstride;
573                                 }
574
575                                 nlines = jpeg_read_scanlines (cinfo, lines,
576                                                               cinfo->rec_outbuf_height);
577                                 if (nlines == 0)
578                                         break;
579
580                                 /* handle gray */
581                                 if (cinfo->output_components == 1)
582                                         explode_gray_into_buf (cinfo, lines);
583
584                                 context->dptr += nlines * context->pixbuf->rowstride;
585
586                                 /* send updated signal */
587                                 (* context->updated_func) (context->pixbuf,
588                                                            0, 
589                                                            cinfo->output_scanline-1,
590                                                            cinfo->image_width, 
591                                                            nlines,
592                                                            context->user_data);
593
594 #undef DEBUG_JPEG_PROGRESSIVE
595 #ifdef DEBUG_JPEG_PROGRESSIVE
596                                 
597                                 if (start_scanline != cinfo->output_scanline)
598                                         g_print("jpeg: Input pass=%2d, next input scanline=%3d,"
599                                                 " emitted %3d - %3d\n",
600                                                 cinfo->input_scan_number, cinfo->input_iMCU_row * 16,
601                                                 start_scanline, cinfo->output_scanline - 1);
602                                 
603                                 
604                                 
605                                 g_print ("Scanline %d of %d - ", 
606                                          cinfo->output_scanline,
607                                          cinfo->output_height);
608 /*                      g_print ("rec_height %d -", cinfo->rec_outbuf_height); */
609                                 g_print ("Processed %d lines - bytes left = %d\n",
610                                          nlines, cinfo->src->bytes_in_buffer);
611 #endif
612                         }
613                         /* did entire image */
614                         if (cinfo->output_scanline >= cinfo->output_height)
615                                 return TRUE;
616                         else
617                                 continue;
618                 }
619         }
620
621         return TRUE;
622 }
623
624 static gboolean
625 gdk_pixbuf__jpeg_image_save (FILE          *f, 
626                              GdkPixbuf     *pixbuf, 
627                              gchar        **keys,
628                              gchar        **values,
629                              GError       **error)
630 {
631         /* FIXME error handling is broken */
632         
633        struct jpeg_compress_struct cinfo;
634        guchar *buf = NULL;
635        guchar *ptr;
636        guchar *pixels = NULL;
637        JSAMPROW *jbuf;
638        int y = 0;
639        int quality = 75; /* default; must be between 0 and 100 */
640        int i, j;
641        int w, h = 0;
642        int rowstride = 0;
643        struct error_handler_data jerr;
644
645        if (keys && *keys) {
646                gchar **kiter = keys;
647                gchar **viter = values;
648
649                while (*kiter) {
650                        if (strcmp (*kiter, "quality") == 0) {
651                                char *endptr = NULL;
652                                quality = strtol (*viter, &endptr, 10);
653
654                                if (endptr == *viter) {
655                                        g_set_error (error,
656                                                     GDK_PIXBUF_ERROR,
657                                                     GDK_PIXBUF_ERROR_BAD_OPTION_VALUE,
658                                                     _("JPEG quality must be a value between 0 and 100; value '%s' could not be parsed."),
659                                                     *viter);
660
661                                        return FALSE;
662                                }
663                                
664                                if (quality < 0 ||
665                                    quality > 100) {
666                                        /* This is a user-visible error;
667                                         * lets people skip the range-checking
668                                         * in their app.
669                                         */
670                                        g_set_error (error,
671                                                     GDK_PIXBUF_ERROR,
672                                                     GDK_PIXBUF_ERROR_BAD_OPTION_VALUE,
673                                                     _("JPEG quality must be a value between 0 and 100; value '%d' is not allowed."),
674                                                     quality);
675
676                                        return FALSE;
677                                }
678                        } else {
679                                g_warning ("Bad option name '%s' passed to JPEG saver",
680                                           *kiter);
681                                return FALSE;
682                        }
683                
684                        ++kiter;
685                        ++viter;
686                }
687        }
688        
689        rowstride = gdk_pixbuf_get_rowstride (pixbuf);
690
691        w = gdk_pixbuf_get_width (pixbuf);
692        h = gdk_pixbuf_get_height (pixbuf);
693
694        /* no image data? abort */
695        pixels = gdk_pixbuf_get_pixels (pixbuf);
696        g_return_val_if_fail (pixels != NULL, FALSE);
697
698        /* allocate a small buffer to convert image data */
699        buf = g_malloc (w * 3 * sizeof (guchar));
700
701        /* set up error handling */
702        jerr.pub.error_exit = fatal_error_handler;
703        jerr.pub.output_message = output_message_handler;
704        jerr.error = error;
705        
706        cinfo.err = jpeg_std_error (&(jerr.pub));
707        if (sigsetjmp (jerr.setjmp_buffer, 1)) {
708                jpeg_destroy_compress (&cinfo);
709                free (buf);
710                return FALSE;
711        }
712
713        /* setup compress params */
714        jpeg_create_compress (&cinfo);
715        jpeg_stdio_dest (&cinfo, f);
716        cinfo.image_width      = w;
717        cinfo.image_height     = h;
718        cinfo.input_components = 3; 
719        cinfo.in_color_space   = JCS_RGB;
720
721        /* set up jepg compression parameters */
722        jpeg_set_defaults (&cinfo);
723        jpeg_set_quality (&cinfo, quality, TRUE);
724        jpeg_start_compress (&cinfo, TRUE);
725        /* get the start pointer */
726        ptr = pixels;
727        /* go one scanline at a time... and save */
728        i = 0;
729        while (cinfo.next_scanline < cinfo.image_height) {
730                /* convert scanline from ARGB to RGB packed */
731                for (j = 0; j < w; j++)
732                        memcpy (&(buf[j*3]), &(ptr[i*rowstride + j*3]), 3);
733
734                /* write scanline */
735                jbuf = (JSAMPROW *)(&buf);
736                jpeg_write_scanlines (&cinfo, jbuf, 1);
737                i++;
738                y++;
739
740        }
741        
742        /* finish off */
743        jpeg_finish_compress (&cinfo);   
744        free (buf);
745        return TRUE;
746 }
747
748 void
749 gdk_pixbuf__jpeg_fill_vtable (GdkPixbufModule *module)
750 {
751   module->load = gdk_pixbuf__jpeg_image_load;
752   module->begin_load = gdk_pixbuf__jpeg_image_begin_load;
753   module->stop_load = gdk_pixbuf__jpeg_image_stop_load;
754   module->load_increment = gdk_pixbuf__jpeg_image_load_increment;
755   module->save = gdk_pixbuf__jpeg_image_save;
756 }