/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* Copyright (C) 2011 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#if HAVE_LCMS2
#include
#endif
#include
#include "cairo-image-surface-png.h"
/* starting from libpng version 1.5 it is not possible
* to access inside the PNG struct directly
*/
#define PNG_SETJMP(ptr) setjmp(png_jmpbuf(ptr))
#ifdef PNG_LIBPNG_VER
#if PNG_LIBPNG_VER < 10400
#ifdef PNG_SETJMP
#undef PNG_SETJMP
#endif
#define PNG_SETJMP(ptr) setjmp(ptr->jmpbuf)
#endif
#endif
typedef struct {
GInputStream *stream;
GCancellable *cancellable;
GError **error;
png_struct *png_ptr;
png_info *png_info_ptr;
cairo_surface_t *surface;
} CairoPngData;
static void
_cairo_png_data_destroy (CairoPngData *cairo_png_data)
{
png_destroy_read_struct (&cairo_png_data->png_ptr, &cairo_png_data->png_info_ptr, NULL);
g_object_unref (cairo_png_data->stream);
cairo_surface_destroy (cairo_png_data->surface);
g_free (cairo_png_data);
}
static void
gerror_error_func (png_structp png_ptr,
png_const_charp message)
{
GError ***error_p = png_get_error_ptr (png_ptr);
GError **error = *error_p;
if (error != NULL)
*error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%s", message);
}
static void
gerror_warning_func (png_structp png_ptr,
png_const_charp message)
{
/* void: we don't care about warnings */
}
static void
cairo_png_read_data_func (png_structp png_ptr,
png_bytep buffer,
png_size_t size)
{
CairoPngData *cairo_png_data;
GError *error = NULL;
cairo_png_data = png_get_io_ptr (png_ptr);
if(! g_input_stream_read_all (cairo_png_data->stream,
buffer,
size,
NULL,
cairo_png_data->cancellable,
&error))
{
png_error (png_ptr, error->message);
g_error_free (error);
}
}
static void
transform_to_argb32_format_func (png_structp png,
png_row_infop row_info,
png_bytep data)
{
guint i;
guint32 pixel;
for (i = 0; i < row_info->rowbytes; i += 4) {
guchar *p_iter = data + i;
guchar r, g, b, a;
a = p_iter[3];
if (a == 0xff) {
pixel = CAIRO_RGBA_TO_UINT32 (p_iter[0], p_iter[1], p_iter[2], 0xff);
}
else if (a == 0) {
pixel = 0;
}
else {
r = _cairo_multiply_alpha (p_iter[0], a);
g = _cairo_multiply_alpha (p_iter[1], a);
b = _cairo_multiply_alpha (p_iter[2], a);
pixel = CAIRO_RGBA_TO_UINT32 (r, g, b, a);
}
memcpy (p_iter, &pixel, sizeof (guint32));
}
}
GthImage *
_cairo_image_surface_create_from_png (GInputStream *istream,
GthFileData *file_data,
int requested_size,
int *original_width,
int *original_height,
gboolean *loaded_original,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
GthImage *image;
CairoPngData *cairo_png_data;
png_uint_32 width, height;
int bit_depth, color_type, interlace_type;
cairo_surface_metadata_t *metadata;
unsigned char *surface_row;
int rowstride;
png_bytep *row_pointers;
int row;
png_textp text_ptr;
int num_texts;
image = gth_image_new ();
cairo_png_data = g_new0 (CairoPngData, 1);
cairo_png_data->cancellable = cancellable;
cairo_png_data->error = error;
cairo_png_data->stream = _g_object_ref (istream);
if (cairo_png_data->stream == NULL) {
_cairo_png_data_destroy (cairo_png_data);
return image;
}
cairo_png_data->png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING,
&cairo_png_data->error,
gerror_error_func,
gerror_warning_func);
if (cairo_png_data->png_ptr == NULL) {
_cairo_png_data_destroy (cairo_png_data);
return image;
}
cairo_png_data->png_info_ptr = png_create_info_struct (cairo_png_data->png_ptr);
if (cairo_png_data->png_info_ptr == NULL) {
_cairo_png_data_destroy (cairo_png_data);
return image;
}
if (PNG_SETJMP (cairo_png_data->png_ptr)) {
_cairo_png_data_destroy (cairo_png_data);
return image;
}
png_set_read_fn (cairo_png_data->png_ptr, cairo_png_data, cairo_png_read_data_func);
#ifndef HAVE_LCMS2
png_set_gamma (cairo_png_data->png_ptr, PNG_DEFAULT_sRGB, PNG_DEFAULT_sRGB);
#endif
png_read_info (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
png_get_IHDR (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
&width,
&height,
&bit_depth,
&color_type,
&interlace_type,
NULL,
NULL);
cairo_png_data->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
if (cairo_surface_status (cairo_png_data->surface) != CAIRO_STATUS_SUCCESS) {
/* g_warning ("%s", cairo_status_to_string (cairo_surface_status (surface))); */
_cairo_png_data_destroy (cairo_png_data);
return image;
}
metadata = _cairo_image_surface_get_metadata (cairo_png_data->surface);
_cairo_metadata_set_has_alpha (metadata, (color_type & PNG_COLOR_MASK_ALPHA) || (color_type & PNG_COLOR_MASK_PALETTE));
_cairo_metadata_set_original_size (metadata, width, height);
/* Set the data transformations */
png_set_strip_16 (cairo_png_data->png_ptr);
png_set_packing (cairo_png_data->png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb (cairo_png_data->png_ptr);
if ((color_type == PNG_COLOR_TYPE_GRAY) && (bit_depth < 8))
png_set_expand_gray_1_2_4_to_8 (cairo_png_data->png_ptr);
if (png_get_valid (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha (cairo_png_data->png_ptr);
png_set_filler (cairo_png_data->png_ptr, 0xff, PNG_FILLER_AFTER);
if ((color_type == PNG_COLOR_TYPE_GRAY) || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA))
png_set_gray_to_rgb (cairo_png_data->png_ptr);
if (interlace_type != PNG_INTERLACE_NONE)
png_set_interlace_handling (cairo_png_data->png_ptr);
png_set_read_user_transform_fn (cairo_png_data->png_ptr, transform_to_argb32_format_func);
png_read_update_info (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
/* Read the image */
surface_row = _cairo_image_surface_flush_and_get_data (cairo_png_data->surface);
rowstride = cairo_image_surface_get_stride (cairo_png_data->surface);
row_pointers = g_new (png_bytep, height);
for (row = 0; row < height; row++) {
row_pointers[row] = surface_row;
surface_row += rowstride;
}
png_read_image (cairo_png_data->png_ptr, row_pointers);
png_read_end (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
cairo_surface_mark_dirty (cairo_png_data->surface);
if (cairo_surface_status (cairo_png_data->surface) == CAIRO_STATUS_SUCCESS)
gth_image_set_cairo_surface (image, cairo_png_data->surface);
if (original_width != NULL)
*original_width = png_get_image_width (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
if (original_height != NULL)
*original_height = png_get_image_height (cairo_png_data->png_ptr, cairo_png_data->png_info_ptr);
if (png_get_text (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
&text_ptr,
&num_texts))
{
int i;
for (i = 0; i < num_texts; i++) {
if (strcmp (text_ptr[i].key, "Thumb::Image::Width") == 0)
metadata->thumbnail.image_width = atoi (text_ptr[i].text);
else if (strcmp (text_ptr[i].key, "Thumb::Image::Height") == 0)
metadata->thumbnail.image_height = atoi (text_ptr[i].text);
}
}
g_free (row_pointers);
#if HAVE_LCMS2
{
GthICCProfile *profile = NULL;
int intent;
png_charp name;
int compression_type;
png_bytep icc_data;
png_uint_32 icc_data_size;
double gamma;
if (png_get_sRGB (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
&intent) == PNG_INFO_sRGB)
{
profile = gth_icc_profile_new_srgb ();
}
else if (png_get_iCCP (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
&name,
&compression_type,
&icc_data,
&icc_data_size) == PNG_INFO_iCCP)
{
if ((icc_data_size > 0) && (icc_data != NULL))
profile = gth_icc_profile_new (GTH_ICC_PROFILE_ID_UNKNOWN, cmsOpenProfileFromMem (icc_data, icc_data_size));
}
else if (png_get_gAMA (cairo_png_data->png_ptr,
cairo_png_data->png_info_ptr,
&gamma))
{
profile = gth_icc_profile_new_srgb_with_gamma (1.0 / gamma);
}
if (profile != NULL) {
gth_image_set_icc_profile (image, profile);
g_object_unref (profile);
}
}
#endif
_cairo_png_data_destroy (cairo_png_data);
return image;
}