[libgxps] Support ImageBrush Viewbox and rotation/shearing matrices



commit e20cd4bb51b8c2c36faf213b2e3c91d6c8434581
Author: Jason Crain <jason aquaticape us>
Date:   Mon Jul 27 22:52:32 2015 -0500

    Support ImageBrush Viewbox and rotation/shearing matrices
    
    This modifies ImageBrush handling to support clipping based on the
    Viewbox and multiplies the transform matrix to support rotation or
    shearing transformations.
    
    Viewbox is given in units of 1/96 of an inch, which means it depends
    on the image resolution.  This modifies image loading (including using
    libpng instead of cairo) in order to capture the resolution and wraps
    it in a GXPSImage struct.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=751357

 configure.ac                |    3 +
 libgxps/gxps-brush.c        |   71 ++++----
 libgxps/gxps-images.c       |  430 ++++++++++++++++++++++++++++++++++++-------
 libgxps/gxps-images.h       |   15 ++-
 libgxps/gxps-page-private.h |   11 +-
 libgxps/gxps-page.c         |   22 +-
 6 files changed, 435 insertions(+), 117 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index bb3f24d..6a44364 100644
--- a/configure.ac
+++ b/configure.ac
@@ -185,6 +185,9 @@ dnl libpng
 PKG_CHECK_MODULES(LIBPNG, libpng, [have_libpng="yes"], [have_libpng="no"])
 
 AM_CONDITIONAL(HAVE_LIBPNG, test x$have_libpng = xyes)
+if test x$have_libpng = xyes; then
+   AC_DEFINE(HAVE_LIBPNG, [1], [Have PNG library])
+fi
 
 AC_SUBST(LIBPNG_CFLAGS)
 AC_SUBST(LIBPNG_LIBS)
diff --git a/libgxps/gxps-brush.c b/libgxps/gxps-brush.c
index 5997ba1..7c403ab 100644
--- a/libgxps/gxps-brush.c
+++ b/libgxps/gxps-brush.c
@@ -947,55 +947,60 @@ brush_end_element (GMarkupParseContext  *context,
         } else if (strcmp (element_name, "RadialGradientBrush") == 0) {
                 g_markup_parse_context_pop (context);
         } else if (strcmp (element_name, "ImageBrush") == 0) {
-                GXPSBrushImage  *image;
-                cairo_surface_t *surface;
+                GXPSBrushImage  *brush_image;
+                GXPSImage       *image;
                 GError          *err = NULL;
 
-                image = g_markup_parse_context_pop (context);
+                brush_image = g_markup_parse_context_pop (context);
 
                 GXPS_DEBUG (g_message ("set_fill_pattern (image)"));
-                surface = gxps_page_get_image (brush->ctx->page, image->image_uri, &err);
-                if (surface) {
-                        cairo_matrix_t matrix, port_matrix;
-                        gdouble        x_scale, y_scale;
-
-                        image->brush->pattern = cairo_pattern_create_for_surface (surface);
-                        cairo_pattern_set_extend (image->brush->pattern, image->extend);
-
-                        cairo_matrix_init (&port_matrix,
-                                           image->viewport.width,
-                                           0, 0,
-                                           image->viewport.height,
-                                           image->viewport.x,
-                                           image->viewport.y);
-                        cairo_matrix_multiply (&port_matrix, &port_matrix, &image->matrix);
-
-                        x_scale = cairo_image_surface_get_width (surface) / port_matrix.xx;
-                        y_scale = cairo_image_surface_get_height (surface) / port_matrix.yy;
+                image = gxps_page_get_image (brush->ctx->page, brush_image->image_uri, &err);
+                if (image) {
+                        cairo_matrix_t   matrix;
+                        gdouble          x_scale, y_scale;
+                        cairo_surface_t *clip_surface;
+
+                        /* viewbox units is 1/96 inch, convert to pixels */
+                        brush_image->viewbox.x *= image->res_x / 96;
+                        brush_image->viewbox.y *= image->res_y / 96;
+                        brush_image->viewbox.width *= image->res_x / 96;
+                        brush_image->viewbox.height *= image->res_y / 96;
+
+                        clip_surface = cairo_surface_create_for_rectangle (image->surface,
+                                                                           brush_image->viewbox.x,
+                                                                           brush_image->viewbox.y,
+                                                                           brush_image->viewbox.width,
+                                                                           brush_image->viewbox.height);
+                        brush_image->brush->pattern = cairo_pattern_create_for_surface (clip_surface);
+                        cairo_pattern_set_extend (brush_image->brush->pattern, brush_image->extend);
+
+                        x_scale = brush_image->viewport.width / brush_image->viewbox.width;
+                        y_scale = brush_image->viewport.height / brush_image->viewbox.height;
                         cairo_matrix_init (&matrix, x_scale, 0, 0, y_scale,
-                                           -port_matrix.x0 * x_scale,
-                                           -port_matrix.y0 * y_scale);
-                        cairo_pattern_set_matrix (image->brush->pattern, &matrix);
+                                           brush_image->viewport.x, brush_image->viewport.y);
+                        cairo_matrix_multiply (&matrix, &matrix, &brush_image->matrix);
+                        cairo_matrix_invert (&matrix);
+                        cairo_pattern_set_matrix (brush_image->brush->pattern, &matrix);
 
                         if (brush->opacity != 1.0) {
                                 cairo_push_group (brush->ctx->cr);
-                                cairo_set_source (brush->ctx->cr, image->brush->pattern);
-                                cairo_pattern_destroy (image->brush->pattern);
+                                cairo_set_source (brush->ctx->cr, brush_image->brush->pattern);
+                                cairo_pattern_destroy (brush_image->brush->pattern);
                                 cairo_paint_with_alpha (brush->ctx->cr, brush->opacity);
-                                image->brush->pattern = cairo_pop_group (brush->ctx->cr);
+                                brush_image->brush->pattern = cairo_pop_group (brush->ctx->cr);
                         }
 
-                        if (cairo_pattern_status (image->brush->pattern)) {
-                                GXPS_DEBUG (g_debug ("%s", cairo_status_to_string (cairo_pattern_status 
(image->brush->pattern))));
-                                cairo_pattern_destroy (image->brush->pattern);
-                                image->brush->pattern = NULL;
+                        if (cairo_pattern_status (brush_image->brush->pattern)) {
+                                GXPS_DEBUG (g_debug ("%s", cairo_status_to_string (cairo_pattern_status 
(brush_image->brush->pattern))));
+                                cairo_pattern_destroy (brush_image->brush->pattern);
+                                brush_image->brush->pattern = NULL;
                         }
-                        cairo_surface_destroy (surface);
+                        cairo_surface_destroy (clip_surface);
                 } else if (err) {
                         GXPS_DEBUG (g_debug ("%s", err->message));
                         g_error_free (err);
                 }
-                gxps_brush_image_free (image);
+                gxps_brush_image_free (brush_image);
         } else if (strcmp (element_name, "VisualBrush") == 0) {
                 GXPSRenderContext *sub_ctx;
                 GXPSBrushVisual   *visual;
diff --git a/libgxps/gxps-images.c b/libgxps/gxps-images.c
index 8629b2b..7d62508 100644
--- a/libgxps/gxps-images.c
+++ b/libgxps/gxps-images.c
@@ -23,6 +23,10 @@
 #include <stdlib.h>
 #include <string.h>
 
+#ifdef HAVE_LIBPNG
+#include <png.h>
+#endif
+
 #ifdef HAVE_LIBJPEG
 #include <jpeglib.h>
 #include <setjmp.h>
@@ -36,27 +40,144 @@
 #include "gxps-error.h"
 #include "gxps-debug.h"
 
+#define METERS_PER_INCH 0.0254
+#define CENTIMETERS_PER_INCH 2.54
+
 /* PNG */
-static cairo_status_t
-_read_png (GInputStream *stream,
-          guchar       *data,
-          guint         len)
+#ifdef HAVE_LIBPNG
+
+static const cairo_user_data_key_t image_data_cairo_key;
+
+static void
+_read_png (png_structp png_ptr,
+          png_bytep data,
+          png_size_t len)
 {
-       gssize bytes_read;
+       GInputStream *stream;
 
-       bytes_read = g_input_stream_read (stream, data, len, NULL, NULL);
+       stream = png_get_io_ptr (png_ptr);
+       g_input_stream_read (stream, data, len, NULL, NULL);
+}
 
-       return (bytes_read > 0 && bytes_read == len) ?
-               CAIRO_STATUS_SUCCESS : CAIRO_STATUS_READ_ERROR;
+static void
+png_error_callback (png_structp png_ptr,
+                   png_const_charp error_msg)
+{
+       char **msg;
+
+       msg = png_get_error_ptr (png_ptr);
+       *msg = g_strdup (error_msg);
+       longjmp (png_jmpbuf (png_ptr), 1);
+}
+
+static void
+png_warning_callback (png_structp png,
+                     png_const_charp error_msg)
+{
+}
+
+/* From cairo's cairo-png.c <http://cairographics.org> */
+static inline int
+multiply_alpha (int alpha, int color)
+{
+       int temp = (alpha * color) + 0x80;
+
+       return ((temp + (temp >> 8)) >> 8);
+}
+
+/* Premultiplies data and converts RGBA bytes => native endian
+ * From cairo's cairo-png.c <http://cairographics.org> */
+static void
+premultiply_data (png_structp   png,
+                  png_row_infop row_info,
+                  png_bytep     data)
+{
+       unsigned int i;
+
+       for (i = 0; i < row_info->rowbytes; i += 4) {
+               uint8_t *base  = &data[i];
+               uint8_t  alpha = base[3];
+               uint32_t p;
+
+               if (alpha == 0) {
+                       p = 0;
+               } else {
+                       uint8_t red   = base[0];
+                       uint8_t green = base[1];
+                       uint8_t blue  = base[2];
+
+                       if (alpha != 0xff) {
+                               red   = multiply_alpha (alpha, red);
+                               green = multiply_alpha (alpha, green);
+                               blue  = multiply_alpha (alpha, blue);
+                       }
+                       p = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
+               }
+               memcpy (base, &p, sizeof (uint32_t));
+       }
 }
 
-static cairo_surface_t *
+/* Converts RGBx bytes to native endian xRGB
+ * From cairo's cairo-png.c <http://cairographics.org> */
+static void
+convert_bytes_to_data (png_structp png, png_row_infop row_info, png_bytep data)
+{
+       unsigned int i;
+
+       for (i = 0; i < row_info->rowbytes; i += 4) {
+               uint8_t *base  = &data[i];
+               uint8_t  red   = base[0];
+               uint8_t  green = base[1];
+               uint8_t  blue  = base[2];
+               uint32_t pixel;
+
+               pixel = (0xff << 24) | (red << 16) | (green << 8) | (blue << 0);
+               memcpy (base, &pixel, sizeof (uint32_t));
+       }
+}
+
+static void
+fill_png_error (GError      **error,
+               const gchar  *image_uri,
+               const gchar  *msg)
+{
+       if (msg) {
+               g_set_error (error,
+                            GXPS_ERROR,
+                            GXPS_ERROR_IMAGE,
+                            "Error loading PNG image %s: %s",
+                            image_uri, msg);
+       } else {
+               g_set_error (error,
+                            GXPS_ERROR,
+                            GXPS_ERROR_IMAGE,
+                            "Error loading PNG image %s",
+                            image_uri);
+       }
+}
+
+#endif /* HAVE_LIBPNG */
+
+/* Adapted from cairo's read_png in cairo-png.c
+ * http://cairographics.org/ */
+static GXPSImage *
 gxps_images_create_from_png (GXPSArchive *zip,
                             const gchar *image_uri,
                             GError     **error)
 {
-       GInputStream    *stream;
-       cairo_surface_t *surface;
+#ifdef HAVE_LIBPNG
+       GInputStream  *stream;
+       GXPSImage     *image = NULL;
+       char          *png_err_msg = NULL;
+       png_struct    *png;
+       png_info      *info;
+       png_byte      *data = NULL;
+       png_byte     **row_pointers = NULL;
+       png_uint_32    png_width, png_height;
+       int            depth, color_type, interlace, stride;
+       unsigned int   i;
+       cairo_format_t format;
+       cairo_status_t status;
 
        stream = gxps_archive_open (zip, image_uri);
        if (!stream) {
@@ -68,21 +189,154 @@ gxps_images_create_from_png (GXPSArchive *zip,
                return NULL;
        }
 
-       surface = cairo_image_surface_create_from_png_stream ((cairo_read_func_t)_read_png, stream);
+       png = png_create_read_struct (PNG_LIBPNG_VER_STRING,
+                                     &png_err_msg,
+                                     png_error_callback,
+                                     png_warning_callback);
+       if (png == NULL) {
+               fill_png_error (error, image_uri, NULL);
+               g_object_unref (stream);
+               return NULL;
+       }
+
+       info = png_create_info_struct (png);
+       if (info == NULL) {
+               fill_png_error (error, image_uri, NULL);
+               g_object_unref (stream);
+               png_destroy_read_struct (&png, NULL, NULL);
+               return NULL;
+       }
+
+       png_set_read_fn (png, stream, _read_png);
+
+       if (setjmp (png_jmpbuf (png))) {
+               fill_png_error (error, image_uri, png_err_msg);
+               g_free (png_err_msg);
+               g_object_unref (stream);
+               png_destroy_read_struct (&png, &info, NULL);
+               gxps_image_free (image);
+               g_free (row_pointers);
+               g_free (data);
+               return NULL;
+       }
+
+       png_read_info (png, info);
+
+       png_get_IHDR (png, info,
+                     &png_width, &png_height, &depth,
+                     &color_type, &interlace, NULL, NULL);
+
+       /* 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)
+               png_set_expand_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_filler (png, 0xff, PNG_FILLER_AFTER);
+
+       /* recheck header after setting EXPAND options */
+       png_read_update_info (png, info);
+       png_get_IHDR (png, info,
+                     &png_width, &png_height, &depth,
+                     &color_type, &interlace, NULL, NULL);
+       if (depth != 8 ||
+           !(color_type == PNG_COLOR_TYPE_RGB ||
+              color_type == PNG_COLOR_TYPE_RGB_ALPHA)) {
+               fill_png_error (error, image_uri, NULL);
+               g_object_unref (stream);
+               png_destroy_read_struct (&png, &info, NULL);
+               return NULL;
+       }
+
+       switch (color_type) {
+       default:
+               g_assert_not_reached();
+               /* fall-through just in case ;-) */
+
+       case PNG_COLOR_TYPE_RGB_ALPHA:
+               format = CAIRO_FORMAT_ARGB32;
+               png_set_read_user_transform_fn (png, premultiply_data);
+               break;
+
+       case PNG_COLOR_TYPE_RGB:
+               format = CAIRO_FORMAT_RGB24;
+               png_set_read_user_transform_fn (png, convert_bytes_to_data);
+               break;
+       }
+
+       stride = cairo_format_stride_for_width (format, png_width);
+       if (stride < 0) {
+               fill_png_error (error, image_uri, NULL);
+               g_object_unref (stream);
+               png_destroy_read_struct (&png, &info, NULL);
+               return NULL;
+       }
+
+       image = g_slice_new0 (GXPSImage);
+       image->res_x = png_get_x_pixels_per_meter (png, info) * METERS_PER_INCH;
+       if (image->res_x == 0)
+               image->res_x = 96;
+       image->res_y = png_get_y_pixels_per_meter (png, info) * METERS_PER_INCH;
+       if (image->res_y == 0)
+               image->res_y = 96;
+
+       data = g_malloc (png_height * stride);
+       row_pointers = g_new (png_byte *, png_height);
+
+       for (i = 0; i < png_height; i++)
+               row_pointers[i] = &data[i * stride];
+
+       png_read_image (png, row_pointers);
+       png_read_end (png, info);
+       png_destroy_read_struct (&png, &info, NULL);
        g_object_unref (stream);
-       if (cairo_surface_status (surface)) {
-               g_set_error (error,
-                            GXPS_ERROR,
-                            GXPS_ERROR_IMAGE,
-                            "Error loading PNG image %s: %s",
-                            image_uri,
-                            cairo_status_to_string (cairo_surface_status (surface)));
-               cairo_surface_destroy (surface);
+       g_free (row_pointers);
+
+       image->surface = cairo_image_surface_create_for_data (data, format,
+                                                             png_width, png_height,
+                                                             stride);
+       if (cairo_surface_status (image->surface)) {
+               fill_png_error (error, image_uri, NULL);
+               gxps_image_free (image);
+               g_free (data);
+               return NULL;
+       }
 
+       status = cairo_surface_set_user_data (image->surface,
+                                             &image_data_cairo_key,
+                                             data,
+                                             (cairo_destroy_func_t) g_free);
+       if (status) {
+               fill_png_error (error, image_uri, NULL);
+               gxps_image_free (image);
+               g_free (data);
                return NULL;
        }
 
-       return surface;
+       return image;
+#else
+    return NULL;
+#endif  /* HAVE_LIBPNG */
 }
 
 /* JPEG */
@@ -164,7 +418,7 @@ _jpeg_error_exit (j_common_ptr error)
 }
 #endif /* HAVE_LIBJPEG */
 
-static cairo_surface_t *
+static GXPSImage *
 gxps_images_create_from_jpeg (GXPSArchive *zip,
                              const gchar *image_uri,
                              GError     **error)
@@ -174,7 +428,7 @@ gxps_images_create_from_jpeg (GXPSArchive *zip,
        struct jpeg_error_mgr         error_mgr;
        struct jpeg_decompress_struct cinfo;
        struct _jpeg_src_mgr          src;
-       cairo_surface_t              *surface;
+       GXPSImage                    *image;
        guchar                       *data;
        gint                          stride;
        JSAMPARRAY                    lines;
@@ -225,25 +479,28 @@ gxps_images_create_from_jpeg (GXPSArchive *zip,
        cinfo.do_fancy_upsampling = FALSE;
        jpeg_start_decompress (&cinfo);
 
-       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
-                                             cinfo.output_width,
-                                             cinfo.output_height);
-       if (cairo_surface_status (surface)) {
+       image = g_slice_new (GXPSImage);
+       image->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+                                                    cinfo.output_width,
+                                                    cinfo.output_height);
+       image->res_x = 96;
+       image->res_y = 96;
+       if (cairo_surface_status (image->surface)) {
                g_set_error (error,
                             GXPS_ERROR,
                             GXPS_ERROR_IMAGE,
                             "Error loading JPEG image %s: %s",
                             image_uri,
-                            cairo_status_to_string (cairo_surface_status (surface)));
+                            cairo_status_to_string (cairo_surface_status (image->surface)));
                jpeg_destroy_decompress (&cinfo);
-               cairo_surface_destroy (surface);
+               gxps_image_free (image);
                g_object_unref (stream);
 
                return NULL;
        }
 
-       data = cairo_image_surface_get_data (surface);
-       stride = cairo_image_surface_get_stride (surface);
+       data = cairo_image_surface_get_data (image->surface);
+       stride = cairo_image_surface_get_stride (image->surface);
        jpeg_stride = cinfo.output_width * cinfo.out_color_components;
        lines = cinfo.mem->alloc_sarray((j_common_ptr) &cinfo, JPOOL_IMAGE, jpeg_stride, 4);
 
@@ -273,7 +530,7 @@ gxps_images_create_from_jpeg (GXPSArchive *zip,
                                        GXPS_DEBUG (g_message ("Unsupported jpeg color space %s",
                                                                _jpeg_color_space_name 
(cinfo.out_color_space)));
 
-                                       cairo_surface_destroy (surface);
+                                       gxps_image_free (image);
                                        jpeg_destroy_decompress (&cinfo);
                                        g_object_unref (stream);
                                        return NULL;
@@ -286,25 +543,33 @@ gxps_images_create_from_jpeg (GXPSArchive *zip,
                }
        }
 
+       if (cinfo.density_unit == 1) { /* dots/inch */
+               image->res_x = cinfo.X_density;
+               image->res_y = cinfo.Y_density;
+       } else if (cinfo.density_unit == 2) { /* dots/cm */
+               image->res_x = cinfo.X_density * CENTIMETERS_PER_INCH;
+               image->res_y = cinfo.Y_density * CENTIMETERS_PER_INCH;
+       }
+
        jpeg_finish_decompress (&cinfo);
        jpeg_destroy_decompress (&cinfo);
        g_object_unref (stream);
 
-       cairo_surface_mark_dirty (surface);
+       cairo_surface_mark_dirty (image->surface);
 
-       if (cairo_surface_status (surface)) {
+       if (cairo_surface_status (image->surface)) {
                g_set_error (error,
                             GXPS_ERROR,
                             GXPS_ERROR_IMAGE,
                             "Error loading JPEG image %s: %s",
                             image_uri,
-                            cairo_status_to_string (cairo_surface_status (surface)));
-               cairo_surface_destroy (surface);
+                            cairo_status_to_string (cairo_surface_status (image->surface)));
+               gxps_image_free (image);
 
                return NULL;
        }
 
-       return surface;
+       return image;
 #else
        return NULL;
 #endif /* HAVE_LIBJPEG */
@@ -457,19 +722,21 @@ _tiff_unmap_file (thandle_t handle,
 }
 #endif /* #ifdef HAVE_LIBTIFF */
 
-static cairo_surface_t *
+static GXPSImage *
 gxps_images_create_from_tiff (GXPSArchive *zip,
                              const gchar *image_uri,
                              GError     **error)
 {
 #ifdef HAVE_LIBTIFF
-       TIFF            *tiff;
-       TiffBuffer       buffer;
-       cairo_surface_t *surface;
-       gint             width, height;
-       gint             stride;
-       guchar          *data;
-       guchar          *p;
+       TIFF       *tiff;
+       TiffBuffer  buffer;
+       GXPSImage  *image;
+       gint        width, height;
+       guint16     res_unit;
+       float       res_x, res_y;
+       gint        stride;
+       guchar     *data;
+       guchar     *p;
 
        if (!gxps_archive_read_entry (zip, image_uri,
                                      &buffer.buffer,
@@ -529,28 +796,49 @@ gxps_images_create_from_tiff (GXPSArchive *zip,
                return NULL;
        }
 
-       surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
-                                             width, height);
-       if (cairo_surface_status (surface)) {
+       image = g_slice_new (GXPSImage);
+       image->surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+                                                    width, height);
+       image->res_x = 96;
+       image->res_y = 96;
+
+       if (!TIFFGetField (tiff, TIFFTAG_RESOLUTIONUNIT, &res_unit))
+               res_unit = 0;
+       if (TIFFGetField (tiff, TIFFTAG_XRESOLUTION, &res_x)) {
+               if (res_unit == 2) { /* inches */
+                       image->res_x = res_x;
+               } else if (res_unit == 3) { /* centimeters */
+                       image->res_x = res_x * CENTIMETERS_PER_INCH;
+               }
+       }
+       if (TIFFGetField (tiff, TIFFTAG_YRESOLUTION, &res_y)) {
+               if (res_unit == 2) { /* inches */
+                       image->res_y = res_y;
+               } else if (res_unit == 3) { /* centimeters */
+                       image->res_y = res_y * CENTIMETERS_PER_INCH;
+               }
+       }
+
+       if (cairo_surface_status (image->surface)) {
                g_set_error (error,
                             GXPS_ERROR,
                             GXPS_ERROR_IMAGE,
                             "Error loading TIFF image %s: %s",
                             image_uri,
-                            cairo_status_to_string (cairo_surface_status (surface)));
-               cairo_surface_destroy (surface);
+                            cairo_status_to_string (cairo_surface_status (image->surface)));
+               gxps_image_free (image);
                TIFFClose (tiff);
                _tiff_pop_handlers ();
                g_free (buffer.buffer);
                return NULL;
        }
 
-       data = cairo_image_surface_get_data (surface);
+       data = cairo_image_surface_get_data (image->surface);
        if (!TIFFReadRGBAImageOriented (tiff, width, height,
                                        (uint32 *)data,
                                        ORIENTATION_TOPLEFT, 1) || _tiff_error) {
                fill_tiff_error (error, image_uri);
-               cairo_surface_destroy (surface);
+               gxps_image_free (image);
                TIFFClose (tiff);
                _tiff_pop_handlers ();
                g_free (buffer.buffer);
@@ -561,7 +849,7 @@ gxps_images_create_from_tiff (GXPSArchive *zip,
        _tiff_pop_handlers ();
        g_free (buffer.buffer);
 
-       stride = cairo_image_surface_get_stride (surface);
+       stride = cairo_image_surface_get_stride (image->surface);
        p = data;
        while (p < data + (height * stride)) {
                guint32 *pixel = (guint32 *)p;
@@ -575,9 +863,9 @@ gxps_images_create_from_tiff (GXPSArchive *zip,
                p += 4;
        }
 
-       cairo_surface_mark_dirty (surface);
+       cairo_surface_mark_dirty (image->surface);
 
-       return surface;
+       return image;
 #else
        return NULL;
 #endif /* #ifdef HAVE_LIBTIFF */
@@ -603,43 +891,55 @@ gxps_images_guess_content_type (GXPSArchive *zip,
        return mime_type;
 }
 
-cairo_surface_t *
+GXPSImage *
 gxps_images_get_image (GXPSArchive *zip,
                       const gchar *image_uri,
                       GError     **error)
 {
-       cairo_surface_t *surface = NULL;
+       GXPSImage *image = NULL;
 
        /* First try with extensions,
         * as it's recommended by the spec
         * (2.1.5 Image Parts)
         */
        if (g_str_has_suffix (image_uri, ".png")) {
-               surface = gxps_images_create_from_png (zip, image_uri, error);
+               image = gxps_images_create_from_png (zip, image_uri, error);
        } else if (g_str_has_suffix (image_uri, ".jpg")) {
-               surface = gxps_images_create_from_jpeg (zip, image_uri, error);
+               image = gxps_images_create_from_jpeg (zip, image_uri, error);
        } else if (g_str_has_suffix (image_uri, ".tif")) {
-               surface = gxps_images_create_from_tiff (zip, image_uri, error);
+               image = gxps_images_create_from_tiff (zip, image_uri, error);
        } else if (g_str_has_suffix (image_uri, "wdp")) {
                GXPS_DEBUG (g_message ("Unsupported image format windows media photo"));
                return NULL;
        }
 
-       if (!surface) {
+       if (!image) {
                gchar *mime_type;
 
                mime_type = gxps_images_guess_content_type (zip, image_uri);
                if (g_strcmp0 (mime_type, "image/png") == 0) {
-                       surface = gxps_images_create_from_png (zip, image_uri, error);
+                       image = gxps_images_create_from_png (zip, image_uri, error);
                } else if (g_strcmp0 (mime_type, "image/jpeg") == 0) {
-                       surface = gxps_images_create_from_jpeg (zip, image_uri, error);
+                       image = gxps_images_create_from_jpeg (zip, image_uri, error);
                } else if (g_strcmp0 (mime_type, "image/tiff") == 0) {
-                       surface = gxps_images_create_from_tiff (zip, image_uri, error);
+                       image = gxps_images_create_from_tiff (zip, image_uri, error);
                } else {
                        GXPS_DEBUG (g_message ("Unsupported image format: %s", mime_type));
                }
                g_free (mime_type);
        }
 
-       return surface;
+       return image;
+}
+
+void
+gxps_image_free (GXPSImage *image)
+{
+       if (G_UNLIKELY (!image))
+               return;
+
+       if (G_LIKELY (image->surface))
+               cairo_surface_destroy (image->surface);
+
+       g_slice_free (GXPSImage, image);
 }
diff --git a/libgxps/gxps-images.h b/libgxps/gxps-images.h
index 9102603..81cfdc9 100644
--- a/libgxps/gxps-images.h
+++ b/libgxps/gxps-images.h
@@ -25,9 +25,18 @@
 
 G_BEGIN_DECLS
 
-cairo_surface_t *gxps_images_get_image (GXPSArchive *zip,
-                                       const gchar *image_uri,
-                                       GError     **error);
+typedef struct _GXPSImage GXPSImage;
+
+struct _GXPSImage {
+       cairo_surface_t *surface;
+       double           res_x;
+       double           res_y;
+};
+
+GXPSImage *gxps_images_get_image (GXPSArchive  *zip,
+                                  const gchar  *image_uri,
+                                  GError      **error);
+void       gxps_image_free       (GXPSImage    *image);
 
 G_END_DECLS
 
diff --git a/libgxps/gxps-page-private.h b/libgxps/gxps-page-private.h
index 3303410..9dab52c 100644
--- a/libgxps/gxps-page-private.h
+++ b/libgxps/gxps-page-private.h
@@ -25,6 +25,7 @@
 
 #include "gxps-page.h"
 #include "gxps-archive.h"
+#include "gxps-images.h"
 
 G_BEGIN_DECLS
 
@@ -57,11 +58,11 @@ struct _GXPSRenderContext {
         GXPSBrushVisual *visual;
 };
 
-cairo_surface_t *gxps_page_get_image          (GXPSPage            *page,
-                                               const gchar         *image_uri,
-                                               GError             **error);
-void             gxps_page_render_parser_push (GMarkupParseContext *context,
-                                               GXPSRenderContext   *ctx);
+GXPSImage *gxps_page_get_image          (GXPSPage            *page,
+                                         const gchar         *image_uri,
+                                         GError             **error);
+void       gxps_page_render_parser_push (GMarkupParseContext *context,
+                                         GXPSRenderContext   *ctx);
 
 G_END_DECLS
 
diff --git a/libgxps/gxps-page.c b/libgxps/gxps-page.c
index 66c6228..4e4837b 100644
--- a/libgxps/gxps-page.c
+++ b/libgxps/gxps-page.c
@@ -75,35 +75,35 @@ gxps_page_error_quark (void)
 }
 
 /* Images */
-cairo_surface_t *
+GXPSImage *
 gxps_page_get_image (GXPSPage    *page,
                     const gchar *image_uri,
                     GError     **error)
 {
-       cairo_surface_t *surface;
+       GXPSImage *image;
 
        if (page->priv->image_cache) {
-               surface = g_hash_table_lookup (page->priv->image_cache,
-                                              image_uri);
-               if (surface)
-                       return cairo_surface_reference (surface);
+               image = g_hash_table_lookup (page->priv->image_cache,
+                                            image_uri);
+               if (image)
+                       return image;
        }
 
-       surface = gxps_images_get_image (page->priv->zip, image_uri, error);
-       if (!surface)
+       image = gxps_images_get_image (page->priv->zip, image_uri, error);
+       if (!image)
                return NULL;
 
        if (!page->priv->image_cache) {
                page->priv->image_cache = g_hash_table_new_full (g_str_hash,
                                                                 g_str_equal,
                                                                 (GDestroyNotify)g_free,
-                                                                (GDestroyNotify)cairo_surface_destroy);
+                                                                (GDestroyNotify)gxps_image_free);
        }
 
        g_hash_table_insert (page->priv->image_cache,
                             g_strdup (image_uri),
-                            cairo_surface_reference (surface));
-       return surface;
+                            image);
+       return image;
 }
 
 /* FixedPage parser */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]