[gdk-pixbuf] Add _new_from_bytes() and _read_pixels() API, handle read-only pixbufs



commit 862e3890f54aaeb8589a6ee4c146f1fb50556004
Author: Colin Walters <walters verbum org>
Date:   Sat Jul 5 11:15:20 2014 -0400

    Add _new_from_bytes() and _read_pixels() API, handle read-only pixbufs
    
    GdkPixbuf is an old API that predates introspection and GBytes.  It
    has some confusion around whether or not pixbuf data is mutable or
    not.  The _new_from_data() API takes a *const* pointer, but it's not
    copied, and _get_pixels() returns a non-const copy of the same
    pointer.
    
    There are several cases where we get read-only data, such as a
    GResource.  For language bindings, _new_from_data() doesn't work
    because the array may be a temporary copy only allocated for the call.
    
    In order to support a clean _new_from_bytes() API, we need to add the
    concept of a read-only pixbuf into the core.  The fundamental hack
    here is that _get_pixels() now causes an implicit copy.
    
    For the cases where we don't want to copy, add a new
    gdk_pixbuf_read_pixels() that returns a proper const pointer.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=732297

 gdk-pixbuf/gdk-pixbuf-core.h       |   10 ++
 gdk-pixbuf/gdk-pixbuf-data.c       |   50 +++++++++++-
 gdk-pixbuf/gdk-pixbuf-private.h    |    3 +
 gdk-pixbuf/gdk-pixbuf-scale.c      |   76 +++++++++++++----
 gdk-pixbuf/gdk-pixbuf-util.c       |   19 +++--
 gdk-pixbuf/gdk-pixbuf.c            |  116 ++++++++++++++++++++------
 gdk-pixbuf/gdk-pixbuf.symbols      |    2 +
 tests/Makefile.am                  |    7 ++
 tests/pixbuf-readonly-to-mutable.c |  159 ++++++++++++++++++++++++++++++++++++
 9 files changed, 390 insertions(+), 52 deletions(-)
---
diff --git a/gdk-pixbuf/gdk-pixbuf-core.h b/gdk-pixbuf/gdk-pixbuf-core.h
index 025a8ae..9f11d87 100644
--- a/gdk-pixbuf/gdk-pixbuf-core.h
+++ b/gdk-pixbuf/gdk-pixbuf-core.h
@@ -241,6 +241,9 @@ gsize         gdk_pixbuf_get_byte_length     (const GdkPixbuf *pixbuf);
 guchar       *gdk_pixbuf_get_pixels_with_length (const GdkPixbuf *pixbuf,
                                                  guint           *length);
 
+const guint8* gdk_pixbuf_read_pixels    (const GdkPixbuf  *pixbuf);
+                                             
+
 
 
 
@@ -298,6 +301,13 @@ GdkPixbuf *gdk_pixbuf_new_from_data (const guchar *data,
                                     GdkPixbufDestroyNotify destroy_fn,
                                     gpointer destroy_fn_data);
 
+GdkPixbuf *gdk_pixbuf_new_from_bytes (GBytes *data,
+                                     GdkColorspace colorspace,
+                                     gboolean has_alpha,
+                                     int bits_per_sample,
+                                     int width, int height,
+                                     int rowstride);
+ 
 GdkPixbuf *gdk_pixbuf_new_from_xpm_data (const char **data);
 GdkPixbuf* gdk_pixbuf_new_from_inline  (gint          data_length,
                                         const guint8 *data,
diff --git a/gdk-pixbuf/gdk-pixbuf-data.c b/gdk-pixbuf/gdk-pixbuf-data.c
index d9c2291..769ead3 100644
--- a/gdk-pixbuf/gdk-pixbuf-data.c
+++ b/gdk-pixbuf/gdk-pixbuf-data.c
@@ -41,7 +41,15 @@
  * 
  * Creates a new #GdkPixbuf out of in-memory image data.  Currently only RGB
  * images with 8 bits per sample are supported.
- * 
+ *
+ * Since you are providing a pre-allocated pixel buffer, you must also
+ * specify a way to free that data.  This is done with a function of
+ * type #GdkPixbufDestroyNotify.  When a pixbuf created with is
+ * finalized, your destroy notification function will be called, and
+ * it is its responsibility to free the pixel array.
+ *
+ * See also gdk_pixbuf_new_from_bytes().
+ *
  * Return value: (transfer full): A newly-created #GdkPixbuf structure with a reference count of 1.
  **/
 GdkPixbuf *
@@ -75,3 +83,43 @@ gdk_pixbuf_new_from_data (const guchar *data, GdkColorspace colorspace, gboolean
 
        return pixbuf;
 }
+
+/**
+ * gdk_pixbuf_new_from_bytes:
+ * @data: Image data in 8-bit/sample packed format inside a #GBytes
+ * @colorspace: Colorspace for the image data
+ * @has_alpha: Whether the data has an opacity channel
+ * @bits_per_sample: Number of bits per sample
+ * @width: Width of the image in pixels, must be > 0
+ * @height: Height of the image in pixels, must be > 0
+ * @rowstride: Distance in bytes between row starts
+ * 
+ * Creates a new #GdkPixbuf out of in-memory readonly image data.
+ * Currently only RGB images with 8 bits per sample are supported.
+ * This is the #GBytes variant of gdk_pixbuf_new_from_data().
+ *
+ * Return value: (transfer full): A newly-created #GdkPixbuf structure with a reference count of 1.
+ * Since: 2.32
+ **/
+GdkPixbuf *
+gdk_pixbuf_new_from_bytes (GBytes *data, GdkColorspace colorspace, gboolean has_alpha,
+                          int bits_per_sample, int width, int height, int rowstride)
+{
+       g_return_val_if_fail (data != NULL, NULL);
+       g_return_val_if_fail (colorspace == GDK_COLORSPACE_RGB, NULL);
+       g_return_val_if_fail (bits_per_sample == 8, NULL);
+       g_return_val_if_fail (width > 0, NULL);
+       g_return_val_if_fail (height > 0, NULL);
+       g_return_val_if_fail (g_bytes_get_size (data) >= width * height * (has_alpha ? 4 : 3), NULL);
+
+       return (GdkPixbuf*) g_object_new (GDK_TYPE_PIXBUF, 
+                                         "pixel-bytes", data,
+                                         "colorspace", colorspace,
+                                         "n-channels", has_alpha ? 4 : 3,
+                                         "bits-per-sample", bits_per_sample,
+                                         "has-alpha", has_alpha ? TRUE : FALSE,
+                                         "width", width,
+                                         "height", height,
+                                         "rowstride", rowstride,
+                                         NULL);
+}
diff --git a/gdk-pixbuf/gdk-pixbuf-private.h b/gdk-pixbuf/gdk-pixbuf-private.h
index 7c88d43..e209694 100644
--- a/gdk-pixbuf/gdk-pixbuf-private.h
+++ b/gdk-pixbuf/gdk-pixbuf-private.h
@@ -73,6 +73,9 @@ struct _GdkPixbuf {
        /* User data for the destroy notification function */
        gpointer destroy_fn_data;
 
+        /* Replaces "pixels" member (and destroy notify) */
+        GBytes *bytes;
+
        /* Do we have an alpha channel? */
        guint has_alpha : 1;
 };
diff --git a/gdk-pixbuf/gdk-pixbuf-scale.c b/gdk-pixbuf/gdk-pixbuf-scale.c
index d6d4392..4288c65 100644
--- a/gdk-pixbuf/gdk-pixbuf-scale.c
+++ b/gdk-pixbuf/gdk-pixbuf-scale.c
@@ -46,6 +46,8 @@
  * given size, scale an original image to fit, and then return the
  * new pixbuf.
  * 
+ * If the destination pixbuf was created from a readonly source, these
+ * operations will force a copy into a mutable buffer.
  * 
  * Scaling and compositing functions take advantage of MMX hardware
  * acceleration on systems where MMX is supported.  If gdk-pixbuf is built
@@ -136,6 +138,9 @@ gdk_pixbuf_scale (const GdkPixbuf *src,
                  double           scale_y,
                  GdkInterpType    interp_type)
 {
+  const guint8 *src_pixels;
+  guint8 *dest_pixels;
+
   g_return_if_fail (GDK_IS_PIXBUF (src));
   g_return_if_fail (GDK_IS_PIXBUF (dest));
   g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width);
@@ -144,8 +149,12 @@ gdk_pixbuf_scale (const GdkPixbuf *src,
   offset_x = floor (offset_x + 0.5);
   offset_y = floor (offset_y + 0.5);
 
-  _pixops_scale (dest->pixels, dest->width, dest->height, dest->rowstride,
-                 dest->n_channels, dest->has_alpha, src->pixels, src->width,
+  /* Force an implicit copy */
+  dest_pixels = gdk_pixbuf_get_pixels (dest);
+  src_pixels = gdk_pixbuf_read_pixels (src);
+
+  _pixops_scale (dest_pixels, dest->width, dest->height, dest->rowstride,
+                 dest->n_channels, dest->has_alpha, src_pixels, src->width,
                  src->height, src->rowstride, src->n_channels, src->has_alpha,
                  dest_x, dest_y, dest_width, dest_height, offset_x, offset_y,
                  scale_x, scale_y, (PixopsInterpType)interp_type);
@@ -193,6 +202,9 @@ gdk_pixbuf_composite (const GdkPixbuf *src,
                      GdkInterpType    interp_type,
                      int              overall_alpha)
 {
+  const guint8 *src_pixels;
+  guint8 *dest_pixels;
+
   g_return_if_fail (GDK_IS_PIXBUF (src));
   g_return_if_fail (GDK_IS_PIXBUF (dest));
   g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width);
@@ -202,8 +214,12 @@ gdk_pixbuf_composite (const GdkPixbuf *src,
   offset_x = floor (offset_x + 0.5);
   offset_y = floor (offset_y + 0.5);
 
-  _pixops_composite (dest->pixels, dest->width, dest->height, dest->rowstride,
-                     dest->n_channels, dest->has_alpha, src->pixels,
+  /* Force an implicit copy */
+  dest_pixels = gdk_pixbuf_get_pixels (dest);
+  src_pixels = gdk_pixbuf_read_pixels (src);
+
+  _pixops_composite (dest_pixels, dest->width, dest->height, dest->rowstride,
+                     dest->n_channels, dest->has_alpha, src_pixels,
                      src->width, src->height, src->rowstride, src->n_channels,
                      src->has_alpha, dest_x, dest_y, dest_width, dest_height,
                      offset_x, offset_y, scale_x, scale_y,
@@ -260,6 +276,9 @@ gdk_pixbuf_composite_color (const GdkPixbuf *src,
                            guint32          color1,
                            guint32          color2)
 {
+  const guint8 *src_pixels;
+  guint8 *dest_pixels;
+
   g_return_if_fail (GDK_IS_PIXBUF (src));
   g_return_if_fail (GDK_IS_PIXBUF (dest));
   g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width);
@@ -269,9 +288,13 @@ gdk_pixbuf_composite_color (const GdkPixbuf *src,
   offset_x = floor (offset_x + 0.5);
   offset_y = floor (offset_y + 0.5);
   
-  _pixops_composite_color (dest->pixels, dest_width, dest_height,
+  /* Force an implicit copy */
+  dest_pixels = gdk_pixbuf_get_pixels (dest);
+  src_pixels = gdk_pixbuf_read_pixels (src);
+
+  _pixops_composite_color (dest_pixels, dest_width, dest_height,
                           dest->rowstride, dest->n_channels, dest->has_alpha,
-                          src->pixels, src->width, src->height,
+                          src_pixels, src->width, src->height,
                           src->rowstride, src->n_channels, src->has_alpha,
                           dest_x, dest_y, dest_width, dest_height, offset_x,
                           offset_y, scale_x, scale_y,
@@ -392,10 +415,15 @@ GdkPixbuf *
 gdk_pixbuf_rotate_simple (const GdkPixbuf   *src,
                          GdkPixbufRotation  angle)
 {
+  const guint8 *src_pixels;
+  guint8 *dest_pixels;
   GdkPixbuf *dest;
-  guchar *p, *q;
+  const guchar *p;
+  guchar *q;
   gint x, y;
 
+  src_pixels = gdk_pixbuf_read_pixels (src);
+
   switch (angle % 360)
     {
     case 0:
@@ -410,12 +438,14 @@ gdk_pixbuf_rotate_simple (const GdkPixbuf   *src,
       if (!dest)
        return NULL;
 
+      dest_pixels = gdk_pixbuf_get_pixels (dest);
+
       for (y = 0; y < src->height; y++) 
        { 
          for (x = 0; x < src->width; x++) 
            { 
-             p = src->pixels + OFFSET (src, x, y); 
-             q = dest->pixels + OFFSET (dest, y, src->width - x - 1); 
+             p = src_pixels + OFFSET (src, x, y); 
+             q = dest_pixels + OFFSET (dest, y, src->width - x - 1); 
              memcpy (q, p, dest->n_channels);
            }
        } 
@@ -429,12 +459,14 @@ gdk_pixbuf_rotate_simple (const GdkPixbuf   *src,
       if (!dest)
        return NULL;
 
+      dest_pixels = gdk_pixbuf_get_pixels (dest);
+
       for (y = 0; y < src->height; y++) 
        { 
          for (x = 0; x < src->width; x++) 
            { 
-             p = src->pixels + OFFSET (src, x, y); 
-             q = dest->pixels + OFFSET (dest, src->width - x - 1, src->height - y - 1); 
+             p = src_pixels + OFFSET (src, x, y); 
+             q = dest_pixels + OFFSET (dest, src->width - x - 1, src->height - y - 1); 
              memcpy (q, p, dest->n_channels);
            }
        } 
@@ -448,12 +480,14 @@ gdk_pixbuf_rotate_simple (const GdkPixbuf   *src,
       if (!dest)
        return NULL;
 
+      dest_pixels = gdk_pixbuf_get_pixels (dest);
+
       for (y = 0; y < src->height; y++) 
        { 
          for (x = 0; x < src->width; x++) 
            { 
-             p = src->pixels + OFFSET (src, x, y); 
-             q = dest->pixels + OFFSET (dest, src->height - y - 1, x); 
+             p = src_pixels + OFFSET (src, x, y); 
+             q = dest_pixels + OFFSET (dest, src->height - y - 1, x); 
              memcpy (q, p, dest->n_channels);
            }
        } 
@@ -485,8 +519,11 @@ GdkPixbuf *
 gdk_pixbuf_flip (const GdkPixbuf *src,
                 gboolean         horizontal)
 {
+  const guint8 *src_pixels;
+  guint8 *dest_pixels;
   GdkPixbuf *dest;
-  guchar *p, *q;
+  const guchar *p;
+  guchar *q;
   gint x, y;
 
   dest = gdk_pixbuf_new (src->colorspace, 
@@ -497,12 +534,15 @@ gdk_pixbuf_flip (const GdkPixbuf *src,
   if (!dest)
     return NULL;
 
+  dest_pixels = gdk_pixbuf_get_pixels (dest);
+  src_pixels = gdk_pixbuf_read_pixels (src);
+
   if (!horizontal) /* flip vertical */
     {
       for (y = 0; y < dest->height; y++)
        {
-         p = src->pixels + OFFSET (src, 0, y);
-         q = dest->pixels + OFFSET (dest, 0, dest->height - y - 1);
+         p = src_pixels + OFFSET (src, 0, y);
+         q = dest_pixels + OFFSET (dest, 0, dest->height - y - 1);
          memcpy (q, p, dest->rowstride);
        }
     }
@@ -512,8 +552,8 @@ gdk_pixbuf_flip (const GdkPixbuf *src,
        {
          for (x = 0; x < dest->width; x++)
            {
-             p = src->pixels + OFFSET (src, x, y);
-             q = dest->pixels + OFFSET (dest, dest->width - x - 1, y);
+             p = src_pixels + OFFSET (src, x, y);
+             q = dest_pixels + OFFSET (dest, dest->width - x - 1, y);
              memcpy (q, p, dest->n_channels);
            }
        }
diff --git a/gdk-pixbuf/gdk-pixbuf-util.c b/gdk-pixbuf/gdk-pixbuf-util.c
index 6fbaa8e..63627e9 100644
--- a/gdk-pixbuf/gdk-pixbuf-util.c
+++ b/gdk-pixbuf/gdk-pixbuf-util.c
@@ -65,12 +65,16 @@ gdk_pixbuf_add_alpha (const GdkPixbuf *pixbuf,
 {
        GdkPixbuf *new_pixbuf;
        int x, y;
+       const guint8 *src_pixels;
+       guint8 *ret_pixels;
 
        g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
        g_return_val_if_fail (pixbuf->colorspace == GDK_COLORSPACE_RGB, NULL);
        g_return_val_if_fail (pixbuf->n_channels == 3 || pixbuf->n_channels == 4, NULL);
        g_return_val_if_fail (pixbuf->bits_per_sample == 8, NULL);
 
+       src_pixels = gdk_pixbuf_read_pixels (pixbuf);
+
        if (pixbuf->has_alpha) {
                new_pixbuf = gdk_pixbuf_copy (pixbuf);
                if (!new_pixbuf)
@@ -85,12 +89,15 @@ gdk_pixbuf_add_alpha (const GdkPixbuf *pixbuf,
        if (!new_pixbuf)
                return NULL;
 
+       ret_pixels = gdk_pixbuf_get_pixels (new_pixbuf);
+
        for (y = 0; y < pixbuf->height; y++) {
-               guchar *src, *dest;
+               const guchar *src;
+               guchar *dest;
                guchar tr, tg, tb;
 
-               src = pixbuf->pixels + y * pixbuf->rowstride;
-               dest = new_pixbuf->pixels + y * new_pixbuf->rowstride;
+               src = src_pixels + y * pixbuf->rowstride;
+               dest = ret_pixels + y * new_pixbuf->rowstride;
                 
                 if (pixbuf->has_alpha) {
                         /* Just subst color, we already copied everything else */
@@ -208,9 +215,9 @@ gdk_pixbuf_saturate_and_pixelate(const GdkPixbuf *src,
         } else {
                 int i, j, t;
                 int width, height, has_alpha, src_rowstride, dest_rowstride, bytes_per_pixel;
-               guchar *src_line;
+               const guchar *src_line;
                guchar *dest_line;
-                guchar *src_pixel;
+                const guchar *src_pixel;
                guchar *dest_pixel;
                 guchar intensity;
 
@@ -221,8 +228,8 @@ gdk_pixbuf_saturate_and_pixelate(const GdkPixbuf *src,
                 src_rowstride = gdk_pixbuf_get_rowstride (src);
                 dest_rowstride = gdk_pixbuf_get_rowstride (dest);
                 
-                src_line = gdk_pixbuf_get_pixels (src);
                 dest_line = gdk_pixbuf_get_pixels (dest);
+                src_line = gdk_pixbuf_read_pixels (src);
                
 #define DARK_FACTOR 0.7
 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
diff --git a/gdk-pixbuf/gdk-pixbuf.c b/gdk-pixbuf/gdk-pixbuf.c
index 6a907cc..1f5e5d4 100644
--- a/gdk-pixbuf/gdk-pixbuf.c
+++ b/gdk-pixbuf/gdk-pixbuf.c
@@ -88,18 +88,15 @@
  * > gdk_pixbuf_unref() are deprecated in favour of g_object_ref()
  * > and g_object_unref() resp.
  * 
- * Finalizing a pixbuf means to free its pixel
- * data and to free the #GdkPixbuf structure itself.  Most of the
- * library functions that create #GdkPixbuf structures create the
- * pixel data by themselves and define the way it should be freed;
- * you do not need to worry about those.  The only function that lets
- * you specify how to free the pixel data is
- * gdk_pixbuf_new_from_data().  Since you pass it a pre-allocated
- * pixel buffer, you must also specify a way to free that data.  This
- * is done with a function of type #GdkPixbufDestroyNotify.  When a
- * pixbuf created with gdk_pixbuf_new_from_data() is finalized, your
- * destroy notification function will be called, and it is its
- * responsibility to free the pixel array.
+ * Finalizing a pixbuf means to free its pixel data and to free the
+ * #GdkPixbuf structure itself.  Most of the library functions that
+ * create #GdkPixbuf structures create the pixel data by themselves
+ * and define the way it should be freed; you do not need to worry
+ * about those.
+ *
+ * To provide preallocated pixel data, use
+ * gdk_pixbuf_new_from_bytes().  The gdk_pixbuf_new_from_data() API is
+ * an older variant that predates the existence of #GBytes.
  */
 
 static void gdk_pixbuf_finalize     (GObject        *object);
@@ -123,7 +120,8 @@ enum
   PROP_WIDTH,
   PROP_HEIGHT,
   PROP_ROWSTRIDE,
-  PROP_PIXELS
+  PROP_PIXELS,
+  PROP_PIXEL_BYTES
 };
 
 static void gdk_pixbuf_icon_iface_init (GIconIface *iface);
@@ -241,6 +239,22 @@ gdk_pixbuf_class_init (GdkPixbufClass *klass)
                                                                P_("Pixels"),
                                                                P_("A pointer to the pixel data of the 
pixbuf"),
                                                                PIXBUF_PARAM_FLAGS));
+
+        /**
+         * GdkPixbuf::pixel-bytes:
+         *
+         * If set, this pixbuf was created from read-only #GBytes.
+         * Replaces GdkPixbuf::pixels.
+         * 
+         * Since: 2.32
+         */
+        g_object_class_install_property (object_class,
+                                         PROP_PIXEL_BYTES,
+                                         g_param_spec_boxed ("pixel-bytes",
+                                                             P_("Pixel Bytes"),
+                                                             P_("Readonly pixel data"),
+                                                             G_TYPE_BYTES,
+                                                             PIXBUF_PARAM_FLAGS));
 }
 
 static void
@@ -248,8 +262,10 @@ gdk_pixbuf_finalize (GObject *object)
 {
         GdkPixbuf *pixbuf = GDK_PIXBUF (object);
         
-        if (pixbuf->destroy_fn)
+        if (pixbuf->pixels && pixbuf->destroy_fn)
                 (* pixbuf->destroy_fn) (pixbuf->pixels, pixbuf->destroy_fn_data);
+
+        g_clear_pointer (&pixbuf->bytes, g_bytes_unref);
         
         G_OBJECT_CLASS (gdk_pixbuf_parent_class)->finalize (object);
 }
@@ -475,7 +491,7 @@ gdk_pixbuf_copy (const GdkPixbuf *pixbuf)
        if (!buf)
                return NULL;
 
-       memcpy (buf, pixbuf->pixels, size);
+       memcpy (buf, gdk_pixbuf_read_pixels (pixbuf), size);
 
        return gdk_pixbuf_new_from_data (buf,
                                         pixbuf->colorspace, pixbuf->has_alpha,
@@ -494,13 +510,15 @@ gdk_pixbuf_copy (const GdkPixbuf *pixbuf)
  * @width: width of region in @src_pixbuf
  * @height: height of region in @src_pixbuf
  * 
- * Creates a new pixbuf which represents a sub-region of
- * @src_pixbuf. The new pixbuf shares its pixels with the
- * original pixbuf, so writing to one affects both.
- * The new pixbuf holds a reference to @src_pixbuf, so
- * @src_pixbuf will not be finalized until the new pixbuf
- * is finalized.
- * 
+ * Creates a new pixbuf which represents a sub-region of @src_pixbuf.
+ * The new pixbuf shares its pixels with the original pixbuf, so
+ * writing to one affects both.  The new pixbuf holds a reference to
+ * @src_pixbuf, so @src_pixbuf will not be finalized until the new
+ * pixbuf is finalized.
+ *
+ * Note that if @src_pixbuf is read-only, this function will force it
+ * to be mutable.
+ *
  * Return value: (transfer full): a new pixbuf 
  **/
 GdkPixbuf*
@@ -516,7 +534,8 @@ gdk_pixbuf_new_subpixbuf (GdkPixbuf *src_pixbuf,
         g_return_val_if_fail (GDK_IS_PIXBUF (src_pixbuf), NULL);
         g_return_val_if_fail (src_x >= 0 && src_x + width <= src_pixbuf->width, NULL);
         g_return_val_if_fail (src_y >= 0 && src_y + height <= src_pixbuf->height, NULL);
-
+        
+        /* Note causes an implicit copy where src_pixbuf owns the data */
         pixels = (gdk_pixbuf_get_pixels (src_pixbuf)
                   + src_y * src_pixbuf->rowstride
                   + src_x * src_pixbuf->n_channels);
@@ -617,13 +636,14 @@ gdk_pixbuf_get_bits_per_sample (const GdkPixbuf *pixbuf)
  * Return value: (array): A pointer to the pixbuf's pixel data.
  * Please see the section on [image data](image-data) for information
  * about how the pixel data is stored in memory.
+ *
+ * This function will cause an implicit copy of the pixbuf data if the
+ * pixbuf was created from read-only data.
  **/
 guchar *
 gdk_pixbuf_get_pixels (const GdkPixbuf *pixbuf)
 {
-       g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
-
-       return pixbuf->pixels;
+        return gdk_pixbuf_get_pixels_with_length (pixbuf, NULL);
 }
 
 /**
@@ -637,6 +657,9 @@ gdk_pixbuf_get_pixels (const GdkPixbuf *pixbuf)
  * pixel data.  Please see the section on [image data](image-data)
  * for information about how the pixel data is stored in memory.
  *
+ * This function will cause an implicit copy of the pixbuf data if the
+ * pixbuf was created from read-only data.
+ *
  * Rename to: gdk_pixbuf_get_pixels
  *
  * Since: 2.26
@@ -647,6 +670,13 @@ gdk_pixbuf_get_pixels_with_length (const GdkPixbuf *pixbuf,
 {
        g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
 
+        if (pixbuf->bytes) {
+                GdkPixbuf *mut_pixbuf = (GdkPixbuf*)pixbuf;
+                gsize len;
+                mut_pixbuf->pixels = g_bytes_unref_to_data (pixbuf->bytes, &len);
+                mut_pixbuf->bytes = NULL;
+        }
+
         if (length)
                 *length = gdk_pixbuf_get_byte_length (pixbuf);
 
@@ -654,6 +684,31 @@ gdk_pixbuf_get_pixels_with_length (const GdkPixbuf *pixbuf,
 }
 
 /**
+ * gdk_pixbuf_read_pixels:
+ * @pixbuf: A pixbuf
+ *
+ * Returns a read-only pointer to the raw pixel data; must not be
+ * modified.  This function allows skipping the implicit copy that
+ * must be made if gdk_pixbuf_get_pixels() is called on a read-only
+ * pixbuf.
+ *
+ * Since: 2.32
+ */
+const guint8*
+gdk_pixbuf_read_pixels (const GdkPixbuf  *pixbuf)
+{
+       g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
+        
+        if (pixbuf->bytes) {
+                gsize len;
+                /* Ignore len; callers know the size via other variables */
+                return g_bytes_get_data (pixbuf->bytes, &len);
+        } else {
+                return pixbuf->pixels;
+        }
+}
+
+/**
  * gdk_pixbuf_get_width:
  * @pixbuf: A pixbuf.
  *
@@ -762,7 +817,8 @@ gdk_pixbuf_fill (GdkPixbuf *pixbuf,
         if (pixbuf->width == 0 || pixbuf->height == 0)
                 return;
 
-        pixels = pixbuf->pixels;
+        /* Force an implicit copy */
+        pixels = gdk_pixbuf_get_pixels (pixbuf);
 
         r = (pixel & 0xff000000) >> 24;
         g = (pixel & 0x00ff0000) >> 16;
@@ -931,6 +987,9 @@ gdk_pixbuf_set_property (GObject         *object,
           case PROP_PIXELS:
                   pixbuf->pixels = (guchar *) g_value_get_pointer (value);
                   break;
+          case PROP_PIXEL_BYTES:
+                  pixbuf->bytes = g_value_dup_boxed (value);
+                  break;
           default:
                   G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                   break;
@@ -971,6 +1030,9 @@ gdk_pixbuf_get_property (GObject         *object,
           case PROP_PIXELS:
                   g_value_set_pointer (value, gdk_pixbuf_get_pixels (pixbuf));
                   break;
+          case PROP_PIXEL_BYTES:
+                  g_value_set_boxed (value, pixbuf->bytes);
+                  break;
           default:
                   G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                   break;
diff --git a/gdk-pixbuf/gdk-pixbuf.symbols b/gdk-pixbuf/gdk-pixbuf.symbols
index 66ef07b..3f292e0 100644
--- a/gdk-pixbuf/gdk-pixbuf.symbols
+++ b/gdk-pixbuf/gdk-pixbuf.symbols
@@ -23,6 +23,7 @@ gdk_pixbuf_get_colorspace
 gdk_pixbuf_get_has_alpha
 gdk_pixbuf_get_height
 gdk_pixbuf_get_n_channels
+gdk_pixbuf_read_pixels
 gdk_pixbuf_get_pixels
 gdk_pixbuf_get_pixels_with_length
 gdk_pixbuf_get_byte_length
@@ -38,6 +39,7 @@ gdk_pixbuf_fill
 #if IN_HEADER(GDK_PIXBUF_CORE_H)
 #if IN_FILE(__GDK_PIXBUF_DATA_C__)
 gdk_pixbuf_new_from_data
+gdk_pixbuf_new_from_bytes
 #endif
 #endif
 
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 80840c2..1c17877 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -32,6 +32,7 @@ test_programs =                       \
        pixbuf-resource                 \
        pixbuf-scale                    \
        pixbuf-save                     \
+       pixbuf-readonly-to-mutable      \
        $(NULL)
 
 dist_installed_test_data =             \
@@ -78,6 +79,12 @@ pixbuf_save_SOURCES =                        \
        test-common.h                   \
        $(NULL)
 
+pixbuf_readonly_to_mutable_SOURCES =   \
+       pixbuf-readonly-to-mutable.c    \
+       test-common.c                   \
+       test-common.h                   \
+       $(NULL)
+
 pixbuf_resource_SOURCES =              \
        pixbuf-resource.c               \
        test-common.c                   \
diff --git a/tests/pixbuf-readonly-to-mutable.c b/tests/pixbuf-readonly-to-mutable.c
new file mode 100644
index 0000000..8f81c87
--- /dev/null
+++ b/tests/pixbuf-readonly-to-mutable.c
@@ -0,0 +1,159 @@
+/* -*- Mode: C; c-basic-offset: 2; -*- */
+/* GdkPixbuf library - test loaders
+ *
+ * Copyright (C) 2014 Red Hat, 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "gdk-pixbuf/gdk-pixbuf.h"
+#include "test-common.h"
+#include <string.h>
+
+#ifdef G_OS_UNIX
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+#ifdef G_OS_UNIX
+typedef struct {
+  void *buf;
+  gsize len;
+} MappedBuf;
+
+static void
+destroy_buf_unmap (gpointer data)
+{
+  MappedBuf *buf = data;
+  int r;
+
+  r = munmap (buf->buf, buf->len);
+  g_assert_cmpint (r, ==, 0);
+  g_free (buf);
+}
+#endif
+
+static GdkPixbuf *
+get_readonly_pixbuf (void)
+{
+  GdkPixbuf *reference;
+  GdkPixbuf *result;
+  GBytes *bytes;
+  GError *error = NULL;
+
+  reference = gdk_pixbuf_new_from_file (g_test_get_filename (G_TEST_DIST, "test-image.png", NULL), &error);
+  g_assert_no_error (error);
+  
+#ifdef G_OS_UNIX
+  {
+    MappedBuf *buf;
+    int pagesize;
+    int pages;
+    int r;
+    gsize pixlen;
+
+    pagesize = sysconf (_SC_PAGE_SIZE);
+    g_assert_cmpint (pagesize, >, 0);
+
+    pixlen = gdk_pixbuf_get_byte_length (reference);
+    pages = pixlen / pagesize + 1;
+
+    buf = g_new0 (MappedBuf, 1);
+    buf->len = pages * pagesize;
+    buf->buf = mmap (NULL, buf->len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+    g_assert (buf->buf != NULL);
+
+    memcpy (buf->buf, gdk_pixbuf_get_pixels (reference), pixlen);
+
+    r = mprotect (buf->buf, buf->len, PROT_READ);
+    g_assert_cmpint (r, ==, 0);
+
+    bytes = g_bytes_new_with_free_func (buf->buf, buf->len, destroy_buf_unmap, buf);
+  }
+#else
+  bytes = g_bytes_new (gdk_pixbuf_get_pixels (reference), gdk_pixbuf_get_byte_length (reference));
+#endif
+
+  result = gdk_pixbuf_new_from_bytes (bytes,
+                                     gdk_pixbuf_get_colorspace (reference),
+                                     gdk_pixbuf_get_has_alpha (reference),
+                                     gdk_pixbuf_get_bits_per_sample (reference),
+                                     gdk_pixbuf_get_width (reference),
+                                     gdk_pixbuf_get_height (reference),
+                                     gdk_pixbuf_get_rowstride (reference));
+  g_object_unref (reference);
+  g_bytes_unref (bytes);
+
+  return result;
+}
+
+static void
+test_mutate_readonly (void)
+{
+  GdkPixbuf *src;
+  GdkPixbuf *dest;
+
+  if (!format_supported ("png"))
+    {
+      g_test_skip ("format not supported");
+      return;
+    }
+  
+  src = get_readonly_pixbuf ();
+  gdk_pixbuf_scale (src, src,
+                   0, 0, 
+                   gdk_pixbuf_get_width (src) / 4, 
+                   gdk_pixbuf_get_height (src) / 4,
+                   0, 0, 0.5, 0.5,
+                   GDK_INTERP_NEAREST);
+  g_object_unref (src);
+
+  src = get_readonly_pixbuf ();
+                   
+  dest = gdk_pixbuf_scale_simple (src,
+                                 gdk_pixbuf_get_width (src) / 4, 
+                                 gdk_pixbuf_get_height (src) / 4,
+                                 GDK_INTERP_NEAREST);
+  g_object_unref (dest);
+
+  dest = gdk_pixbuf_composite_color_simple (src,
+                                           gdk_pixbuf_get_width (src) / 4, 
+                                           gdk_pixbuf_get_height (src) / 4,
+                                           GDK_INTERP_NEAREST,
+                                           128,
+                                           8,
+                                           G_MAXUINT32,
+                                           G_MAXUINT32/2);
+  g_object_unref (dest);
+
+  dest = gdk_pixbuf_rotate_simple (src, 180);
+  g_object_unref (dest);
+
+  dest = gdk_pixbuf_flip (src, TRUE);
+  g_object_unref (dest);
+  dest = gdk_pixbuf_flip (src, FALSE);
+  g_object_unref (dest);
+
+  g_object_unref (src);
+}
+
+int
+main (int argc, char **argv)
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/pixbuf/readonly/mutate", test_mutate_readonly);
+
+  return g_test_run ();
+}


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