forked from AuroraMiddleware/gtk
Add color management support to gdk_pixbuf_save
This patch adds an icc-profile option to a GdkPixbuf which can be used to read or write an embedded ICC profile. Add PNG support for now, but other image formats are awaiting review.
This commit is contained in:
parent
89ac81e767
commit
f289f667e7
@ -24,6 +24,7 @@ LDADDS = \
|
|||||||
noinst_PROGRAMS = \
|
noinst_PROGRAMS = \
|
||||||
testpixbuf-drawable \
|
testpixbuf-drawable \
|
||||||
testanimation \
|
testanimation \
|
||||||
|
testpixbuf-color \
|
||||||
testpixbuf-save \
|
testpixbuf-save \
|
||||||
testpixbuf-scale \
|
testpixbuf-scale \
|
||||||
pixbuf-demo
|
pixbuf-demo
|
||||||
@ -52,6 +53,7 @@ test-inline-pixbufs.h: $(pixbuf_csource_deps) apple-red.png gnome-foot.png
|
|||||||
testpixbuf_DEPENDENCIES = $(DEPS)
|
testpixbuf_DEPENDENCIES = $(DEPS)
|
||||||
testpixbuf_drawable_DEPENDENCIES = $(DEPS)
|
testpixbuf_drawable_DEPENDENCIES = $(DEPS)
|
||||||
testpixbuf_save_DEPENDENCIES = $(DEPS)
|
testpixbuf_save_DEPENDENCIES = $(DEPS)
|
||||||
|
testpixbuf_color_DEPENDENCIES = $(DEPS)
|
||||||
testpixbuf_scale_DEPENDENCIES = $(DEPS)
|
testpixbuf_scale_DEPENDENCIES = $(DEPS)
|
||||||
testanimation_DEPENDENCIES = $(DEPS)
|
testanimation_DEPENDENCIES = $(DEPS)
|
||||||
pixbuf_demo_DEPENDENCIES = $(DEPS)
|
pixbuf_demo_DEPENDENCIES = $(DEPS)
|
||||||
@ -59,6 +61,7 @@ pixbuf_demo_DEPENDENCIES = $(DEPS)
|
|||||||
testpixbuf_LDADD = $(LDADDS)
|
testpixbuf_LDADD = $(LDADDS)
|
||||||
testpixbuf_drawable_LDADD = $(LDADDS)
|
testpixbuf_drawable_LDADD = $(LDADDS)
|
||||||
testpixbuf_save_LDADD = $(LDADDS)
|
testpixbuf_save_LDADD = $(LDADDS)
|
||||||
|
testpixbuf_color_LDADD = $(LDADDS)
|
||||||
testpixbuf_scale_LDADD = $(LDADDS)
|
testpixbuf_scale_LDADD = $(LDADDS)
|
||||||
testanimation_LDADD = $(LDADDS)
|
testanimation_LDADD = $(LDADDS)
|
||||||
pixbuf_demo_LDADD = $(LDADDS)
|
pixbuf_demo_LDADD = $(LDADDS)
|
||||||
@ -66,6 +69,7 @@ pixbuf_demo_LDADD = $(LDADDS)
|
|||||||
testpixbuf_SOURCES = testpixbuf.c pixbuf-init.c
|
testpixbuf_SOURCES = testpixbuf.c pixbuf-init.c
|
||||||
testpixbuf_drawable_SOURCES = testpixbuf-drawable.c pixbuf-init.c
|
testpixbuf_drawable_SOURCES = testpixbuf-drawable.c pixbuf-init.c
|
||||||
testpixbuf_save_SOURCES = testpixbuf-save.c
|
testpixbuf_save_SOURCES = testpixbuf-save.c
|
||||||
|
testpixbuf_color_SOURCES = testpixbuf-color.c
|
||||||
testpixbuf_scale_SOURCES = testpixbuf-scale.c pixbuf-init.c
|
testpixbuf_scale_SOURCES = testpixbuf-scale.c pixbuf-init.c
|
||||||
testanimation_SOURCES = testanimation.c pixbuf-init.c
|
testanimation_SOURCES = testanimation.c pixbuf-init.c
|
||||||
pixbuf_demo_SOURCES = pixbuf-demo.c pixbuf-init.c
|
pixbuf_demo_SOURCES = pixbuf-demo.c pixbuf-init.c
|
||||||
|
116
demos/testpixbuf-color.c
Normal file
116
demos/testpixbuf-color.c
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#define ICC_PROFILE "/usr/share/color/icc/bluish.icc"
|
||||||
|
#define ICC_PROFILE_SIZE 3966
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
save_image_png (const gchar *filename, GdkPixbuf *pixbuf, GError **error)
|
||||||
|
{
|
||||||
|
gchar *contents = NULL;
|
||||||
|
gchar *contents_encode = NULL;
|
||||||
|
gsize length;
|
||||||
|
gboolean ret;
|
||||||
|
gint len;
|
||||||
|
|
||||||
|
/* get icc file */
|
||||||
|
ret = g_file_get_contents (ICC_PROFILE, &contents, &length, error);
|
||||||
|
if (!ret)
|
||||||
|
goto out;
|
||||||
|
contents_encode = g_base64_encode ((const guchar *) contents, length);
|
||||||
|
ret = gdk_pixbuf_save (pixbuf, filename, "png", error,
|
||||||
|
"tEXt::Software", "Hello my name is dave",
|
||||||
|
"icc-profile", contents_encode,
|
||||||
|
NULL);
|
||||||
|
len = strlen (contents_encode);
|
||||||
|
g_debug ("ICC profile was %i bytes", len);
|
||||||
|
out:
|
||||||
|
g_free (contents);
|
||||||
|
g_free (contents_encode);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
save_image_verify (const gchar *filename, GError **error)
|
||||||
|
{
|
||||||
|
gboolean ret = FALSE;
|
||||||
|
GdkPixbuf *pixbuf = NULL;
|
||||||
|
const gchar *option;
|
||||||
|
gchar *icc_profile = NULL;
|
||||||
|
gsize len = 0;
|
||||||
|
|
||||||
|
/* load */
|
||||||
|
pixbuf = gdk_pixbuf_new_from_file (filename, error);
|
||||||
|
if (pixbuf == NULL)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
/* check values */
|
||||||
|
option = gdk_pixbuf_get_option (pixbuf, "icc-profile");
|
||||||
|
if (option == NULL) {
|
||||||
|
*error = g_error_new (1, 0, "no profile set");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* decode base64 */
|
||||||
|
icc_profile = (gchar *) g_base64_decode (option, &len);
|
||||||
|
if (len != ICC_PROFILE_SIZE) {
|
||||||
|
*error = g_error_new (1, 0, "profile length invalid, got %i", len);
|
||||||
|
g_file_set_contents ("error.icc", icc_profile, len, NULL);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
ret = TRUE;
|
||||||
|
out:
|
||||||
|
if (pixbuf != NULL)
|
||||||
|
g_object_unref (pixbuf);
|
||||||
|
g_free (icc_profile);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char **argv)
|
||||||
|
{
|
||||||
|
GdkWindow *root;
|
||||||
|
GdkPixbuf *pixbuf;
|
||||||
|
gboolean ret;
|
||||||
|
gint retval = 1;
|
||||||
|
GError *error = NULL;
|
||||||
|
|
||||||
|
gtk_init (&argc, &argv);
|
||||||
|
|
||||||
|
gtk_widget_set_default_colormap (gdk_rgb_get_colormap ());
|
||||||
|
|
||||||
|
root = gdk_get_default_root_window ();
|
||||||
|
pixbuf = gdk_pixbuf_get_from_drawable (NULL, root, NULL,
|
||||||
|
0, 0, 0, 0, 150, 160);
|
||||||
|
|
||||||
|
/* PASS */
|
||||||
|
g_debug ("try to save PNG with a profile");
|
||||||
|
ret = save_image_png ("icc-profile.png", pixbuf, &error);
|
||||||
|
if (!ret) {
|
||||||
|
g_warning ("FAILED: did not save image: %s", error->message);
|
||||||
|
g_error_free (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PASS */
|
||||||
|
g_debug ("try to load PNG and get color attributes");
|
||||||
|
ret = save_image_verify ("icc-profile.png", &error);
|
||||||
|
if (!ret) {
|
||||||
|
g_warning ("FAILED: did not load image: %s", error->message);
|
||||||
|
g_error_free (error);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* success */
|
||||||
|
retval = 0;
|
||||||
|
g_debug ("ALL OKAY!");
|
||||||
|
out:
|
||||||
|
return retval;
|
||||||
|
}
|
@ -1902,6 +1902,21 @@ gdk_pixbuf_real_save_to_callback (GdkPixbuf *pixbuf,
|
|||||||
* be specified using the "compression" parameter; it's value is in an
|
* be specified using the "compression" parameter; it's value is in an
|
||||||
* integer in the range of [0,9].
|
* integer in the range of [0,9].
|
||||||
*
|
*
|
||||||
|
* ICC color profiles can also be embedded into PNG images.
|
||||||
|
* The "icc-profile" value should be the complete ICC profile encoded
|
||||||
|
* into base64.
|
||||||
|
*
|
||||||
|
* <informalexample><programlisting>
|
||||||
|
* gchar *contents;
|
||||||
|
* gchar *contents_encode;
|
||||||
|
* gsize length;
|
||||||
|
* g_file_get_contents ("/home/hughsie/.color/icc/L225W.icm", &contents, &length, NULL);
|
||||||
|
* contents_encode = g_base64_encode ((const guchar *) contents, length);
|
||||||
|
* gdk_pixbuf_save (pixbuf, handle, "png", &error,
|
||||||
|
* "icc-profile", contents_encode,
|
||||||
|
* NULL);
|
||||||
|
* </programlisting></informalexample>
|
||||||
|
*
|
||||||
* TIFF images recognize a "compression" option which acceps an integer value.
|
* TIFF images recognize a "compression" option which acceps an integer value.
|
||||||
* Among the codecs are 1 None, 2 Huffman, 5 LZW, 7 JPEG and 8 Deflate, see
|
* Among the codecs are 1 None, 2 Huffman, 5 LZW, 7 JPEG and 8 Deflate, see
|
||||||
* the libtiff documentation and tiff.h for all supported codec values.
|
* the libtiff documentation and tiff.h for all supported codec values.
|
||||||
|
@ -258,6 +258,12 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
|
|||||||
gint num_texts;
|
gint num_texts;
|
||||||
gchar *key;
|
gchar *key;
|
||||||
gchar *value;
|
gchar *value;
|
||||||
|
gchar *icc_profile_base64;
|
||||||
|
const gchar *icc_profile_title;
|
||||||
|
const gchar *icc_profile;
|
||||||
|
guint icc_profile_size;
|
||||||
|
guint32 retval;
|
||||||
|
gint compression_type;
|
||||||
|
|
||||||
#ifdef PNG_USER_MEM_SUPPORTED
|
#ifdef PNG_USER_MEM_SUPPORTED
|
||||||
png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
|
png_ptr = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
|
||||||
@ -332,6 +338,18 @@ gdk_pixbuf__png_image_load (FILE *f, GError **error)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(PNG_cHRM_SUPPORTED)
|
||||||
|
/* Extract embedded ICC profile */
|
||||||
|
retval = png_get_iCCP (png_ptr, info_ptr,
|
||||||
|
(png_charpp) &icc_profile_title, &compression_type,
|
||||||
|
(png_charpp) &icc_profile, (png_uint_32*) &icc_profile_size);
|
||||||
|
if (retval != 0) {
|
||||||
|
icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, icc_profile_size);
|
||||||
|
gdk_pixbuf_set_option (pixbuf, "icc-profile", icc_profile_base64);
|
||||||
|
g_free (icc_profile_base64);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
g_free (rows);
|
g_free (rows);
|
||||||
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
|
png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
|
||||||
|
|
||||||
@ -586,6 +604,12 @@ png_info_callback (png_structp png_read_ptr,
|
|||||||
int i, num_texts;
|
int i, num_texts;
|
||||||
int color_type;
|
int color_type;
|
||||||
gboolean have_alpha = FALSE;
|
gboolean have_alpha = FALSE;
|
||||||
|
gchar *icc_profile_base64;
|
||||||
|
const gchar *icc_profile_title;
|
||||||
|
const gchar *icc_profile;
|
||||||
|
guint icc_profile_size;
|
||||||
|
guint32 retval;
|
||||||
|
gint compression_type;
|
||||||
|
|
||||||
lc = png_get_progressive_ptr(png_read_ptr);
|
lc = png_get_progressive_ptr(png_read_ptr);
|
||||||
|
|
||||||
@ -651,6 +675,18 @@ png_info_callback (png_structp png_read_ptr,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(PNG_cHRM_SUPPORTED)
|
||||||
|
/* Extract embedded ICC profile */
|
||||||
|
retval = png_get_iCCP (png_read_ptr, png_info_ptr,
|
||||||
|
(png_charpp) &icc_profile_title, &compression_type,
|
||||||
|
(png_charpp) &icc_profile, (png_uint_32*) &icc_profile_size);
|
||||||
|
if (retval != 0) {
|
||||||
|
icc_profile_base64 = g_base64_encode ((const guchar *) icc_profile, icc_profile_size);
|
||||||
|
gdk_pixbuf_set_option (lc->pixbuf, "icc-profile", icc_profile_base64);
|
||||||
|
g_free (icc_profile_base64);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Notify the client that we are ready to go */
|
/* Notify the client that we are ready to go */
|
||||||
|
|
||||||
if (lc->prepare_func)
|
if (lc->prepare_func)
|
||||||
@ -791,7 +827,7 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
GdkPixbufSaveFunc save_func,
|
GdkPixbufSaveFunc save_func,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
png_structp png_ptr;
|
png_structp png_ptr = NULL;
|
||||||
png_infop info_ptr;
|
png_infop info_ptr;
|
||||||
png_textp text_ptr = NULL;
|
png_textp text_ptr = NULL;
|
||||||
guchar *ptr;
|
guchar *ptr;
|
||||||
@ -806,6 +842,8 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
int num_keys;
|
int num_keys;
|
||||||
int compression = -1;
|
int compression = -1;
|
||||||
gboolean success = TRUE;
|
gboolean success = TRUE;
|
||||||
|
guchar *icc_profile = NULL;
|
||||||
|
gsize icc_profile_size = 0;
|
||||||
SaveToFunctionIoPtr to_callback_ioptr;
|
SaveToFunctionIoPtr to_callback_ioptr;
|
||||||
|
|
||||||
num_keys = 0;
|
num_keys = 0;
|
||||||
@ -823,7 +861,8 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
GDK_PIXBUF_ERROR,
|
GDK_PIXBUF_ERROR,
|
||||||
GDK_PIXBUF_ERROR_BAD_OPTION,
|
GDK_PIXBUF_ERROR_BAD_OPTION,
|
||||||
_("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
|
_("Keys for PNG text chunks must have at least 1 and at most 79 characters."));
|
||||||
return FALSE;
|
success = FALSE;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
for (i = 0; i < len; i++) {
|
for (i = 0; i < len; i++) {
|
||||||
if ((guchar) key[i] > 127) {
|
if ((guchar) key[i] > 127) {
|
||||||
@ -831,10 +870,24 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
GDK_PIXBUF_ERROR,
|
GDK_PIXBUF_ERROR,
|
||||||
GDK_PIXBUF_ERROR_BAD_OPTION,
|
GDK_PIXBUF_ERROR_BAD_OPTION,
|
||||||
_("Keys for PNG text chunks must be ASCII characters."));
|
_("Keys for PNG text chunks must be ASCII characters."));
|
||||||
return FALSE;
|
success = FALSE;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
num_keys++;
|
num_keys++;
|
||||||
|
} else if (strcmp (*kiter, "icc-profile") == 0) {
|
||||||
|
/* decode from base64 */
|
||||||
|
icc_profile = g_base64_decode (*viter, &icc_profile_size);
|
||||||
|
if (icc_profile_size < 127) {
|
||||||
|
/* This is a user-visible error */
|
||||||
|
g_set_error (error,
|
||||||
|
GDK_PIXBUF_ERROR,
|
||||||
|
GDK_PIXBUF_ERROR_BAD_OPTION,
|
||||||
|
_("Color profile has invalid length '%d'."),
|
||||||
|
icc_profile_size);
|
||||||
|
success = FALSE;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
} else if (strcmp (*kiter, "compression") == 0) {
|
} else if (strcmp (*kiter, "compression") == 0) {
|
||||||
char *endptr = NULL;
|
char *endptr = NULL;
|
||||||
compression = strtol (*viter, &endptr, 10);
|
compression = strtol (*viter, &endptr, 10);
|
||||||
@ -845,7 +898,8 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
GDK_PIXBUF_ERROR_BAD_OPTION,
|
GDK_PIXBUF_ERROR_BAD_OPTION,
|
||||||
_("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
|
_("PNG compression level must be a value between 0 and 9; value '%s' could not be parsed."),
|
||||||
*viter);
|
*viter);
|
||||||
return FALSE;
|
success = FALSE;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
if (compression < 0 || compression > 9) {
|
if (compression < 0 || compression > 9) {
|
||||||
/* This is a user-visible error;
|
/* This is a user-visible error;
|
||||||
@ -857,7 +911,8 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
GDK_PIXBUF_ERROR_BAD_OPTION,
|
GDK_PIXBUF_ERROR_BAD_OPTION,
|
||||||
_("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
|
_("PNG compression level must be a value between 0 and 9; value '%d' is not allowed."),
|
||||||
compression);
|
compression);
|
||||||
return FALSE;
|
success = FALSE;
|
||||||
|
goto cleanup;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
g_warning ("Unrecognized parameter (%s) passed to PNG saver.", *kiter);
|
g_warning ("Unrecognized parameter (%s) passed to PNG saver.", *kiter);
|
||||||
@ -947,6 +1002,15 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
if (compression >= 0)
|
if (compression >= 0)
|
||||||
png_set_compression_level (png_ptr, compression);
|
png_set_compression_level (png_ptr, compression);
|
||||||
|
|
||||||
|
#if defined(PNG_iCCP_SUPPORTED)
|
||||||
|
/* the proper ICC profile title is encoded in the profile */
|
||||||
|
if (icc_profile != NULL) {
|
||||||
|
png_set_iCCP (png_ptr, info_ptr,
|
||||||
|
"ICC profile", PNG_COMPRESSION_TYPE_BASE,
|
||||||
|
(gchar*) icc_profile, icc_profile_size);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (has_alpha) {
|
if (has_alpha) {
|
||||||
png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
|
png_set_IHDR (png_ptr, info_ptr, w, h, bpc,
|
||||||
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
|
PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
|
||||||
@ -975,9 +1039,12 @@ static gboolean real_save_png (GdkPixbuf *pixbuf,
|
|||||||
png_write_end (png_ptr, info_ptr);
|
png_write_end (png_ptr, info_ptr);
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
|
if (png_ptr != NULL)
|
||||||
png_destroy_write_struct (&png_ptr, &info_ptr);
|
png_destroy_write_struct (&png_ptr, &info_ptr);
|
||||||
|
|
||||||
if (num_keys > 0) {
|
g_free (icc_profile);
|
||||||
|
|
||||||
|
if (text_ptr != NULL) {
|
||||||
for (i = 0; i < num_keys; i++)
|
for (i = 0; i < num_keys; i++)
|
||||||
g_free (text_ptr[i].text);
|
g_free (text_ptr[i].text);
|
||||||
g_free (text_ptr);
|
g_free (text_ptr);
|
||||||
|
Loading…
Reference in New Issue
Block a user