[cairo] API Shakeup: remove png backend

Kristian Høgsberg krh at bitplanet.net
Sun Feb 27 11:51:38 PST 2005


Hi,

We've been discussing dropping the PNG backend for a while, and I 
believe that we've reached consensus that it makes sense to phase it out 
in favor of a couple of PNG helper functions.  The attached patch plus 
removal of cairo_png_surface.c implements this.

Here's what I'm proposing for writing an image surface to an PNG file:

   /**
    * cairo_image_surface_write_png:
    * @surface: a #cairo_surface_t, this must be an image surface
    * @file: a #FILE opened in write mode
    *
    * Writes the image surface to the given #FILE pointer.  The file
    * should be opened in write mode and binary mode if applicable.
    *
    * Return value: CAIRO_STATUS_SUCCESS if the PNG file was written
    * successfully.  Otherwise, CAIRO_STATUS_NO_MEMORY is returned if
    * memory could not be allocated for the operation,
    * CAIRO_STATUS_NULL_POINTER if the surface is not an image surface.
    **/
   cairo_status_t
   cairo_image_surface_write_png (cairo_surface_t *surface,
                                  FILE            *file);

I'm thinking we should have a write callback base function as well:

   cairo_status_t
   cairo_image_surface_write_png_with_callback (cairo_surface_t *surface,
                                                cairo_write_func_t write,
                                                void *closure);

but I didn't implement that just yet.

What error do we return if the surface isn't an image surface?

Given that we're already linking to libpng, it makes sense to also 
provide functions to create an image surface from a  PNG file:

   /**
    * cairo_image_surface_create_from_png:
    * @file: a #FILE
    * @width: if not %NULL, the width of the image surface is written to
    *   this address
    * @height: if not %NULL, the height of the image surface is written
    *   to this address
    *
    * Creates a new image surface and initializes the contents to the
    * given PNG file.  If width or height are not %NULL the dimensions of
    * the image surface will be written to those addresses.
    *
    * Return value: a new #cairo_surface_t initialized with the contents
    * of the PNG file or %NULL if the file is not a valid PNG file or
    * memory could not be allocated for the operation.
    **/
   cairo_surface_t *
   cairo_image_surface_create_from_png (FILE *file,
                                        int  *width,
                                        int  *height);

There's a problem here, that the function can fail if the file is not a 
PNG file.  The only means we have of reporting error is returning NULL, 
since we want it to follow the same convention as the other 
constructors.  I was thinking of something like:

   cairo_surface_t *
   cairo_image_surface_create_from_png (FILE *file,
                                        int *width, int *height,
                                        cairo_status_t *status);

but that feels awkward, and we would have to introduce a 
CAIRO_STATUS_INVALID_PNG_FILE error code... or maybe 
CAIRO_STATUS_READ_ERROR would be sufficient.  Suggestions?

cheers,
Kristian

-------------- next part --------------
Index: ChangeLog
===================================================================
RCS file: /cvs/cairo/cairo/ChangeLog,v
retrieving revision 1.373
diff -u -p -r1.373 ChangeLog
--- ChangeLog	16 Feb 2005 17:34:47 -0000	1.373
+++ ChangeLog	27 Feb 2005 19:06:33 -0000
@@ -1,3 +1,8 @@
+2005-02-27  Kristian Høgsberg  <krh at redhat.com>
+
+	* src/cairo-png.c (cairo_image_surface_write_png) 
+	(cairo_image_surface_create_from_png): New PNG utility functions.
+
 2005-02-16  Kristian Høgsberg  <krh at redhat.com>
 
 	Patches from Mike Owens <etc at filespanker.com>:
Index: configure.in
===================================================================
RCS file: /cvs/cairo/cairo/configure.in,v
retrieving revision 1.82
diff -u -p -r1.82 configure.in
--- configure.in	6 Feb 2005 01:17:19 -0000	1.82
+++ configure.in	27 Feb 2005 19:06:34 -0000
@@ -189,7 +189,7 @@ AC_SUBST(PS_LIBS)
 dnl ===========================================================================
 
 AC_ARG_ENABLE(png,
-  [  --disable-png           Disable cairo's PNG backend],
+  [  --disable-png           Disable cairo's PNG functions],
   [use_png=$enableval], [use_png=yes])
 
 if test "x$use_png" = "xyes"; then
@@ -212,17 +212,17 @@ if test "x$use_png" = "xyes"; then
 fi
 
 if test "x$use_png" != "xyes"; then
-  PNG_SURFACE_FEATURE=CAIRO_HAS_NO_PNG_SURFACE
-  AM_CONDITIONAL(CAIRO_HAS_PNG_SURFACE, false)
+  PNG_FUNCTIONS_FEATURE=CAIRO_HAS_NO_PNG_FUNCTIONS
+  AM_CONDITIONAL(CAIRO_HAS_PNG_FUNCTIONS, false)
 else
-  PNG_SURFACE_FEATURE=CAIRO_HAS_PNG_SURFACE
-  AM_CONDITIONAL(CAIRO_HAS_PNG_SURFACE, true)
+  PNG_FUNCTIONS_FEATURE=CAIRO_HAS_PNG_FUNCTIONS
+  AM_CONDITIONAL(CAIRO_HAS_PNG_FUNCTIONS, true)
 fi
 
 CAIRO_CFLAGS="$CAIRO_CFLAGS $PNG_CFLAGS"
 CAIRO_LIBS="$CAIRO_LIBS $PNG_LIBS"
 
-AC_SUBST(PNG_SURFACE_FEATURE)
+AC_SUBST(PNG_FUNCTIONS_FEATURE)
 AC_SUBST(PNG_REQUIRES)
 
 dnl ===========================================================================
@@ -433,12 +433,12 @@ echo "  XCB: $use_xcb"
 echo "  Win32: $use_win32"
 echo "  PostScript: $use_ps"
 echo "  PDF: $use_pdf"
-echo "  PNG: $use_png"
 echo "  glitz: $use_glitz"
 echo ""
-echo "and the following font backends:"
+echo "the following font backends:"
 echo "  FreeType: $use_freetype"
 echo "  Win32: false"
 echo "  ATSUI: $use_atsui"
 echo ""
-
+echo "and the following features:"
+echo "  PNG functions: $use_png"
Index: src/Makefile.am
===================================================================
RCS file: /cvs/cairo/cairo/src/Makefile.am,v
retrieving revision 1.39
diff -u -p -r1.39 Makefile.am
--- src/Makefile.am	4 Feb 2005 16:04:36 -0000	1.39
+++ src/Makefile.am	27 Feb 2005 19:06:35 -0000
@@ -9,9 +9,9 @@ libcairo_pdf_headers = cairo-pdf.h
 libcairo_pdf_sources = cairo_pdf_surface.c
 endif
 
-if CAIRO_HAS_PNG_SURFACE
+if CAIRO_HAS_PNG_FUNCTIONS
 libcairo_png_headers = cairo-png.h
-libcairo_png_sources = cairo_png_surface.c
+libcairo_png_sources = cairo-png.c
 endif
 
 if CAIRO_HAS_XLIB_SURFACE
Index: src/cairo-features.h.in
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo-features.h.in,v
retrieving revision 1.15
diff -u -p -r1.15 cairo-features.h.in
--- src/cairo-features.h.in	1 Feb 2005 00:11:37 -0000	1.15
+++ src/cairo-features.h.in	27 Feb 2005 19:06:35 -0000
@@ -41,8 +41,6 @@
 
 #define @PDF_SURFACE_FEATURE@
 
-#define @PNG_SURFACE_FEATURE@
-
 #define @XLIB_SURFACE_FEATURE@
 
 #define @QUARTZ_SURFACE_FEATURE@
@@ -59,6 +57,8 @@
 
 #define @ATSUI_FONT_FEATURE@
 
+#define @PNG_FUNCTIONS_FEATURE@
+
 #define @SANITY_CHECKING_FEATURE@
 
 #endif
Index: src/cairo-png.h
===================================================================
RCS file: /cvs/cairo/cairo/src/cairo-png.h,v
retrieving revision 1.2
diff -u -p -r1.2 cairo-png.h
--- src/cairo-png.h	21 Jan 2005 22:33:48 -0000	1.2
+++ src/cairo-png.h	27 Feb 2005 19:06:35 -0000
@@ -31,7 +31,7 @@
  * California.
  *
  * Contributor(s):
- *	Carl D. Worth <cworth at isi.edu>
+ *	Carl D. Worth <cworth at cworth.org>
  */
 
 #ifndef CAIRO_PNG_H
@@ -39,26 +39,22 @@
 
 #include <cairo.h>
 
-#ifdef CAIRO_HAS_PNG_SURFACE
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
 
 #include <stdio.h>
 
 CAIRO_BEGIN_DECLS
 
-void
-cairo_set_target_png (cairo_t	*cr,
-		      FILE	*file,
-		      cairo_format_t	format,
-		      int	       	width,
-		      int		height);
+cairo_status_t
+cairo_image_surface_write_png (cairo_surface_t	*surface,
+			       FILE		 *file);
 
 cairo_surface_t *
-cairo_png_surface_create (FILE			*file,
-			  cairo_format_t	format,
-			  int			width,
-			  int			height);
+cairo_image_surface_create_from_png (FILE	*file,
+				     int	*width,
+				     int	*height);
 
 CAIRO_END_DECLS
 
-#endif /* CAIRO_HAS_PNG_SURFACE */
+#endif /* CAIRO_HAS_PNG_FUNCTIONS */
 #endif /* CAIRO_PNG_H */
--- /dev/null	2005-02-27 06:47:58.851243336 -0500
+++ src/cairo-png.c	2005-02-27 14:06:22.000000000 -0500
@@ -0,0 +1,327 @@
+/* cairo - a vector graphics library with display and print output
+ *
+ * Copyright © 2003 University of Southern California
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it either under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation
+ * (the "LGPL") or, at your option, under the terms of the Mozilla
+ * Public License Version 1.1 (the "MPL"). If you do not alter this
+ * notice, a recipient may use your version of this file under either
+ * the MPL or the LGPL.
+ *
+ * You should have received a copy of the LGPL along with this library
+ * in the file COPYING-LGPL-2.1; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * You should have received a copy of the MPL along with this library
+ * in the file COPYING-MPL-1.1
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
+ * OF ANY KIND, either express or implied. See the LGPL or the MPL for
+ * the specific language governing rights and limitations.
+ *
+ * The Original Code is the cairo graphics library.
+ *
+ * The Initial Developer of the Original Code is University of Southern
+ * California.
+ *
+ * Contributor(s):
+ *	Carl D. Worth <cworth at cworth.org>
+ *	Kristian Høgsberg <krh at redhat.com>
+ */
+
+#include <png.h>
+#include "cairo-png.h"
+#include "cairoint.h"
+
+
+static void
+unpremultiply_data (png_structp png, png_row_infop row_info, png_bytep data)
+{
+    int i;
+
+    for (i = 0; i < row_info->rowbytes; i += 4) {
+        unsigned char *b = &data[i];
+        unsigned int pixel;
+        unsigned char alpha;
+
+	memcpy (&pixel, b, sizeof (unsigned int));
+	alpha = (pixel & 0xff000000) >> 24;
+        if (alpha == 0) {
+	    b[0] = b[1] = b[2] = b[3] = 0;
+	} else {
+            b[0] = (((pixel & 0x0000ff) >>  0) * 255 + alpha / 2) / alpha;
+            b[1] = (((pixel & 0x00ff00) >>  8) * 255 + alpha / 2) / alpha;
+            b[2] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
+	    b[3] = alpha;
+	}
+    }
+}
+
+/**
+ * cairo_image_surface_write_png:
+ * @surface: a #cairo_surface_t, this must be an image surface
+ * @file: a #FILE opened in write mode
+ * 
+ * Writes the image surface to the given #FILE pointer.  The file
+ * should be opened in write mode and binary mode if applicable.
+ * 
+ * Return value: CAIRO_STATUS_SUCCESS if the PNG file was written
+ * successfully.  Otherwise, CAIRO_STATUS_NO_MEMORY is returned if
+ * memory could not be allocated for the operation,
+ * CAIRO_STATUS_NULL_POINTER if the surface is not an image surface.
+ **/
+cairo_status_t
+cairo_image_surface_write_png (cairo_surface_t *surface, FILE *file)
+{
+    int i;
+    cairo_status_t status = CAIRO_STATUS_SUCCESS;
+    cairo_image_surface_t *image = (cairo_image_surface_t *) surface;
+    png_struct *png;
+    png_info *info;
+    png_byte **rows;
+    png_color_16 white;
+    int png_color_type;
+    int depth;
+
+    if (!_cairo_surface_is_image (surface))
+	return CAIRO_STATUS_NULL_POINTER;
+
+    rows = malloc (image->height * sizeof(png_byte*));
+    if (rows == NULL)
+	return CAIRO_STATUS_NO_MEMORY;
+
+    for (i = 0; i < image->height; i++)
+	rows[i] = image->data + i * image->stride;
+
+    png = png_create_write_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+    if (png == NULL) {
+	status = CAIRO_STATUS_NO_MEMORY;
+	goto BAIL1;
+    }
+
+    info = png_create_info_struct (png);
+    if (info == NULL) {
+	status = CAIRO_STATUS_NO_MEMORY;
+	goto BAIL2;
+    }
+
+    if (setjmp (png_jmpbuf (png))) {
+	status = CAIRO_STATUS_NO_MEMORY;
+	goto BAIL2;
+    }
+    
+    png_init_io (png, file);
+
+    switch (image->format) {
+    case CAIRO_FORMAT_ARGB32:
+	depth = 8;
+	png_color_type = PNG_COLOR_TYPE_RGB_ALPHA;
+	break;
+    case CAIRO_FORMAT_RGB24:
+	depth = 8;
+	png_color_type = PNG_COLOR_TYPE_RGB;
+	break;
+    case CAIRO_FORMAT_A8:
+	depth = 8;
+	png_color_type = PNG_COLOR_TYPE_GRAY;
+	break;
+    case CAIRO_FORMAT_A1:
+	depth = 1;
+	png_color_type = PNG_COLOR_TYPE_GRAY;
+	break;
+    default:
+	status = CAIRO_STATUS_NULL_POINTER;
+	goto BAIL2;
+    }
+
+    png_set_IHDR (png, info,
+		  image->width,
+		  image->height, depth,
+		  png_color_type,
+		  PNG_INTERLACE_NONE,
+		  PNG_COMPRESSION_TYPE_DEFAULT,
+		  PNG_FILTER_TYPE_DEFAULT);
+
+    white.red = 0xff;
+    white.blue = 0xff;
+    white.green = 0xff;
+    png_set_bKGD (png, info, &white);
+
+/* XXX: Setting the time is interfereing with the image comparison
+    png_convert_from_time_t (&png_time, time (NULL));
+    png_set_tIME (png, info, &png_time);
+*/
+
+    png_set_write_user_transform_fn (png, unpremultiply_data);
+    if (image->format == CAIRO_FORMAT_ARGB32 ||
+	image->format == CAIRO_FORMAT_RGB24)
+	png_set_bgr (png);
+    if (image->format == CAIRO_FORMAT_RGB24)
+	png_set_filler (png, 0, PNG_FILLER_AFTER);
+
+    png_write_info (png, info);
+    png_write_image (png, rows);
+    png_write_end (png, info);
+
+BAIL2:
+    png_destroy_write_struct (&png, &info);
+BAIL1:
+    free (rows);
+
+    return status;
+}
+
+static void
+premultiply_data (png_structp   png,
+                  png_row_infop row_info,
+                  png_bytep     data)
+{
+    int i;
+
+    for (i = 0; i < row_info->rowbytes; i += 4) {
+	unsigned char  *base = &data[i];
+	unsigned char  blue = base[0];
+	unsigned char  green = base[1];
+	unsigned char  red = base[2];
+	unsigned char  alpha = base[3];
+	unsigned long	p;
+
+	red = ((unsigned) red * (unsigned) alpha + 127) / 255;
+	green = ((unsigned) green * (unsigned) alpha + 127) / 255;
+	blue = ((unsigned) blue * (unsigned) alpha + 127) / 255;
+	p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+	memcpy (base, &p, sizeof (unsigned long));
+    }
+}
+
+/**
+ * cairo_image_surface_create_from_png:
+ * @file: a #FILE 
+ * @width: if not %NULL, the width of the image surface is written to
+ *   this address
+ * @height: if not %NULL, the height of the image surface is written to
+ *   this address
+ * 
+ * Creates a new image surface and initializes the contents to the
+ * given PNG file.  If width or height are not %NULL the dimensions of
+ * the image surface will be written to those addresses.
+ * 
+ * Return value: a new #cairo_surface_t initialized with the contents
+ * of the PNG file or %NULL if the file is not a valid PNG file or
+ * memory could not be allocated for the operation.
+ **/
+cairo_surface_t *
+cairo_image_surface_create_from_png (FILE *file, int *width, int *height)
+{
+    png_byte *data;
+    int i;
+    static const int PNG_SIG_SIZE = 8;
+    unsigned char png_sig[PNG_SIG_SIZE];
+    int sig_bytes;
+    png_struct *png;
+    png_info *info;
+    png_uint_32 png_width, png_height, stride;
+    int depth, color_type, interlace;
+    unsigned int pixel_size;
+    png_byte **row_pointers;
+
+    sig_bytes = fread (png_sig, 1, PNG_SIG_SIZE, file);
+    if (png_check_sig (png_sig, sig_bytes) == 0)
+	goto BAIL1; /* FIXME: ERROR_NOT_PNG */
+
+    /* XXX: Perhaps we'll want some other error handlers? */
+    png = png_create_read_struct (PNG_LIBPNG_VER_STRING,
+                                  NULL,
+                                  NULL,
+                                  NULL);
+    if (png == NULL)
+	goto BAIL1;
+
+    info = png_create_info_struct (png);
+    if (info == NULL)
+	goto BAIL2;
+
+    png_init_io (png, file);
+    png_set_sig_bytes (png, sig_bytes);
+
+    png_read_info (png, info);
+
+    png_get_IHDR (png, info,
+                  &png_width, &png_height, &depth,
+                  &color_type, &interlace, NULL, NULL);
+    stride = 4 * png_width;
+
+    /* convert palette/gray image to rgb */
+    if (color_type == PNG_COLOR_TYPE_PALETTE)
+        png_set_palette_to_rgb (png);
+
+    /* expand gray bit depth if needed */
+    if (color_type == PNG_COLOR_TYPE_GRAY && depth < 8)
+        png_set_gray_1_2_4_to_8 (png);
+    /* transform transparency to alpha */
+    if (png_get_valid(png, info, PNG_INFO_tRNS))
+        png_set_tRNS_to_alpha (png);
+
+    if (depth == 16)
+        png_set_strip_16 (png);
+
+    if (depth < 8)
+        png_set_packing (png);
+
+    /* convert grayscale to RGB */
+    if (color_type == PNG_COLOR_TYPE_GRAY
+        || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+        png_set_gray_to_rgb (png);
+
+    if (interlace != PNG_INTERLACE_NONE)
+        png_set_interlace_handling (png);
+
+    png_set_bgr (png);
+    png_set_filler (png, 0xff, PNG_FILLER_AFTER);
+
+    png_set_read_user_transform_fn (png, premultiply_data);
+
+    png_read_update_info (png, info);
+
+    pixel_size = 4;
+    data = malloc (png_width * png_height * pixel_size);
+    if (data == NULL)
+	goto BAIL2;
+
+    row_pointers = malloc (png_height * sizeof(char *));
+    if (row_pointers == NULL)
+	goto BAIL3;
+
+    for (i = 0; i < png_height; i++)
+        row_pointers[i] = &data[i * png_width * pixel_size];
+
+    png_read_image (png, row_pointers);
+    png_read_end (png, info);
+
+    free (row_pointers);
+    png_destroy_read_struct (&png, &info, NULL);
+    fclose (file);
+
+    if (width != NULL)
+	*width = png_width;
+    if (height != NULL)
+	*height = png_height;
+
+    return cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32,
+						png_width, png_height, stride);
+
+ BAIL3:
+    free (data);
+ BAIL2:
+    png_destroy_read_struct (&png, NULL, NULL);
+ BAIL1:
+    fclose (file);
+
+    return NULL;
+}


More information about the cairo mailing list