1 /* This code is based on the jpeg saving code from gdk-pixbuf. Full copyright
2 * notice is given in the following:
3 */
4 /* GdkPixbuf library - JPEG image loader
5 *
6 * Copyright (C) 1999 Michael Zucchi
7 * Copyright (C) 1999 The Free Software Foundation
8 *
9 * Progressive loading code Copyright (C) 1999 Red Hat, Inc.
10 *
11 * Authors: Michael Zucchi <zucchi@zedzone.mmc.com.au>
12 * Federico Mena-Quintero <federico@gimp.org>
13 * Michael Fulbright <drmike@redhat.com>
14 *
15 * This library is free software; you can redistribute it and/or
16 * modify it under the terms of the GNU Lesser General Public
17 * License as published by the Free Software Foundation; either
18 * version 2 of the License, or (at your option) any later version.
19 *
20 * This library is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * Lesser General Public License for more details.
24 *
25 * You should have received a copy of the GNU General Public License along
26 * with this program; if not, write to the Free Software Foundation, Inc.,
27 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include "eog-image-jpeg.h"
35 #include "eog-image-private.h"
36
37 #ifdef HAVE_JPEG
38
39 #include <sys/types.h>
40 #include <unistd.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <setjmp.h>
45 #include <jpeglib.h>
46 #include <jerror.h>
47 #include "transupp.h"
48 #include <glib.h>
49 #include <gdk-pixbuf/gdk-pixbuf.h>
50 #include <glib/gi18n.h>
51 #ifdef HAVE_EXIF
52 #include <libexif/exif-data.h>
53 #endif
54
55 #ifdef G_OS_WIN32
56 #define sigjmp_buf jmp_buf
57 #define sigsetjmp(env, savesigs) setjmp (env)
58 #define siglongjmp longjmp
59 #endif
60
61 typedef enum {
62 EOG_SAVE_NONE,
63 EOG_SAVE_JPEG_AS_JPEG,
64 EOG_SAVE_ANY_AS_JPEG
65 } EogJpegSaveMethod;
66
67 /* error handler data */
68 struct error_handler_data {
69 struct jpeg_error_mgr pub;
70 sigjmp_buf setjmp_buffer;
71 GError **error;
72 char *filename;
73 };
74
75
76 static void
fatal_error_handler(j_common_ptr cinfo)77 fatal_error_handler (j_common_ptr cinfo)
78 {
79 struct error_handler_data *errmgr;
80 char buffer[JMSG_LENGTH_MAX];
81
82 errmgr = (struct error_handler_data *) cinfo->err;
83
84 /* Create the message */
85 (* cinfo->err->format_message) (cinfo, buffer);
86
87 /* broken check for *error == NULL for robustness against
88 * crappy JPEG library
89 */
90 if (errmgr->error && *errmgr->error == NULL) {
91 g_set_error (errmgr->error,
92 0,
93 0,
94 "Error interpreting JPEG image file: %s\n\n%s",
95 g_path_get_basename (errmgr->filename),
96 buffer);
97 }
98
99 siglongjmp (errmgr->setjmp_buffer, 1);
100
101 g_assert_not_reached ();
102 }
103
104
105 static void
output_message_handler(j_common_ptr cinfo)106 output_message_handler (j_common_ptr cinfo)
107 {
108 /* This method keeps libjpeg from dumping crap to stderr */
109 /* do nothing */
110 }
111
112 static void
init_transform_info(EogImage * image,jpeg_transform_info * info)113 init_transform_info (EogImage *image, jpeg_transform_info *info)
114 {
115 EogImagePrivate *priv;
116 EogTransform *composition = NULL;
117 EogTransformType transformation;
118 JXFORM_CODE trans_code = JXFORM_NONE;
119
120 g_return_if_fail (EOG_IS_IMAGE (image));
121
122 memset (info, 0x0, sizeof (jpeg_transform_info));
123
124 priv = image->priv;
125
126 if (priv->trans != NULL && priv->trans_autorotate != NULL) {
127 composition = eog_transform_compose (priv->trans,
128 priv->trans_autorotate);
129 } else if (priv->trans != NULL) {
130 composition = g_object_ref (priv->trans);
131 } else if (priv->trans_autorotate != NULL) {
132 composition = g_object_ref (priv->trans_autorotate);
133 }
134
135 if (composition != NULL) {
136 transformation = eog_transform_get_transform_type (composition);
137
138 switch (transformation) {
139 case EOG_TRANSFORM_ROT_90:
140 trans_code = JXFORM_ROT_90;
141 break;
142 case EOG_TRANSFORM_ROT_270:
143 trans_code = JXFORM_ROT_270;
144 break;
145 case EOG_TRANSFORM_ROT_180:
146 trans_code = JXFORM_ROT_180;
147 break;
148 case EOG_TRANSFORM_FLIP_HORIZONTAL:
149 trans_code = JXFORM_FLIP_H;
150 break;
151 case EOG_TRANSFORM_FLIP_VERTICAL:
152 trans_code = JXFORM_FLIP_V;
153 break;
154 case EOG_TRANSFORM_TRANSPOSE:
155 trans_code = JXFORM_TRANSPOSE;
156 break;
157 case EOG_TRANSFORM_TRANSVERSE:
158 trans_code = JXFORM_TRANSVERSE;
159 break;
160 default:
161 g_warning("EogTransformType not supported!");
162 /* Fallthrough intended here. */
163 case EOG_TRANSFORM_NONE:
164 trans_code = JXFORM_NONE;
165 break;
166 }
167 }
168
169 info->transform = trans_code;
170 info->trim = FALSE;
171 #if JPEG_LIB_VERSION >= 80
172 info->crop = FALSE;
173 #endif
174 info->force_grayscale = FALSE;
175
176 g_object_unref (composition);
177 }
178
179 static gboolean
_save_jpeg_as_jpeg(EogImage * image,const char * file,EogImageSaveInfo * source,EogImageSaveInfo * target,GError ** error)180 _save_jpeg_as_jpeg (EogImage *image, const char *file, EogImageSaveInfo *source,
181 EogImageSaveInfo *target, GError **error)
182 {
183 struct jpeg_decompress_struct srcinfo;
184 struct jpeg_compress_struct dstinfo;
185 struct error_handler_data jsrcerr, jdsterr;
186 jpeg_transform_info transformoption;
187 jvirt_barray_ptr *src_coef_arrays;
188 jvirt_barray_ptr *dst_coef_arrays;
189 FILE *output_file;
190 FILE *input_file;
191 EogImagePrivate *priv;
192 gchar *infile_uri;
193
194 g_return_val_if_fail (EOG_IS_IMAGE (image), FALSE);
195 g_return_val_if_fail (EOG_IMAGE (image)->priv->file != NULL, FALSE);
196
197 priv = image->priv;
198
199 init_transform_info (image, &transformoption);
200
201 /* Initialize the JPEG decompression object with default error
202 * handling. */
203 jsrcerr.filename = g_file_get_path (priv->file);
204 srcinfo.err = jpeg_std_error (&(jsrcerr.pub));
205 jsrcerr.pub.error_exit = fatal_error_handler;
206 jsrcerr.pub.output_message = output_message_handler;
207 jsrcerr.error = error;
208
209 jpeg_create_decompress (&srcinfo);
210
211 /* Initialize the JPEG compression object with default error
212 * handling. */
213 jdsterr.filename = (char *) file;
214 dstinfo.err = jpeg_std_error (&(jdsterr.pub));
215 jdsterr.pub.error_exit = fatal_error_handler;
216 jdsterr.pub.output_message = output_message_handler;
217 jdsterr.error = error;
218
219 jpeg_create_compress (&dstinfo);
220
221 dstinfo.err->trace_level = 0;
222 dstinfo.arith_code = FALSE;
223 dstinfo.optimize_coding = FALSE;
224
225 jsrcerr.pub.trace_level = jdsterr.pub.trace_level;
226 srcinfo.mem->max_memory_to_use = dstinfo.mem->max_memory_to_use;
227
228 /* Open the output file. */
229 /* FIXME: Make this a GIO aware input manager */
230 infile_uri = g_file_get_path (priv->file);
231 input_file = fopen (infile_uri, "rb");
232 if (input_file == NULL) {
233 g_warning ("Input file not openable: %s\n", infile_uri);
234 g_free (jsrcerr.filename);
235 g_free (infile_uri);
236 return FALSE;
237 }
238 g_free (infile_uri);
239
240 output_file = fopen (file, "wb");
241 if (output_file == NULL) {
242 g_warning ("Output file not openable: %s\n", file);
243 fclose (input_file);
244 g_free (jsrcerr.filename);
245 return FALSE;
246 }
247
248 if (sigsetjmp (jsrcerr.setjmp_buffer, 1)) {
249 fclose (output_file);
250 fclose (input_file);
251 jpeg_destroy_compress (&dstinfo);
252 jpeg_destroy_decompress (&srcinfo);
253 g_free (jsrcerr.filename);
254 return FALSE;
255 }
256
257 if (sigsetjmp (jdsterr.setjmp_buffer, 1)) {
258 fclose (output_file);
259 fclose (input_file);
260 jpeg_destroy_compress (&dstinfo);
261 jpeg_destroy_decompress (&srcinfo);
262 g_free (jsrcerr.filename);
263 return FALSE;
264 }
265
266 /* Specify data source for decompression */
267 jpeg_stdio_src (&srcinfo, input_file);
268
269 /* Enable saving of extra markers that we want to copy */
270 jcopy_markers_setup (&srcinfo, JCOPYOPT_DEFAULT);
271
272 /* Read file header */
273 (void) jpeg_read_header (&srcinfo, TRUE);
274
275 /* Any space needed by a transform option must be requested before
276 * jpeg_read_coefficients so that memory allocation will be done right.
277 */
278 jtransform_request_workspace (&srcinfo, &transformoption);
279
280 /* Read source file as DCT coefficients */
281 src_coef_arrays = jpeg_read_coefficients (&srcinfo);
282
283 /* Initialize destination compression parameters from source values */
284 jpeg_copy_critical_parameters (&srcinfo, &dstinfo);
285
286 /* Adjust destination parameters if required by transform options;
287 * also find out which set of coefficient arrays will hold the output.
288 */
289 dst_coef_arrays = jtransform_adjust_parameters (&srcinfo,
290 &dstinfo,
291 src_coef_arrays,
292 &transformoption);
293
294 /* Specify data destination for compression */
295 jpeg_stdio_dest (&dstinfo, output_file);
296
297 /* Start compressor (note no image data is actually written here) */
298 jpeg_write_coefficients (&dstinfo, dst_coef_arrays);
299
300 /* handle EXIF/IPTC data explicitly */
301 #ifdef HAVE_EXIF
302 /* exif_chunk and exif are mutually exclusive, this is what we assure here */
303 g_assert (priv->exif_chunk == NULL);
304 if (priv->exif != NULL)
305 {
306 unsigned char *exif_buf;
307 unsigned int exif_buf_len;
308
309 exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len);
310 jpeg_write_marker (&dstinfo, JPEG_APP0+1, exif_buf, exif_buf_len);
311 g_free (exif_buf);
312 }
313 #else
314 if (priv->exif_chunk != NULL) {
315 jpeg_write_marker (&dstinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len);
316 }
317 #endif
318 /* FIXME: Consider IPTC data too */
319
320 /* Copy to the output file any extra markers that we want to
321 * preserve */
322 jcopy_markers_execute (&srcinfo, &dstinfo, JCOPYOPT_DEFAULT);
323
324 /* Execute image transformation, if any */
325 jtransform_execute_transformation (&srcinfo,
326 &dstinfo,
327 src_coef_arrays,
328 &transformoption);
329
330 /* Finish compression and release memory */
331 jpeg_finish_compress (&dstinfo);
332 jpeg_destroy_compress (&dstinfo);
333 (void) jpeg_finish_decompress (&srcinfo);
334 jpeg_destroy_decompress (&srcinfo);
335 g_free (jsrcerr.filename);
336
337 /* Close files */
338 fclose (input_file);
339 fclose (output_file);
340
341 return TRUE;
342 }
343
344 static gboolean
_save_any_as_jpeg(EogImage * image,const char * file,EogImageSaveInfo * source,EogImageSaveInfo * target,GError ** error)345 _save_any_as_jpeg (EogImage *image, const char *file, EogImageSaveInfo *source,
346 EogImageSaveInfo *target, GError **error)
347 {
348 EogImagePrivate *priv;
349 GdkPixbuf *pixbuf;
350 struct jpeg_compress_struct cinfo;
351 guchar *buf = NULL;
352 guchar *ptr;
353 guchar *pixels = NULL;
354 JSAMPROW *jbuf;
355 int y = 0;
356 volatile int quality = 75; /* default; must be between 0 and 100 */
357 int i, j;
358 int w, h = 0;
359 int rowstride = 0;
360 FILE *outfile;
361 struct error_handler_data jerr;
362
363 g_return_val_if_fail (EOG_IS_IMAGE (image), FALSE);
364 g_return_val_if_fail (EOG_IMAGE (image)->priv->image != NULL, FALSE);
365
366 priv = image->priv;
367 pixbuf = priv->image;
368
369 outfile = fopen (file, "wb");
370 if (outfile == NULL) {
371 g_set_error (error, /* FIXME: Better error message */
372 GDK_PIXBUF_ERROR,
373 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
374 _("Couldn’t create temporary file for saving: %s"),
375 file);
376 return FALSE;
377 }
378
379 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
380 w = gdk_pixbuf_get_width (pixbuf);
381 h = gdk_pixbuf_get_height (pixbuf);
382
383 /* no image data? abort */
384 pixels = gdk_pixbuf_get_pixels (pixbuf);
385 g_return_val_if_fail (pixels != NULL, FALSE);
386
387 /* allocate a small buffer to convert image data */
388 buf = g_try_malloc (w * 3 * sizeof (guchar));
389 if (!buf) {
390 g_set_error (error,
391 GDK_PIXBUF_ERROR,
392 GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
393 _("Couldn’t allocate memory for loading JPEG file"));
394 fclose (outfile);
395 return FALSE;
396 }
397
398 /* set up error handling */
399 jerr.filename = (char *) file;
400 cinfo.err = jpeg_std_error (&(jerr.pub));
401 jerr.pub.error_exit = fatal_error_handler;
402 jerr.pub.output_message = output_message_handler;
403 jerr.error = error;
404
405 /* setup compress params */
406 jpeg_create_compress (&cinfo);
407 jpeg_stdio_dest (&cinfo, outfile);
408 cinfo.image_width = w;
409 cinfo.image_height = h;
410 cinfo.input_components = 3;
411 cinfo.in_color_space = JCS_RGB;
412
413 /* error exit routine */
414 if (sigsetjmp (jerr.setjmp_buffer, 1)) {
415 g_free (buf);
416 fclose (outfile);
417 jpeg_destroy_compress (&cinfo);
418 return FALSE;
419 }
420
421 /* set desired jpeg quality if available */
422 if (target != NULL && target->jpeg_quality >= 0.0) {
423 quality = (int) MIN (target->jpeg_quality, 1.0) * 100;
424 }
425
426 /* set up jepg compression parameters */
427 jpeg_set_defaults (&cinfo);
428 jpeg_set_quality (&cinfo, quality, TRUE);
429 jpeg_start_compress (&cinfo, TRUE);
430
431 /* write EXIF/IPTC data explicitly */
432 #ifdef HAVE_EXIF
433 /* exif_chunk and exif are mutually exclusive, this is what we assure here */
434 g_assert (priv->exif_chunk == NULL);
435 if (priv->exif != NULL)
436 {
437 unsigned char *exif_buf;
438 unsigned int exif_buf_len;
439
440 exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len);
441 jpeg_write_marker (&cinfo, 0xe1, exif_buf, exif_buf_len);
442 g_free (exif_buf);
443 }
444 #else
445 if (priv->exif_chunk != NULL) {
446 jpeg_write_marker (&cinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len);
447 }
448 #endif
449 /* FIXME: Consider IPTC data too */
450
451 /* get the start pointer */
452 ptr = pixels;
453 /* go one scanline at a time... and save */
454 i = 0;
455 while (cinfo.next_scanline < cinfo.image_height) {
456 /* convert scanline from ARGB to RGB packed */
457 for (j = 0; j < w; j++)
458 memcpy (&(buf[j*3]), &(ptr[i*rowstride + j*(rowstride/w)]), 3);
459
460 /* write scanline */
461 jbuf = (JSAMPROW *)(&buf);
462 jpeg_write_scanlines (&cinfo, jbuf, 1);
463 i++;
464 y++;
465
466 }
467
468 /* finish off */
469 jpeg_finish_compress (&cinfo);
470 jpeg_destroy_compress(&cinfo);
471 g_free (buf);
472
473 fclose (outfile);
474
475 return TRUE;
476 }
477
478 gboolean
eog_image_jpeg_save_file(EogImage * image,const char * file,EogImageSaveInfo * source,EogImageSaveInfo * target,GError ** error)479 eog_image_jpeg_save_file (EogImage *image, const char *file,
480 EogImageSaveInfo *source, EogImageSaveInfo *target,
481 GError **error)
482 {
483 EogJpegSaveMethod method = EOG_SAVE_NONE;
484 gboolean source_is_jpeg = FALSE;
485 gboolean target_is_jpeg = FALSE;
486 gboolean result;
487
488 g_return_val_if_fail (source != NULL, FALSE);
489
490 source_is_jpeg = !g_ascii_strcasecmp (source->format, EOG_FILE_FORMAT_JPEG);
491
492 /* determine which method should be used for saving */
493 if (target == NULL) {
494 if (source_is_jpeg) {
495 method = EOG_SAVE_JPEG_AS_JPEG;
496 }
497 }
498 else {
499 target_is_jpeg = !g_ascii_strcasecmp (target->format, EOG_FILE_FORMAT_JPEG);
500
501 if (source_is_jpeg && target_is_jpeg) {
502 if (target->jpeg_quality < 0.0) {
503 method = EOG_SAVE_JPEG_AS_JPEG;
504 }
505 else {
506 /* reencoding is required, cause quality is set */
507 method = EOG_SAVE_ANY_AS_JPEG;
508 }
509 }
510 else if (!source_is_jpeg && target_is_jpeg) {
511 method = EOG_SAVE_ANY_AS_JPEG;
512 }
513 }
514
515 switch (method) {
516 case EOG_SAVE_JPEG_AS_JPEG:
517 result = _save_jpeg_as_jpeg (image, file, source, target, error);
518 break;
519 case EOG_SAVE_ANY_AS_JPEG:
520 result = _save_any_as_jpeg (image, file, source, target, error);
521 break;
522 default:
523 result = FALSE;
524 }
525
526 return result;
527 }
528 #endif
529