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