|
Packit |
a4058c |
/* -*- mode: C; c-file-style: "linux" -*- */
|
|
Packit |
a4058c |
/* GdkPixbuf library - JPEG image loader
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* Copyright (C) 1999 Michael Zucchi
|
|
Packit |
a4058c |
* Copyright (C) 1999 The Free Software Foundation
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* Progressive loading code Copyright (C) 1999 Red Hat, Inc.
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* Authors: Michael Zucchi <zucchi@zedzone.mmc.com.au>
|
|
Packit |
a4058c |
* Federico Mena-Quintero <federico@gimp.org>
|
|
Packit |
a4058c |
* Michael Fulbright <drmike@redhat.com>
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* This library is free software; you can redistribute it and/or
|
|
Packit |
a4058c |
* modify it under the terms of the GNU Lesser General Public
|
|
Packit |
a4058c |
* License as published by the Free Software Foundation; either
|
|
Packit |
a4058c |
* version 2 of the License, or (at your option) any later version.
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* This library is distributed in the hope that it will be useful,
|
|
Packit |
a4058c |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
a4058c |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Packit |
a4058c |
* Lesser General Public License for more details.
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* You should have received a copy of the GNU Lesser General Public
|
|
Packit |
a4058c |
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#include "config.h"
|
|
Packit |
a4058c |
#include <stdio.h>
|
|
Packit |
a4058c |
#include <stdlib.h>
|
|
Packit |
a4058c |
#include <string.h>
|
|
Packit |
a4058c |
#include <setjmp.h>
|
|
Packit |
a4058c |
#include <jpeglib.h>
|
|
Packit |
a4058c |
#include <jerror.h>
|
|
Packit |
a4058c |
#include <math.h>
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#include "gdk-pixbuf-private.h"
|
|
Packit |
a4058c |
#include "fallback-c89.c"
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#ifndef HAVE_SIGSETJMP
|
|
Packit |
a4058c |
#define sigjmp_buf jmp_buf
|
|
Packit |
a4058c |
#define sigsetjmp(jb, x) setjmp(jb)
|
|
Packit |
a4058c |
#define siglongjmp longjmp
|
|
Packit |
a4058c |
#endif
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* we are a "source manager" as far as libjpeg is concerned */
|
|
Packit |
a4058c |
#define JPEG_PROG_BUF_SIZE 65536
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
typedef struct {
|
|
Packit |
a4058c |
struct jpeg_source_mgr pub; /* public fields */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
JOCTET buffer[JPEG_PROG_BUF_SIZE]; /* start of buffer */
|
|
Packit |
a4058c |
long skip_next; /* number of bytes to skip next read */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
} my_source_mgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
typedef my_source_mgr * my_src_ptr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* error handler data */
|
|
Packit |
a4058c |
struct error_handler_data {
|
|
Packit |
a4058c |
struct jpeg_error_mgr pub;
|
|
Packit |
a4058c |
sigjmp_buf setjmp_buffer;
|
|
Packit |
a4058c |
GError **error;
|
|
Packit |
a4058c |
};
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* progressive loader context */
|
|
Packit |
a4058c |
typedef struct {
|
|
Packit |
a4058c |
GdkPixbufModuleSizeFunc size_func;
|
|
Packit |
a4058c |
GdkPixbufModuleUpdatedFunc updated_func;
|
|
Packit |
a4058c |
GdkPixbufModulePreparedFunc prepared_func;
|
|
Packit |
a4058c |
gpointer user_data;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
GdkPixbuf *pixbuf;
|
|
Packit |
a4058c |
guchar *dptr; /* current position in pixbuf */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
gboolean did_prescan; /* are we in image data yet? */
|
|
Packit |
a4058c |
gboolean got_header; /* have we loaded jpeg header? */
|
|
Packit |
a4058c |
gboolean src_initialized;/* TRUE when jpeg lib initialized */
|
|
Packit |
a4058c |
gboolean in_output; /* did we get suspended in an output pass? */
|
|
Packit |
a4058c |
struct jpeg_decompress_struct cinfo;
|
|
Packit |
a4058c |
struct error_handler_data jerr;
|
|
Packit |
a4058c |
} JpegProgContext;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* EXIF context */
|
|
Packit |
a4058c |
typedef struct {
|
|
Packit |
a4058c |
gint orientation;
|
|
Packit |
a4058c |
gchar *icc_profile;
|
|
Packit |
a4058c |
gsize icc_profile_size;
|
|
Packit |
a4058c |
gsize icc_profile_size_allocated;
|
|
Packit |
a4058c |
} JpegExifContext;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static GdkPixbuf *gdk_pixbuf__jpeg_image_load (FILE *f, GError **error);
|
|
Packit |
a4058c |
static gpointer gdk_pixbuf__jpeg_image_begin_load (GdkPixbufModuleSizeFunc func0,
|
|
Packit |
a4058c |
GdkPixbufModulePreparedFunc func1,
|
|
Packit |
a4058c |
GdkPixbufModuleUpdatedFunc func2,
|
|
Packit |
a4058c |
gpointer user_data,
|
|
Packit |
a4058c |
GError **error);
|
|
Packit |
a4058c |
static gboolean gdk_pixbuf__jpeg_image_stop_load (gpointer context, GError **error);
|
|
Packit |
a4058c |
static gboolean gdk_pixbuf__jpeg_image_load_increment(gpointer context,
|
|
Packit |
a4058c |
const guchar *buf, guint size,
|
|
Packit |
a4058c |
GError **error);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
fatal_error_handler (j_common_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
struct error_handler_data *errmgr;
|
|
Packit |
a4058c |
char buffer[JMSG_LENGTH_MAX];
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
errmgr = (struct error_handler_data *) cinfo->err;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Create the message */
|
|
Packit |
a4058c |
(* cinfo->err->format_message) (cinfo, buffer);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* broken check for *error == NULL for robustness against
|
|
Packit |
a4058c |
* crappy JPEG library
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
if (errmgr->error && *errmgr->error == NULL) {
|
|
Packit |
a4058c |
g_set_error (errmgr->error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
cinfo->err->msg_code == JERR_OUT_OF_MEMORY
|
|
Packit |
a4058c |
? GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY
|
|
Packit |
a4058c |
: GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
|
|
Packit |
a4058c |
_("Error interpreting JPEG image file (%s)"),
|
|
Packit |
a4058c |
buffer);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
siglongjmp (errmgr->setjmp_buffer, 1);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_assert_not_reached ();
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
output_message_handler (j_common_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
/* This method keeps libjpeg from dumping crap to stderr */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* do nothing */
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* explode gray image data from jpeg library into rgb components in pixbuf */
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
explode_gray_into_buf (struct jpeg_decompress_struct *cinfo,
|
|
Packit |
a4058c |
guchar **lines)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
gint i, j;
|
|
Packit |
a4058c |
guint w;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_return_if_fail (cinfo != NULL);
|
|
Packit |
a4058c |
g_return_if_fail (cinfo->output_components == 1);
|
|
Packit |
a4058c |
g_return_if_fail (cinfo->out_color_space == JCS_GRAYSCALE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Expand grey->colour. Expand from the end of the
|
|
Packit |
a4058c |
* memory down, so we can use the same buffer.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
w = cinfo->output_width;
|
|
Packit |
a4058c |
for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) {
|
|
Packit |
a4058c |
guchar *from, *to;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
from = lines[i] + w - 1;
|
|
Packit |
a4058c |
to = lines[i] + (w - 1) * 3;
|
|
Packit |
a4058c |
for (j = w - 1; j >= 0; j--) {
|
|
Packit |
a4058c |
to[0] = from[0];
|
|
Packit |
a4058c |
to[1] = from[0];
|
|
Packit |
a4058c |
to[2] = from[0];
|
|
Packit |
a4058c |
to -= 3;
|
|
Packit |
a4058c |
from--;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
convert_cmyk_to_rgb (struct jpeg_decompress_struct *cinfo,
|
|
Packit |
a4058c |
guchar **lines)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
gint i, j;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_return_if_fail (cinfo != NULL);
|
|
Packit |
a4058c |
g_return_if_fail (cinfo->output_components == 4);
|
|
Packit |
a4058c |
g_return_if_fail (cinfo->out_color_space == JCS_CMYK);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
for (i = cinfo->rec_outbuf_height - 1; i >= 0; i--) {
|
|
Packit |
a4058c |
guchar *p;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
p = lines[i];
|
|
Packit |
a4058c |
for (j = 0; j < cinfo->output_width; j++) {
|
|
Packit |
a4058c |
int c, m, y, k;
|
|
Packit |
a4058c |
c = p[0];
|
|
Packit |
a4058c |
m = p[1];
|
|
Packit |
a4058c |
y = p[2];
|
|
Packit |
a4058c |
k = p[3];
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* We now assume that all CMYK JPEG files
|
|
Packit |
a4058c |
* use inverted CMYK, as Photoshop does
|
|
Packit |
a4058c |
* See https://bugzilla.gnome.org/show_bug.cgi?id=618096 */
|
|
Packit |
a4058c |
p[0] = k*c / 255;
|
|
Packit |
a4058c |
p[1] = k*m / 255;
|
|
Packit |
a4058c |
p[2] = k*y / 255;
|
|
Packit |
a4058c |
p[3] = 255;
|
|
Packit |
a4058c |
p += 4;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
typedef struct {
|
|
Packit |
a4058c |
struct jpeg_source_mgr pub; /* public fields */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
FILE * infile; /* source stream */
|
|
Packit |
a4058c |
JOCTET * buffer; /* start of buffer */
|
|
Packit |
a4058c |
boolean start_of_file; /* have we gotten any data yet? */
|
|
Packit |
a4058c |
} stdio_source_mgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
typedef stdio_source_mgr * stdio_src_ptr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
stdio_init_source (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
stdio_src_ptr src = (stdio_src_ptr)cinfo->src;
|
|
Packit |
a4058c |
src->start_of_file = FALSE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static boolean
|
|
Packit |
a4058c |
stdio_fill_input_buffer (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
stdio_src_ptr src = (stdio_src_ptr) cinfo->src;
|
|
Packit |
a4058c |
size_t nbytes;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
nbytes = fread (src->buffer, 1, JPEG_PROG_BUF_SIZE, src->infile);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (nbytes <= 0) {
|
|
Packit |
a4058c |
#if 0
|
|
Packit |
a4058c |
if (src->start_of_file) /* Treat empty input file as fatal error */
|
|
Packit |
a4058c |
ERREXIT(cinfo, JERR_INPUT_EMPTY);
|
|
Packit |
a4058c |
WARNMS(cinfo, JWRN_JPEG_EOF);
|
|
Packit |
a4058c |
#endif
|
|
Packit |
a4058c |
/* Insert a fake EOI marker */
|
|
Packit |
a4058c |
src->buffer[0] = (JOCTET) 0xFF;
|
|
Packit |
a4058c |
src->buffer[1] = (JOCTET) JPEG_EOI;
|
|
Packit |
a4058c |
nbytes = 2;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src->pub.next_input_byte = src->buffer;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer = nbytes;
|
|
Packit |
a4058c |
src->start_of_file = FALSE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return TRUE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
stdio_skip_input_data (j_decompress_ptr cinfo, long num_bytes)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
stdio_src_ptr src = (stdio_src_ptr) cinfo->src;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (num_bytes > 0) {
|
|
Packit |
a4058c |
while (num_bytes > (long) src->pub.bytes_in_buffer) {
|
|
Packit |
a4058c |
num_bytes -= (long) src->pub.bytes_in_buffer;
|
|
Packit |
a4058c |
(void)stdio_fill_input_buffer(cinfo);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
src->pub.next_input_byte += (size_t) num_bytes;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer -= (size_t) num_bytes;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
stdio_term_source (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gchar *
|
|
Packit |
a4058c |
colorspace_name (const J_COLOR_SPACE jpeg_color_space)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
switch (jpeg_color_space) {
|
|
Packit |
a4058c |
case JCS_UNKNOWN: return "UNKNOWN";
|
|
Packit |
a4058c |
case JCS_GRAYSCALE: return "GRAYSCALE";
|
|
Packit |
a4058c |
case JCS_RGB: return "RGB";
|
|
Packit |
a4058c |
case JCS_YCbCr: return "YCbCr";
|
|
Packit |
a4058c |
case JCS_CMYK: return "CMYK";
|
|
Packit |
a4058c |
case JCS_YCCK: return "YCCK";
|
|
Packit |
a4058c |
default: return "invalid";
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#define DE_ENDIAN16(val) endian == G_BIG_ENDIAN ? GUINT16_FROM_BE(val) : GUINT16_FROM_LE(val)
|
|
Packit |
a4058c |
#define DE_ENDIAN32(val) endian == G_BIG_ENDIAN ? GUINT32_FROM_BE(val) : GUINT32_FROM_LE(val)
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#define ENDIAN16_IT(val) endian == G_BIG_ENDIAN ? GUINT16_TO_BE(val) : GUINT16_TO_LE(val)
|
|
Packit |
a4058c |
#define ENDIAN32_IT(val) endian == G_BIG_ENDIAN ? GUINT32_TO_BE(val) : GUINT32_TO_LE(val)
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static unsigned short de_get16(void *ptr, guint endian)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
unsigned short val;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
memcpy(&val, ptr, sizeof(val));
|
|
Packit |
a4058c |
val = DE_ENDIAN16(val);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return val;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static unsigned int de_get32(void *ptr, guint endian)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
unsigned int val;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
memcpy(&val, ptr, sizeof(val));
|
|
Packit |
a4058c |
val = DE_ENDIAN32(val);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return val;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* application specific data segment */
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
jpeg_parse_exif_app2_segment (JpegExifContext *context, jpeg_saved_marker_ptr marker)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
guint ret = FALSE;
|
|
Packit |
a4058c |
guint sequence_number;
|
|
Packit |
a4058c |
guint number_of_chunks;
|
|
Packit |
a4058c |
guint chunk_size;
|
|
Packit |
a4058c |
guint offset;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* do we have enough data? */
|
|
Packit |
a4058c |
if (marker->data_length < 16)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* unique identification string */
|
|
Packit |
a4058c |
if (memcmp (marker->data, "ICC_PROFILE\0", 12) != 0)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* get data about this segment */
|
|
Packit |
a4058c |
sequence_number = marker->data[12];
|
|
Packit |
a4058c |
number_of_chunks = marker->data[13];
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* this is invalid, the base offset is 1 */
|
|
Packit |
a4058c |
if (sequence_number == 0)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* this is invalid, the base offset is 1 */
|
|
Packit |
a4058c |
if (sequence_number > number_of_chunks)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* size includes the id (12 bytes), length field (1 byte), and sequence field (1 byte) */
|
|
Packit |
a4058c |
chunk_size = marker->data_length - 14;
|
|
Packit |
a4058c |
offset = (sequence_number - 1) * 0xffef;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Deal with the trivial profile (99% of images) to avoid allocating
|
|
Packit |
a4058c |
* 64kb when we might only use a few kb. */
|
|
Packit |
a4058c |
if (number_of_chunks == 1) {
|
|
Packit |
a4058c |
if (context->icc_profile_size_allocated > 0)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
context->icc_profile_size = chunk_size;
|
|
Packit |
a4058c |
context->icc_profile_size_allocated = chunk_size;
|
|
Packit |
a4058c |
context->icc_profile = g_new (gchar, chunk_size);
|
|
Packit |
a4058c |
/* copy the segment data to the profile space */
|
|
Packit |
a4058c |
memcpy (context->icc_profile, marker->data + 14, chunk_size);
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* There is no promise the APP2 segments are going to be in order, so we
|
|
Packit |
a4058c |
* have to allocate a huge swathe of memory and fill in the gaps when
|
|
Packit |
a4058c |
* (if) we get the segment.
|
|
Packit |
a4058c |
* Theoretically this could be as much as 16Mb, but display profiles are
|
|
Packit |
a4058c |
* vary rarely above 100kb, and printer profiles are usually less than
|
|
Packit |
a4058c |
* 2Mb */
|
|
Packit |
a4058c |
if (context->icc_profile_size_allocated == 0) {
|
|
Packit |
a4058c |
context->icc_profile_size_allocated = number_of_chunks * 0xffff;
|
|
Packit |
a4058c |
context->icc_profile = g_new0 (gchar, number_of_chunks * 0xffff);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check the data will fit in our previously allocated buffer */
|
|
Packit |
a4058c |
if (offset + chunk_size > context->icc_profile_size_allocated)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* copy the segment data to the profile space */
|
|
Packit |
a4058c |
memcpy (context->icc_profile + offset, marker->data + 14, chunk_size);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* it's now this big plus the new data we've just copied */
|
|
Packit |
a4058c |
context->icc_profile_size += chunk_size;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* success */
|
|
Packit |
a4058c |
ret = TRUE;
|
|
Packit |
a4058c |
out:
|
|
Packit |
a4058c |
return ret;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
jpeg_parse_exif_app1 (JpegExifContext *context, jpeg_saved_marker_ptr marker)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
guint i;
|
|
Packit |
a4058c |
guint ret = FALSE;
|
|
Packit |
a4058c |
guint offset;
|
|
Packit |
a4058c |
guint tags; /* number of tags in current ifd */
|
|
Packit |
a4058c |
guint endian = 0; /* detected endian of data */
|
|
Packit |
a4058c |
const char leth[] = {0x49, 0x49, 0x2a, 0x00}; // Little endian TIFF header
|
|
Packit |
a4058c |
const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; // Big endian TIFF header
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* do we have enough data? */
|
|
Packit |
a4058c |
if (marker->data_length < 4)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* unique identification string */
|
|
Packit |
a4058c |
if (memcmp (marker->data, "Exif", 4) != 0)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* do we have enough data? */
|
|
Packit |
a4058c |
if (marker->data_length < 32)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Just skip data until TIFF header - it should be within 16 bytes from marker start.
|
|
Packit |
a4058c |
Normal structure relative to APP1 marker -
|
|
Packit |
a4058c |
0x0000: APP1 marker entry = 2 bytes
|
|
Packit |
a4058c |
0x0002: APP1 length entry = 2 bytes
|
|
Packit |
a4058c |
0x0004: Exif Identifier entry = 6 bytes
|
|
Packit |
a4058c |
0x000A: Start of TIFF header (Byte order entry) - 4 bytes
|
|
Packit |
a4058c |
- This is what we look for, to determine endianess.
|
|
Packit |
a4058c |
0x000E: 0th IFD offset pointer - 4 bytes
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
marker->data points to the first data after the APP1 marker
|
|
Packit |
a4058c |
and length entries, which is the exif identification string.
|
|
Packit |
a4058c |
The TIFF header should thus normally be found at i=6, below,
|
|
Packit |
a4058c |
and the pointer to IFD0 will be at 6+4 = 10.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
for (i=0; i<16; i++) {
|
|
Packit |
a4058c |
/* little endian TIFF header */
|
|
Packit |
a4058c |
if (memcmp (&marker->data[i], leth, 4) == 0) {
|
|
Packit |
a4058c |
endian = G_LITTLE_ENDIAN;
|
|
Packit |
a4058c |
ret = TRUE;
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* big endian TIFF header */
|
|
Packit |
a4058c |
if (memcmp (&marker->data[i], beth, 4) == 0) {
|
|
Packit |
a4058c |
endian = G_BIG_ENDIAN;
|
|
Packit |
a4058c |
ret = TRUE;
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* could not find header */
|
|
Packit |
a4058c |
if (!ret)
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* read out the offset pointer to IFD0 */
|
|
Packit |
a4058c |
offset = de_get32(&marker->data[i] + 4, endian);
|
|
Packit |
a4058c |
i = i + offset;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check that we still are within the buffer and can read the tag count */
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
const size_t new_i = i + 2;
|
|
Packit |
a4058c |
if (new_i < i || new_i > marker->data_length) {
|
|
Packit |
a4058c |
ret = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* find out how many tags we have in IFD0. As per the TIFF spec, the first
|
|
Packit |
a4058c |
two bytes of the IFD contain a count of the number of tags. */
|
|
Packit |
a4058c |
tags = de_get16(&marker->data[i], endian);
|
|
Packit |
a4058c |
i = new_i;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check that we still have enough data for all tags to check. The tags
|
|
Packit |
a4058c |
are listed in consecutive 12-byte blocks. The tag ID, type, size, and
|
|
Packit |
a4058c |
a pointer to the actual value, are packed into these 12 byte entries. */
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
const size_t new_i = i + tags * 12;
|
|
Packit |
a4058c |
if (new_i < i || new_i > marker->data_length) {
|
|
Packit |
a4058c |
ret = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check through IFD0 for tags */
|
|
Packit |
a4058c |
while (tags--) {
|
|
Packit |
a4058c |
size_t new_i;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* We check for integer overflow before the loop and
|
|
Packit |
a4058c |
* at the end of each iteration */
|
|
Packit |
a4058c |
guint tag = de_get16(&marker->data[i + 0], endian);
|
|
Packit |
a4058c |
guint type = de_get16(&marker->data[i + 2], endian);
|
|
Packit |
a4058c |
guint count = de_get32(&marker->data[i + 4], endian);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* orientation tag? */
|
|
Packit |
a4058c |
if (tag == 0x112){
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* The orientation field should consist of a single 2-byte integer,
|
|
Packit |
a4058c |
* but might be a signed long.
|
|
Packit |
a4058c |
* Values of types smaller than 4 bytes are stored directly in the
|
|
Packit |
a4058c |
* Value Offset field */
|
|
Packit |
a4058c |
if (type == 0x3 && count == 1) {
|
|
Packit |
a4058c |
guint short_value = de_get16(&marker->data[i + 8], endian);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->orientation = short_value <= 8 ? short_value : 0;
|
|
Packit |
a4058c |
} else if (type == 0x9 && count == 1) {
|
|
Packit |
a4058c |
guint long_value = de_get32(&marker->data[i + 8], endian);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->orientation = long_value <= 8 ? long_value : 0;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
/* move the pointer to the next 12-byte tag field. */
|
|
Packit |
a4058c |
new_i = i + 12;
|
|
Packit |
a4058c |
if (new_i < i || new_i > marker->data_length) {
|
|
Packit |
a4058c |
ret = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
i = new_i;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
out:
|
|
Packit |
a4058c |
return ret;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
jpeg_parse_exif (JpegExifContext *context, j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
jpeg_saved_marker_ptr cmarker;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check for interesting Exif markers */
|
|
Packit |
a4058c |
cmarker = cinfo->marker_list;
|
|
Packit |
a4058c |
while (cmarker != NULL) {
|
|
Packit |
a4058c |
if (cmarker->marker == JPEG_APP0+1)
|
|
Packit |
a4058c |
jpeg_parse_exif_app1 (context, cmarker);
|
|
Packit |
a4058c |
else if (cmarker->marker == JPEG_APP0+2)
|
|
Packit |
a4058c |
jpeg_parse_exif_app2_segment (context, cmarker);
|
|
Packit |
a4058c |
cmarker = cmarker->next;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gchar *
|
|
Packit |
a4058c |
jpeg_get_comment (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
jpeg_saved_marker_ptr cmarker;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
cmarker = cinfo->marker_list;
|
|
Packit |
a4058c |
while (cmarker != NULL) {
|
|
Packit |
a4058c |
if (cmarker->marker == JPEG_COM)
|
|
Packit |
a4058c |
return g_strndup ((const gchar *) cmarker->data, cmarker->data_length);
|
|
Packit |
a4058c |
cmarker = cmarker->next;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return NULL;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
jpeg_destroy_exif_context (JpegExifContext *context)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
g_free (context->icc_profile);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Shared library entry point */
|
|
Packit |
a4058c |
static GdkPixbuf *
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_load (FILE *f, GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
gint i;
|
|
Packit |
a4058c |
char otag_str[5];
|
|
Packit |
a4058c |
char *density_str;
|
|
Packit |
a4058c |
GdkPixbuf * volatile pixbuf = NULL;
|
|
Packit |
a4058c |
guchar *dptr;
|
|
Packit |
a4058c |
guchar *lines[4]; /* Used to expand rows, via rec_outbuf_height,
|
|
Packit |
a4058c |
* from the header file:
|
|
Packit |
a4058c |
* " Usually rec_outbuf_height will be 1 or 2,
|
|
Packit |
a4058c |
* at most 4."
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
guchar **lptr;
|
|
Packit |
a4058c |
struct jpeg_decompress_struct cinfo;
|
|
Packit |
a4058c |
struct error_handler_data jerr;
|
|
Packit |
a4058c |
stdio_src_ptr src;
|
|
Packit |
a4058c |
gchar *icc_profile_base64;
|
|
Packit |
a4058c |
gchar *comment;
|
|
Packit |
a4058c |
JpegExifContext exif_context = { 0, };
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* setup error handler */
|
|
Packit |
a4058c |
cinfo.err = jpeg_std_error (&jerr.pub);
|
|
Packit |
a4058c |
jerr.pub.error_exit = fatal_error_handler;
|
|
Packit |
a4058c |
jerr.pub.output_message = output_message_handler;
|
|
Packit |
a4058c |
jerr.error = error;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (sigsetjmp (jerr.setjmp_buffer, 1)) {
|
|
Packit |
a4058c |
/* Whoops there was a jpeg error */
|
|
Packit |
a4058c |
if (pixbuf)
|
|
Packit |
a4058c |
g_object_unref (pixbuf);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_destroy_decompress (&cinfo);
|
|
Packit |
a4058c |
jpeg_destroy_exif_context (&exif_context);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* error should have been set by fatal_error_handler () */
|
|
Packit |
a4058c |
return NULL;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* load header, setup */
|
|
Packit |
a4058c |
jpeg_create_decompress (&cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
cinfo.src = (struct jpeg_source_mgr *)
|
|
Packit |
a4058c |
(*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
|
|
Packit |
a4058c |
sizeof (stdio_source_mgr));
|
|
Packit |
a4058c |
src = (stdio_src_ptr) cinfo.src;
|
|
Packit |
a4058c |
src->buffer = (JOCTET *)
|
|
Packit |
a4058c |
(*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
|
|
Packit |
a4058c |
JPEG_PROG_BUF_SIZE * sizeof (JOCTET));
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src->pub.init_source = stdio_init_source;
|
|
Packit |
a4058c |
src->pub.fill_input_buffer = stdio_fill_input_buffer;
|
|
Packit |
a4058c |
src->pub.skip_input_data = stdio_skip_input_data;
|
|
Packit |
a4058c |
src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
|
|
Packit |
a4058c |
src->pub.term_source = stdio_term_source;
|
|
Packit |
a4058c |
src->infile = f;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
|
|
Packit |
a4058c |
src->pub.next_input_byte = NULL; /* until buffer loaded */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_save_markers (&cinfo, JPEG_APP0+1, 0xffff);
|
|
Packit |
a4058c |
jpeg_save_markers (&cinfo, JPEG_APP0+2, 0xffff);
|
|
Packit |
a4058c |
jpeg_save_markers (&cinfo, JPEG_COM, 0xffff);
|
|
Packit |
a4058c |
jpeg_read_header (&cinfo, TRUE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* parse exif data */
|
|
Packit |
a4058c |
jpeg_parse_exif (&exif_context, &cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_start_decompress (&cinfo);
|
|
Packit |
a4058c |
cinfo.do_fancy_upsampling = FALSE;
|
|
Packit |
a4058c |
cinfo.do_block_smoothing = FALSE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
Packit |
a4058c |
cinfo.out_color_components == 4 ? TRUE : FALSE,
|
|
Packit |
a4058c |
8, cinfo.output_width, cinfo.output_height);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (!pixbuf) {
|
|
Packit |
a4058c |
/* broken check for *error == NULL for robustness against
|
|
Packit |
a4058c |
* crappy JPEG library
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
if (error && *error == NULL) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
|
Packit |
a4058c |
_("Insufficient memory to load image, try exiting some applications to free memory"));
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
comment = jpeg_get_comment (&cinfo);
|
|
Packit |
a4058c |
if (comment != NULL) {
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "comment", comment);
|
|
Packit |
a4058c |
g_free (comment);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
switch (cinfo.density_unit) {
|
|
Packit |
a4058c |
case 1:
|
|
Packit |
a4058c |
/* Dots per inch (no conversion required) */
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", cinfo.X_density);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "x-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", cinfo.Y_density);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "y-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case 2:
|
|
Packit |
a4058c |
/* Dots per cm - convert into dpi */
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo.X_density));
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "x-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo.Y_density));
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "y-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* if orientation tag was found */
|
|
Packit |
a4058c |
if (exif_context.orientation != 0) {
|
|
Packit |
a4058c |
g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context.orientation);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "orientation", otag_str);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* if icc profile was found */
|
|
Packit |
a4058c |
if (exif_context.icc_profile != NULL) {
|
|
Packit |
a4058c |
icc_profile_base64 = g_base64_encode ((const guchar *) exif_context.icc_profile, exif_context.icc_profile_size);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
|
|
Packit |
a4058c |
g_free (icc_profile_base64);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
dptr = pixbuf->pixels;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* decompress all the lines, a few at a time */
|
|
Packit |
a4058c |
while (cinfo.output_scanline < cinfo.output_height) {
|
|
Packit |
a4058c |
lptr = lines;
|
|
Packit |
a4058c |
for (i = 0; i < cinfo.rec_outbuf_height; i++) {
|
|
Packit |
a4058c |
*lptr++ = dptr;
|
|
Packit |
a4058c |
dptr += pixbuf->rowstride;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_read_scanlines (&cinfo, lines, cinfo.rec_outbuf_height);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
switch (cinfo.out_color_space) {
|
|
Packit |
a4058c |
case JCS_GRAYSCALE:
|
|
Packit |
a4058c |
explode_gray_into_buf (&cinfo, lines);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case JCS_RGB:
|
|
Packit |
a4058c |
/* do nothing */
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case JCS_CMYK:
|
|
Packit |
a4058c |
convert_cmyk_to_rgb (&cinfo, lines);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
default:
|
|
Packit |
a4058c |
g_clear_object (&pixbuf);
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
|
|
Packit |
a4058c |
_("Unsupported JPEG color space (%s)"),
|
|
Packit |
a4058c |
colorspace_name (cinfo.out_color_space));
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
out:
|
|
Packit |
a4058c |
jpeg_finish_decompress (&cinfo);
|
|
Packit |
a4058c |
jpeg_destroy_decompress (&cinfo);
|
|
Packit |
a4058c |
jpeg_destroy_exif_context (&exif_context);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return pixbuf;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/**** Progressive image loading handling *****/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* these routines required because we are acting as a source manager for */
|
|
Packit |
a4058c |
/* libjpeg. */
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
init_source (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
my_src_ptr src = (my_src_ptr) cinfo->src;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src->skip_next = 0;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
term_source (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
/* XXXX - probably should scream something has happened */
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* for progressive loading (called "I/O Suspension" by libjpeg docs) */
|
|
Packit |
a4058c |
/* we do nothing except return "FALSE" */
|
|
Packit |
a4058c |
static boolean
|
|
Packit |
a4058c |
fill_input_buffer (j_decompress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
return FALSE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
my_src_ptr src = (my_src_ptr) cinfo->src;
|
|
Packit |
a4058c |
long num_can_do;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* move as far as we can into current buffer */
|
|
Packit |
a4058c |
/* then set skip_next to catch the rest */
|
|
Packit |
a4058c |
if (num_bytes > 0) {
|
|
Packit |
a4058c |
num_can_do = MIN (src->pub.bytes_in_buffer, num_bytes);
|
|
Packit |
a4058c |
src->pub.next_input_byte += (size_t) num_can_do;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer -= (size_t) num_can_do;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src->skip_next = num_bytes - num_can_do;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/*
|
|
Packit |
a4058c |
* func - called when we have pixmap created (but no image data)
|
|
Packit |
a4058c |
* user_data - passed as arg 1 to func
|
|
Packit |
a4058c |
* return context (opaque to user)
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gpointer
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_begin_load (GdkPixbufModuleSizeFunc size_func,
|
|
Packit |
a4058c |
GdkPixbufModulePreparedFunc prepared_func,
|
|
Packit |
a4058c |
GdkPixbufModuleUpdatedFunc updated_func,
|
|
Packit |
a4058c |
gpointer user_data,
|
|
Packit |
a4058c |
GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
JpegProgContext *context;
|
|
Packit |
a4058c |
my_source_mgr *src;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context = g_new0 (JpegProgContext, 1);
|
|
Packit |
a4058c |
context->size_func = size_func;
|
|
Packit |
a4058c |
context->prepared_func = prepared_func;
|
|
Packit |
a4058c |
context->updated_func = updated_func;
|
|
Packit |
a4058c |
context->user_data = user_data;
|
|
Packit |
a4058c |
context->pixbuf = NULL;
|
|
Packit |
a4058c |
context->got_header = FALSE;
|
|
Packit |
a4058c |
context->did_prescan = FALSE;
|
|
Packit |
a4058c |
context->src_initialized = FALSE;
|
|
Packit |
a4058c |
context->in_output = FALSE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* From jpeglib.h: "NB: you must set up the error-manager
|
|
Packit |
a4058c |
* BEFORE calling jpeg_create_xxx". */
|
|
Packit |
a4058c |
context->cinfo.err = jpeg_std_error (&context->jerr.pub);
|
|
Packit |
a4058c |
context->jerr.pub.error_exit = fatal_error_handler;
|
|
Packit |
a4058c |
context->jerr.pub.output_message = output_message_handler;
|
|
Packit |
a4058c |
context->jerr.error = error;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
|
|
Packit |
a4058c |
jpeg_destroy_decompress (&context->cinfo);
|
|
Packit |
a4058c |
g_free(context);
|
|
Packit |
a4058c |
/* error should have been set by fatal_error_handler () */
|
|
Packit |
a4058c |
return NULL;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* create libjpeg structures */
|
|
Packit |
a4058c |
jpeg_create_decompress (&context->cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->cinfo.src = (struct jpeg_source_mgr *) g_try_malloc (sizeof (my_source_mgr));
|
|
Packit |
a4058c |
if (!context->cinfo.src) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
|
Packit |
a4058c |
_("Couldn't allocate memory for loading JPEG file"));
|
|
Packit |
a4058c |
return NULL;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
memset (context->cinfo.src, 0, sizeof (my_source_mgr));
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src = (my_src_ptr) context->cinfo.src;
|
|
Packit |
a4058c |
src->pub.init_source = init_source;
|
|
Packit |
a4058c |
src->pub.fill_input_buffer = fill_input_buffer;
|
|
Packit |
a4058c |
src->pub.skip_input_data = skip_input_data;
|
|
Packit |
a4058c |
src->pub.resync_to_restart = jpeg_resync_to_restart;
|
|
Packit |
a4058c |
src->pub.term_source = term_source;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer = 0;
|
|
Packit |
a4058c |
src->pub.next_input_byte = NULL;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->jerr.error = NULL;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return (gpointer) context;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/*
|
|
Packit |
a4058c |
* context - returned from image_begin_load
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* free context, unref gdk_pixbuf
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_stop_load (gpointer data, GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
JpegProgContext *context = (JpegProgContext *) data;
|
|
Packit |
a4058c |
gboolean retval;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_return_val_if_fail (context != NULL, TRUE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* FIXME this thing needs to report errors if
|
|
Packit |
a4058c |
* we have unused image data
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (context->pixbuf)
|
|
Packit |
a4058c |
g_object_unref (context->pixbuf);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* if we have an error? */
|
|
Packit |
a4058c |
context->jerr.error = error;
|
|
Packit |
a4058c |
if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
jpeg_finish_decompress (&context->cinfo);
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_destroy_decompress (&context->cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (context->cinfo.src) {
|
|
Packit |
a4058c |
my_src_ptr src = (my_src_ptr) context->cinfo.src;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_free (src);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_free (context);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return retval;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_load_lines (JpegProgContext *context,
|
|
Packit |
a4058c |
GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
struct jpeg_decompress_struct *cinfo = &context->cinfo;
|
|
Packit |
a4058c |
guchar *lines[4];
|
|
Packit |
a4058c |
guchar **lptr;
|
|
Packit |
a4058c |
guchar *rowptr;
|
|
Packit |
a4058c |
gint nlines, i;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* keep going until we've done all scanlines */
|
|
Packit |
a4058c |
while (cinfo->output_scanline < cinfo->output_height) {
|
|
Packit |
a4058c |
lptr = lines;
|
|
Packit |
a4058c |
rowptr = context->dptr;
|
|
Packit |
a4058c |
for (i=0; i < cinfo->rec_outbuf_height; i++) {
|
|
Packit |
a4058c |
*lptr++ = rowptr;
|
|
Packit |
a4058c |
rowptr += context->pixbuf->rowstride;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
nlines = jpeg_read_scanlines (cinfo, lines,
|
|
Packit |
a4058c |
cinfo->rec_outbuf_height);
|
|
Packit |
a4058c |
if (nlines == 0)
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
switch (cinfo->out_color_space) {
|
|
Packit |
a4058c |
case JCS_GRAYSCALE:
|
|
Packit |
a4058c |
explode_gray_into_buf (cinfo, lines);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case JCS_RGB:
|
|
Packit |
a4058c |
/* do nothing */
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case JCS_CMYK:
|
|
Packit |
a4058c |
convert_cmyk_to_rgb (cinfo, lines);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
default:
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_UNKNOWN_TYPE,
|
|
Packit |
a4058c |
_("Unsupported JPEG color space (%s)"),
|
|
Packit |
a4058c |
colorspace_name (cinfo->out_color_space));
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return FALSE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->dptr += (gsize)nlines * context->pixbuf->rowstride;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* send updated signal */
|
|
Packit |
a4058c |
if (context->updated_func)
|
|
Packit |
a4058c |
(* context->updated_func) (context->pixbuf,
|
|
Packit |
a4058c |
0,
|
|
Packit |
a4058c |
cinfo->output_scanline - 1,
|
|
Packit |
a4058c |
cinfo->image_width,
|
|
Packit |
a4058c |
nlines,
|
|
Packit |
a4058c |
context->user_data);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return TRUE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/*
|
|
Packit |
a4058c |
* context - from image_begin_load
|
|
Packit |
a4058c |
* buf - new image data
|
|
Packit |
a4058c |
* size - length of new image data
|
|
Packit |
a4058c |
*
|
|
Packit |
a4058c |
* append image data onto inrecrementally built output image
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_load_increment (gpointer data,
|
|
Packit |
a4058c |
const guchar *buf, guint size,
|
|
Packit |
a4058c |
GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
JpegProgContext *context = (JpegProgContext *)data;
|
|
Packit |
a4058c |
struct jpeg_decompress_struct *cinfo;
|
|
Packit |
a4058c |
my_src_ptr src;
|
|
Packit |
a4058c |
guint num_left, num_copy;
|
|
Packit |
a4058c |
guint last_num_left, last_bytes_left;
|
|
Packit |
a4058c |
guint spinguard;
|
|
Packit |
a4058c |
gboolean first;
|
|
Packit |
a4058c |
const guchar *bufhd;
|
|
Packit |
a4058c |
gint width, height;
|
|
Packit |
a4058c |
char otag_str[5];
|
|
Packit |
a4058c |
gchar *icc_profile_base64;
|
|
Packit |
a4058c |
char *density_str;
|
|
Packit |
a4058c |
JpegExifContext exif_context = { 0, };
|
|
Packit |
a4058c |
gboolean retval;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
g_return_val_if_fail (context != NULL, FALSE);
|
|
Packit |
a4058c |
g_return_val_if_fail (buf != NULL, FALSE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
src = (my_src_ptr) context->cinfo.src;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
cinfo = &context->cinfo;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->jerr.error = error;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* check for fatal error */
|
|
Packit |
a4058c |
if (sigsetjmp (context->jerr.setjmp_buffer, 1)) {
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* skip over data if requested, handle unsigned int sizes cleanly */
|
|
Packit |
a4058c |
/* only can happen if we've already called jpeg_get_header once */
|
|
Packit |
a4058c |
if (context->src_initialized && src->skip_next) {
|
|
Packit |
a4058c |
if (src->skip_next > size) {
|
|
Packit |
a4058c |
src->skip_next -= size;
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
num_left = size - src->skip_next;
|
|
Packit |
a4058c |
bufhd = buf + src->skip_next;
|
|
Packit |
a4058c |
src->skip_next = 0;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
num_left = size;
|
|
Packit |
a4058c |
bufhd = buf;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (num_left == 0) {
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
last_num_left = num_left;
|
|
Packit |
a4058c |
last_bytes_left = 0;
|
|
Packit |
a4058c |
spinguard = 0;
|
|
Packit |
a4058c |
first = TRUE;
|
|
Packit |
a4058c |
while (TRUE) {
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* handle any data from caller we haven't processed yet */
|
|
Packit |
a4058c |
if (num_left > 0) {
|
|
Packit |
a4058c |
if(src->pub.bytes_in_buffer &&
|
|
Packit |
a4058c |
src->pub.next_input_byte != src->buffer)
|
|
Packit |
a4058c |
memmove(src->buffer, src->pub.next_input_byte,
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
num_copy = MIN (JPEG_PROG_BUF_SIZE - src->pub.bytes_in_buffer,
|
|
Packit |
a4058c |
num_left);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
memcpy(src->buffer + src->pub.bytes_in_buffer, bufhd,num_copy);
|
|
Packit |
a4058c |
src->pub.next_input_byte = src->buffer;
|
|
Packit |
a4058c |
src->pub.bytes_in_buffer += num_copy;
|
|
Packit |
a4058c |
bufhd += num_copy;
|
|
Packit |
a4058c |
num_left -= num_copy;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* did anything change from last pass, if not return */
|
|
Packit |
a4058c |
if (first) {
|
|
Packit |
a4058c |
last_bytes_left = src->pub.bytes_in_buffer;
|
|
Packit |
a4058c |
first = FALSE;
|
|
Packit |
a4058c |
} else if (src->pub.bytes_in_buffer == last_bytes_left
|
|
Packit |
a4058c |
&& num_left == last_num_left) {
|
|
Packit |
a4058c |
spinguard++;
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
last_bytes_left = src->pub.bytes_in_buffer;
|
|
Packit |
a4058c |
last_num_left = num_left;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* should not go through twice and not pull bytes out of buf */
|
|
Packit |
a4058c |
if (spinguard > 2) {
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* try to load jpeg header */
|
|
Packit |
a4058c |
if (!context->got_header) {
|
|
Packit |
a4058c |
int rc;
|
|
Packit |
a4058c |
gchar* comment;
|
|
Packit |
a4058c |
gboolean has_alpha;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_save_markers (cinfo, JPEG_APP0+1, 0xffff);
|
|
Packit |
a4058c |
jpeg_save_markers (cinfo, JPEG_APP0+2, 0xffff);
|
|
Packit |
a4058c |
jpeg_save_markers (cinfo, JPEG_COM, 0xffff);
|
|
Packit |
a4058c |
rc = jpeg_read_header (cinfo, TRUE);
|
|
Packit |
a4058c |
context->src_initialized = TRUE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (rc == JPEG_SUSPENDED)
|
|
Packit |
a4058c |
continue;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->got_header = TRUE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* parse exif data */
|
|
Packit |
a4058c |
jpeg_parse_exif (&exif_context, cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
width = cinfo->image_width;
|
|
Packit |
a4058c |
height = cinfo->image_height;
|
|
Packit |
a4058c |
if (context->size_func) {
|
|
Packit |
a4058c |
(* context->size_func) (&width, &height, context->user_data);
|
|
Packit |
a4058c |
if (width == 0 || height == 0) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
|
|
Packit |
a4058c |
_("Transformed JPEG has zero width or height."));
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
cinfo->scale_num = 1;
|
|
Packit |
a4058c |
for (cinfo->scale_denom = 2; cinfo->scale_denom <= 8; cinfo->scale_denom *= 2) {
|
|
Packit |
a4058c |
jpeg_calc_output_dimensions (cinfo);
|
|
Packit |
a4058c |
if (cinfo->output_width < width || cinfo->output_height < height) {
|
|
Packit |
a4058c |
cinfo->scale_denom /= 2;
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
jpeg_calc_output_dimensions (cinfo);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (cinfo->output_components == 3) {
|
|
Packit |
a4058c |
has_alpha = FALSE;
|
|
Packit |
a4058c |
} else if (cinfo->output_components == 4) {
|
|
Packit |
a4058c |
has_alpha = TRUE;
|
|
Packit |
a4058c |
} else if (cinfo->output_components == 1 &&
|
|
Packit |
a4058c |
cinfo->out_color_space == JCS_GRAYSCALE) {
|
|
Packit |
a4058c |
has_alpha = FALSE;
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
|
|
Packit |
a4058c |
_("Unsupported number of color components (%d)"),
|
|
Packit |
a4058c |
cinfo->output_components);
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
|
|
Packit |
a4058c |
has_alpha,
|
|
Packit |
a4058c |
8,
|
|
Packit |
a4058c |
cinfo->output_width,
|
|
Packit |
a4058c |
cinfo->output_height);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (context->pixbuf == NULL) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
|
Packit |
a4058c |
_("Couldn't allocate memory for loading JPEG file"));
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
comment = jpeg_get_comment (cinfo);
|
|
Packit |
a4058c |
if (comment != NULL) {
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "comment", comment);
|
|
Packit |
a4058c |
g_free (comment);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
switch (cinfo->density_unit) {
|
|
Packit |
a4058c |
case 1:
|
|
Packit |
a4058c |
/* Dots per inch (no conversion required) */
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", cinfo->X_density);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "x-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", cinfo->Y_density);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "y-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
case 2:
|
|
Packit |
a4058c |
/* Dots per cm - convert into dpi */
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo->X_density));
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "x-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
density_str = g_strdup_printf ("%d", DPCM_TO_DPI (cinfo->Y_density));
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "y-dpi", density_str);
|
|
Packit |
a4058c |
g_free (density_str);
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* if orientation tag was found set an option to remember its value */
|
|
Packit |
a4058c |
if (exif_context.orientation != 0) {
|
|
Packit |
a4058c |
g_snprintf (otag_str, sizeof (otag_str), "%d", exif_context.orientation);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "orientation", otag_str);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
/* if icc profile was found */
|
|
Packit |
a4058c |
if (exif_context.icc_profile != NULL) {
|
|
Packit |
a4058c |
icc_profile_base64 = g_base64_encode ((const guchar *) exif_context.icc_profile, exif_context.icc_profile_size);
|
|
Packit |
a4058c |
gdk_pixbuf_set_option (context->pixbuf, "icc-profile", icc_profile_base64);
|
|
Packit |
a4058c |
g_free (icc_profile_base64);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Use pixbuf buffer to store decompressed data */
|
|
Packit |
a4058c |
context->dptr = context->pixbuf->pixels;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Notify the client that we are ready to go */
|
|
Packit |
a4058c |
if (context->prepared_func)
|
|
Packit |
a4058c |
(* context->prepared_func) (context->pixbuf,
|
|
Packit |
a4058c |
NULL,
|
|
Packit |
a4058c |
context->user_data);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
} else if (!context->did_prescan) {
|
|
Packit |
a4058c |
int rc;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* start decompression */
|
|
Packit |
a4058c |
cinfo->buffered_image = cinfo->progressive_mode;
|
|
Packit |
a4058c |
rc = jpeg_start_decompress (cinfo);
|
|
Packit |
a4058c |
cinfo->do_fancy_upsampling = FALSE;
|
|
Packit |
a4058c |
cinfo->do_block_smoothing = FALSE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (rc == JPEG_SUSPENDED)
|
|
Packit |
a4058c |
continue;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
context->did_prescan = TRUE;
|
|
Packit |
a4058c |
} else if (!cinfo->buffered_image) {
|
|
Packit |
a4058c |
/* we're decompressing unbuffered so
|
|
Packit |
a4058c |
* simply get scanline by scanline from jpeg lib
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
if (! gdk_pixbuf__jpeg_image_load_lines (context,
|
|
Packit |
a4058c |
error)) {
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (cinfo->output_scanline >= cinfo->output_height) {
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
/* we're decompressing buffered (progressive)
|
|
Packit |
a4058c |
* so feed jpeg lib scanlines
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* keep going until we've done all passes */
|
|
Packit |
a4058c |
while (!jpeg_input_complete (cinfo)) {
|
|
Packit |
a4058c |
if (!context->in_output) {
|
|
Packit |
a4058c |
if (jpeg_start_output (cinfo, cinfo->input_scan_number)) {
|
|
Packit |
a4058c |
context->in_output = TRUE;
|
|
Packit |
a4058c |
context->dptr = context->pixbuf->pixels;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
else
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* get scanlines from jpeg lib */
|
|
Packit |
a4058c |
if (! gdk_pixbuf__jpeg_image_load_lines (context,
|
|
Packit |
a4058c |
error)) {
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (cinfo->output_scanline >= cinfo->output_height &&
|
|
Packit |
a4058c |
jpeg_finish_output (cinfo))
|
|
Packit |
a4058c |
context->in_output = FALSE;
|
|
Packit |
a4058c |
else
|
|
Packit |
a4058c |
break;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
if (jpeg_input_complete (cinfo)) {
|
|
Packit |
a4058c |
/* did entire image */
|
|
Packit |
a4058c |
retval = TRUE;
|
|
Packit |
a4058c |
goto out;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
else
|
|
Packit |
a4058c |
continue;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
out:
|
|
Packit |
a4058c |
jpeg_destroy_exif_context (&exif_context);
|
|
Packit |
a4058c |
return retval;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Save */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#define TO_FUNCTION_BUF_SIZE 4096
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
typedef struct {
|
|
Packit |
a4058c |
struct jpeg_destination_mgr pub;
|
|
Packit |
a4058c |
JOCTET *buffer;
|
|
Packit |
a4058c |
GdkPixbufSaveFunc save_func;
|
|
Packit |
a4058c |
gpointer user_data;
|
|
Packit |
a4058c |
GError **error;
|
|
Packit |
a4058c |
} ToFunctionDestinationManager;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
void
|
|
Packit |
a4058c |
to_callback_init (j_compress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
ToFunctionDestinationManager *destmgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
|
|
Packit |
a4058c |
destmgr->pub.next_output_byte = destmgr->buffer;
|
|
Packit |
a4058c |
destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static void
|
|
Packit |
a4058c |
to_callback_do_write (j_compress_ptr cinfo, gsize length)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
ToFunctionDestinationManager *destmgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
|
|
Packit |
a4058c |
if (!destmgr->save_func ((gchar *)destmgr->buffer,
|
|
Packit |
a4058c |
length,
|
|
Packit |
a4058c |
destmgr->error,
|
|
Packit |
a4058c |
destmgr->user_data)) {
|
|
Packit |
a4058c |
struct error_handler_data *errmgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
errmgr = (struct error_handler_data *) cinfo->err;
|
|
Packit |
a4058c |
/* Use a default error message if the callback didn't set one,
|
|
Packit |
a4058c |
* which it should have.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
if (errmgr->error && *errmgr->error == NULL) {
|
|
Packit |
a4058c |
g_set_error_literal (errmgr->error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_CORRUPT_IMAGE,
|
|
Packit |
a4058c |
"write function failed");
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
siglongjmp (errmgr->setjmp_buffer, 1);
|
|
Packit |
a4058c |
g_assert_not_reached ();
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static boolean
|
|
Packit |
a4058c |
to_callback_empty_output_buffer (j_compress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
ToFunctionDestinationManager *destmgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
|
|
Packit |
a4058c |
to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE);
|
|
Packit |
a4058c |
destmgr->pub.next_output_byte = destmgr->buffer;
|
|
Packit |
a4058c |
destmgr->pub.free_in_buffer = TO_FUNCTION_BUF_SIZE;
|
|
Packit |
a4058c |
return TRUE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
void
|
|
Packit |
a4058c |
to_callback_terminate (j_compress_ptr cinfo)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
ToFunctionDestinationManager *destmgr;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
destmgr = (ToFunctionDestinationManager*) cinfo->dest;
|
|
Packit |
a4058c |
to_callback_do_write (cinfo, TO_FUNCTION_BUF_SIZE - destmgr->pub.free_in_buffer);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
real_save_jpeg (GdkPixbuf *pixbuf,
|
|
Packit |
a4058c |
gchar **keys,
|
|
Packit |
a4058c |
gchar **values,
|
|
Packit |
a4058c |
GError **error,
|
|
Packit |
a4058c |
gboolean to_callback,
|
|
Packit |
a4058c |
FILE *f,
|
|
Packit |
a4058c |
GdkPixbufSaveFunc save_func,
|
|
Packit |
a4058c |
gpointer user_data)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
/* FIXME error handling is broken */
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
struct jpeg_compress_struct cinfo;
|
|
Packit |
a4058c |
guchar *buf = NULL;
|
|
Packit |
a4058c |
guchar *ptr;
|
|
Packit |
a4058c |
guchar *pixels = NULL;
|
|
Packit |
a4058c |
JSAMPROW *jbuf;
|
|
Packit |
a4058c |
int y = 0;
|
|
Packit |
a4058c |
volatile int quality = 75; /* default; must be between 0 and 100 */
|
|
Packit |
a4058c |
int i, j;
|
|
Packit |
a4058c |
int w, h = 0;
|
|
Packit |
a4058c |
int rowstride = 0;
|
|
Packit |
a4058c |
int n_channels;
|
|
Packit |
a4058c |
struct error_handler_data jerr;
|
|
Packit |
a4058c |
ToFunctionDestinationManager to_callback_destmgr;
|
|
Packit |
a4058c |
int x_density = 0;
|
|
Packit |
a4058c |
int y_density = 0;
|
|
Packit |
a4058c |
gchar *icc_profile = NULL;
|
|
Packit |
a4058c |
gchar *data;
|
|
Packit |
a4058c |
gint retval = TRUE;
|
|
Packit |
a4058c |
gsize icc_profile_size = 0;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
to_callback_destmgr.buffer = NULL;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (keys && *keys) {
|
|
Packit |
a4058c |
gchar **kiter = keys;
|
|
Packit |
a4058c |
gchar **viter = values;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
while (*kiter) {
|
|
Packit |
a4058c |
if (strcmp (*kiter, "quality") == 0) {
|
|
Packit |
a4058c |
char *endptr = NULL;
|
|
Packit |
a4058c |
quality = strtol (*viter, &endptr, 10);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (endptr == *viter) {
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_BAD_OPTION,
|
|
Packit |
a4058c |
_("JPEG quality must be a value between 0 and 100; value '%s' could not be parsed."),
|
|
Packit |
a4058c |
*viter);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (quality < 0 ||
|
|
Packit |
a4058c |
quality > 100) {
|
|
Packit |
a4058c |
/* This is a user-visible error;
|
|
Packit |
a4058c |
* lets people skip the range-checking
|
|
Packit |
a4058c |
* in their app.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_BAD_OPTION,
|
|
Packit |
a4058c |
_("JPEG quality must be a value between 0 and 100; value '%d' is not allowed."),
|
|
Packit |
a4058c |
quality);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else if (strcmp (*kiter, "x-dpi") == 0) {
|
|
Packit |
a4058c |
char *endptr = NULL;
|
|
Packit |
a4058c |
x_density = strtol (*viter, &endptr, 10);
|
|
Packit |
a4058c |
if (endptr == *viter)
|
|
Packit |
a4058c |
x_density = -1;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (x_density <= 0 ||
|
|
Packit |
a4058c |
x_density > 65535) {
|
|
Packit |
a4058c |
/* This is a user-visible error;
|
|
Packit |
a4058c |
* lets people skip the range-checking
|
|
Packit |
a4058c |
* in their app.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_BAD_OPTION,
|
|
Packit |
a4058c |
_("JPEG x-dpi must be a value between 1 and 65535; value '%s' is not allowed."),
|
|
Packit |
a4058c |
*viter);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else if (strcmp (*kiter, "y-dpi") == 0) {
|
|
Packit |
a4058c |
char *endptr = NULL;
|
|
Packit |
a4058c |
y_density = strtol (*viter, &endptr, 10);
|
|
Packit |
a4058c |
if (endptr == *viter)
|
|
Packit |
a4058c |
y_density = -1;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (y_density <= 0 ||
|
|
Packit |
a4058c |
y_density > 65535) {
|
|
Packit |
a4058c |
/* This is a user-visible error;
|
|
Packit |
a4058c |
* lets people skip the range-checking
|
|
Packit |
a4058c |
* in their app.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_BAD_OPTION,
|
|
Packit |
a4058c |
_("JPEG y-dpi must be a value between 1 and 65535; value '%s' is not allowed."),
|
|
Packit |
a4058c |
*viter);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else if (strcmp (*kiter, "icc-profile") == 0) {
|
|
Packit |
a4058c |
/* decode from base64 */
|
|
Packit |
a4058c |
icc_profile = (gchar*) g_base64_decode (*viter, &icc_profile_size);
|
|
Packit |
a4058c |
if (icc_profile_size < 127) {
|
|
Packit |
a4058c |
/* This is a user-visible error */
|
|
Packit |
a4058c |
g_set_error (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_BAD_OPTION,
|
|
Packit |
a4058c |
_("Color profile has invalid length '%u'."),
|
|
Packit |
a4058c |
(guint) icc_profile_size);
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
g_warning ("Unrecognized parameter (%s) passed to JPEG saver.", *kiter);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
++kiter;
|
|
Packit |
a4058c |
++viter;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
|
Packit |
a4058c |
n_channels = gdk_pixbuf_get_n_channels (pixbuf);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
w = gdk_pixbuf_get_width (pixbuf);
|
|
Packit |
a4058c |
h = gdk_pixbuf_get_height (pixbuf);
|
|
Packit |
a4058c |
pixels = gdk_pixbuf_get_pixels (pixbuf);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Guaranteed by the caller. */
|
|
Packit |
a4058c |
g_assert (w >= 0);
|
|
Packit |
a4058c |
g_assert (h >= 0);
|
|
Packit |
a4058c |
g_assert (rowstride >= 0);
|
|
Packit |
a4058c |
g_assert (n_channels >= 0);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* Allocate a small buffer to convert image data,
|
|
Packit |
a4058c |
* and a larger buffer if doing to_callback save.
|
|
Packit |
a4058c |
*/
|
|
Packit |
a4058c |
buf = g_try_malloc (w * 3 * sizeof (guchar));
|
|
Packit |
a4058c |
if (!buf) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
|
Packit |
a4058c |
_("Couldn't allocate memory for loading JPEG file"));
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
if (to_callback) {
|
|
Packit |
a4058c |
to_callback_destmgr.buffer = g_try_malloc (TO_FUNCTION_BUF_SIZE);
|
|
Packit |
a4058c |
if (!to_callback_destmgr.buffer) {
|
|
Packit |
a4058c |
g_set_error_literal (error,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR,
|
|
Packit |
a4058c |
GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY,
|
|
Packit |
a4058c |
_("Couldn't allocate memory for loading JPEG file"));
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* set up error handling */
|
|
Packit |
a4058c |
cinfo.err = jpeg_std_error (&(jerr.pub));
|
|
Packit |
a4058c |
jerr.pub.error_exit = fatal_error_handler;
|
|
Packit |
a4058c |
jerr.pub.output_message = output_message_handler;
|
|
Packit |
a4058c |
jerr.error = error;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
if (sigsetjmp (jerr.setjmp_buffer, 1)) {
|
|
Packit |
a4058c |
jpeg_destroy_compress (&cinfo);
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* setup compress params */
|
|
Packit |
a4058c |
jpeg_create_compress (&cinfo);
|
|
Packit |
a4058c |
if (to_callback) {
|
|
Packit |
a4058c |
to_callback_destmgr.pub.init_destination = to_callback_init;
|
|
Packit |
a4058c |
to_callback_destmgr.pub.empty_output_buffer = to_callback_empty_output_buffer;
|
|
Packit |
a4058c |
to_callback_destmgr.pub.term_destination = to_callback_terminate;
|
|
Packit |
a4058c |
to_callback_destmgr.error = error;
|
|
Packit |
a4058c |
to_callback_destmgr.save_func = save_func;
|
|
Packit |
a4058c |
to_callback_destmgr.user_data = user_data;
|
|
Packit |
a4058c |
cinfo.dest = (struct jpeg_destination_mgr*) &to_callback_destmgr;
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
jpeg_stdio_dest (&cinfo, f);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
cinfo.image_width = w;
|
|
Packit |
a4058c |
cinfo.image_height = h;
|
|
Packit |
a4058c |
cinfo.input_components = 3;
|
|
Packit |
a4058c |
cinfo.in_color_space = JCS_RGB;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* set up jepg compression parameters */
|
|
Packit |
a4058c |
jpeg_set_defaults (&cinfo);
|
|
Packit |
a4058c |
jpeg_set_quality (&cinfo, quality, TRUE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* set density information */
|
|
Packit |
a4058c |
if (x_density > 0 && y_density > 0) {
|
|
Packit |
a4058c |
cinfo.density_unit = 1; /* Dots per inch */
|
|
Packit |
a4058c |
cinfo.X_density = x_density;
|
|
Packit |
a4058c |
cinfo.Y_density = y_density;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
jpeg_start_compress (&cinfo, TRUE);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* write ICC profile data */
|
|
Packit |
a4058c |
if (icc_profile != NULL) {
|
|
Packit |
a4058c |
/* optimise for the common case where only one APP2 segment is required */
|
|
Packit |
a4058c |
if (icc_profile_size < 0xffef) {
|
|
Packit |
a4058c |
data = g_new (gchar, icc_profile_size + 14);
|
|
Packit |
a4058c |
memcpy (data, "ICC_PROFILE\000\001\001", 14);
|
|
Packit |
a4058c |
memcpy (data + 14, icc_profile, icc_profile_size);
|
|
Packit |
a4058c |
jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, icc_profile_size + 14);
|
|
Packit |
a4058c |
g_free (data);
|
|
Packit |
a4058c |
} else {
|
|
Packit |
a4058c |
guint segments;
|
|
Packit |
a4058c |
guint size = 0xffef;
|
|
Packit |
a4058c |
guint offset;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
segments = (guint) ceilf ((gfloat) icc_profile_size / (gfloat) 0xffef);
|
|
Packit |
a4058c |
data = g_new (gchar, 0xffff);
|
|
Packit |
a4058c |
memcpy (data, "ICC_PROFILE\000", 12);
|
|
Packit |
a4058c |
data[13] = segments;
|
|
Packit |
a4058c |
for (i=0; i<=segments; i++) {
|
|
Packit |
a4058c |
data[12] = i;
|
|
Packit |
a4058c |
offset = 0xffef * i;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* last segment */
|
|
Packit |
a4058c |
if (i == segments)
|
|
Packit |
a4058c |
size = icc_profile_size % 0xffef;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
memcpy (data + 14, icc_profile + offset, size);
|
|
Packit |
a4058c |
jpeg_write_marker (&cinfo, JPEG_APP0+2, (const JOCTET *) data, size + 14);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
g_free (data);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* get the start pointer */
|
|
Packit |
a4058c |
ptr = pixels;
|
|
Packit |
a4058c |
/* go one scanline at a time... and save */
|
|
Packit |
a4058c |
i = 0;
|
|
Packit |
a4058c |
while (cinfo.next_scanline < cinfo.image_height) {
|
|
Packit |
a4058c |
/* convert scanline from ARGB to RGB packed */
|
|
Packit |
a4058c |
for (j = 0; j < w; j++)
|
|
Packit |
a4058c |
memcpy (&(buf[j*3]), &(ptr[(gsize)i*rowstride + j*n_channels]), 3);
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* write scanline */
|
|
Packit |
a4058c |
jbuf = (JSAMPROW *)(&buf;;
|
|
Packit |
a4058c |
if (jpeg_write_scanlines (&cinfo, jbuf, 1) == 0) {
|
|
Packit |
a4058c |
jpeg_destroy_compress (&cinfo);
|
|
Packit |
a4058c |
retval = FALSE;
|
|
Packit |
a4058c |
goto cleanup;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
i++;
|
|
Packit |
a4058c |
y++;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
/* finish off */
|
|
Packit |
a4058c |
jpeg_finish_compress (&cinfo);
|
|
Packit |
a4058c |
jpeg_destroy_compress(&cinfo);
|
|
Packit |
a4058c |
cleanup:
|
|
Packit |
a4058c |
g_free (buf);
|
|
Packit |
a4058c |
g_free (to_callback_destmgr.buffer);
|
|
Packit |
a4058c |
g_free (icc_profile);
|
|
Packit |
a4058c |
return retval;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_save (FILE *f,
|
|
Packit |
a4058c |
GdkPixbuf *pixbuf,
|
|
Packit |
a4058c |
gchar **keys,
|
|
Packit |
a4058c |
gchar **values,
|
|
Packit |
a4058c |
GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
return real_save_jpeg (pixbuf, keys, values, error,
|
|
Packit |
a4058c |
FALSE, f, NULL, NULL);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_image_save_to_callback (GdkPixbufSaveFunc save_func,
|
|
Packit |
a4058c |
gpointer user_data,
|
|
Packit |
a4058c |
GdkPixbuf *pixbuf,
|
|
Packit |
a4058c |
gchar **keys,
|
|
Packit |
a4058c |
gchar **values,
|
|
Packit |
a4058c |
GError **error)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
return real_save_jpeg (pixbuf, keys, values, error,
|
|
Packit |
a4058c |
TRUE, NULL, save_func, user_data);
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
static gboolean
|
|
Packit |
a4058c |
gdk_pixbuf__jpeg_is_save_option_supported (const gchar *option_key)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
if (g_strcmp0 (option_key, "quality") == 0 ||
|
|
Packit |
a4058c |
g_strcmp0 (option_key, "icc-profile") == 0)
|
|
Packit |
a4058c |
return TRUE;
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
return FALSE;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
#ifndef INCLUDE_jpeg
|
|
Packit |
a4058c |
#define MODULE_ENTRY(function) G_MODULE_EXPORT void function
|
|
Packit |
a4058c |
#else
|
|
Packit |
a4058c |
#define MODULE_ENTRY(function) void _gdk_pixbuf__jpeg_ ## function
|
|
Packit |
a4058c |
#endif
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
MODULE_ENTRY (fill_vtable) (GdkPixbufModule *module)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
module->load = gdk_pixbuf__jpeg_image_load;
|
|
Packit |
a4058c |
module->begin_load = gdk_pixbuf__jpeg_image_begin_load;
|
|
Packit |
a4058c |
module->stop_load = gdk_pixbuf__jpeg_image_stop_load;
|
|
Packit |
a4058c |
module->load_increment = gdk_pixbuf__jpeg_image_load_increment;
|
|
Packit |
a4058c |
module->save = gdk_pixbuf__jpeg_image_save;
|
|
Packit |
a4058c |
module->save_to_callback = gdk_pixbuf__jpeg_image_save_to_callback;
|
|
Packit |
a4058c |
module->is_save_option_supported = gdk_pixbuf__jpeg_is_save_option_supported;
|
|
Packit |
a4058c |
}
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
MODULE_ENTRY (fill_info) (GdkPixbufFormat *info)
|
|
Packit |
a4058c |
{
|
|
Packit |
a4058c |
static const GdkPixbufModulePattern signature[] = {
|
|
Packit |
a4058c |
{ "\xff\xd8", NULL, 100 },
|
|
Packit |
a4058c |
{ NULL, NULL, 0 }
|
|
Packit |
a4058c |
};
|
|
Packit |
a4058c |
static const gchar *mime_types[] = {
|
|
Packit |
a4058c |
"image/jpeg",
|
|
Packit |
a4058c |
NULL
|
|
Packit |
a4058c |
};
|
|
Packit |
a4058c |
static const gchar *extensions[] = {
|
|
Packit |
a4058c |
"jpeg",
|
|
Packit |
a4058c |
"jpe",
|
|
Packit |
a4058c |
"jpg",
|
|
Packit |
a4058c |
NULL
|
|
Packit |
a4058c |
};
|
|
Packit |
a4058c |
|
|
Packit |
a4058c |
info->name = "jpeg";
|
|
Packit |
a4058c |
info->signature = (GdkPixbufModulePattern *) signature;
|
|
Packit |
a4058c |
info->description = NC_("image format", "JPEG");
|
|
Packit |
a4058c |
info->mime_types = (gchar **) mime_types;
|
|
Packit |
a4058c |
info->extensions = (gchar **) extensions;
|
|
Packit |
a4058c |
info->flags = GDK_PIXBUF_FORMAT_WRITABLE | GDK_PIXBUF_FORMAT_THREADSAFE;
|
|
Packit |
a4058c |
info->license = "LGPL";
|
|
Packit |
a4058c |
}
|