/* gdktexture.c
*
* Copyright 2016 Benjamin Otte
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
/**
* GdkTexture :
*
* Refers to pixel data in various forms .
*
* It is primarily meant for pixel data that will not change over
* multiple frames , and will be used for a long time .
*
* There are various ways to create ` GdkTexture ` objects from a
* [ class @ GdkPixbuf . Pixbuf ] , or from bytes stored in memory , a file , or a
* [ struct @ Gio . Resource ] .
*
* The ownership of the pixel data is transferred to the ` GdkTexture `
* instance ; you can only make a copy of it , via [ method @ Gdk . Texture . download ] .
*
* ` GdkTexture ` is an immutable object : That means you cannot change
* anything about it other than increasing the reference count via
* [ method @ GObject . Object . ref ] , and consequently , it is a threadsafe object .
*
* GDK provides a number of threadsafe texture loading functions :
* [ ctor @ Gdk . Texture . new_from_resource ] ,
* [ ctor @ Gdk . Texture . new_from_bytes ] ,
* [ ctor @ Gdk . Texture . new_from_file ] ,
* [ ctor @ Gdk . Texture . new_from_filename ] ,
* [ ctor @ Gdk . Texture . new_for_pixbuf ] . Note that these are meant for loading
* icons and resources that are shipped with the toolkit or application . It
* is recommended that you use a dedicated image loading framework such as
* [ glycin ] ( https : //lib.rs/crates/glycin), if you need to load untrusted image
* data .
*/
#include "config.h"
#include "gdktextureprivate.h"
#include "gdkcairoprivate.h"
#include "gdkcolorstateprivate.h"
#include "gdkmemorytextureprivate.h"
#include "gdkpaintable.h"
#include "gdksnapshot.h"
#include "gdktexturedownloaderprivate.h"
#include <glib/gi18n-lib.h>
#include <graphene.h>
#include "loaders/gdkpngprivate.h"
#include "loaders/gdktiffprivate.h"
#include "loaders/gdkjpegprivate.h"
/**
* gdk_texture_error_quark :
*
* Registers an error quark for [ class @ Gdk . Texture ] errors .
*
* Returns : the error quark
**/
G_DEFINE_QUARK (gdk-texture-error-quark, gdk_texture_error)
/* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */
void
gtk_snapshot_append_texture (GdkSnapshot *snapshot,
GdkTexture *texture,
const graphene_rect_t *bounds);
enum {
PROP_0,
PROP_WIDTH,
PROP_HEIGHT,
PROP_COLOR_STATE,
N_PROPS
};
static GParamSpec *properties[N_PROPS];
static GdkTextureChain *
gdk_texture_chain_new (void )
{
GdkTextureChain *chain = g_new0 (GdkTextureChain, 1 );
g_atomic_ref_count_init (&chain->ref_count);
g_mutex_init (&chain->lock);
return chain;
}
static void
gdk_texture_chain_ref (GdkTextureChain *chain)
{
g_atomic_ref_count_inc (&chain->ref_count);
}
static void
gdk_texture_chain_unref (GdkTextureChain *chain)
{
if (g_atomic_ref_count_dec (&chain->ref_count))
{
g_mutex_clear (&chain->lock);
g_free (chain);
}
}
static void
gdk_texture_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GdkTexture *self = GDK_TEXTURE (paintable);
gtk_snapshot_append_texture (snapshot,
self,
&GRAPHENE_RECT_INIT (0 , 0 , width, height));
}
static GdkPaintableFlags
gdk_texture_paintable_get_flags (GdkPaintable *paintable)
{
return GDK_PAINTABLE_STATIC_SIZE
| GDK_PAINTABLE_STATIC_CONTENTS;
}
static int
gdk_texture_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GdkTexture *self = GDK_TEXTURE (paintable);
return self->width;
}
static int
gdk_texture_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GdkTexture *self = GDK_TEXTURE (paintable);
return self->height;
}
static void
gdk_texture_paintable_init (GdkPaintableInterface *iface)
{
iface->snapshot = gdk_texture_paintable_snapshot;
iface->get_flags = gdk_texture_paintable_get_flags;
iface->get_intrinsic_width = gdk_texture_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gdk_texture_paintable_get_intrinsic_height;
}
static GVariant *
gdk_texture_icon_serialize (GIcon *icon)
{
GVariant *result;
GBytes *bytes;
bytes = gdk_texture_save_to_png_bytes (GDK_TEXTURE (icon));
result = g_variant_new_from_bytes (G_VARIANT_TYPE_BYTESTRING, bytes, TRUE );
g_bytes_unref (bytes);
return g_variant_new ("(sv)" , "bytes" , result);
}
static void
gdk_texture_icon_init (GIconIface *iface)
{
iface->hash = (guint (*) (GIcon *)) g_direct_hash;
iface->equal = (gboolean (*) (GIcon *, GIcon *)) g_direct_equal;
iface->serialize = gdk_texture_icon_serialize;
}
static GInputStream *
gdk_texture_loadable_icon_load (GLoadableIcon *icon,
int size,
char **type,
GCancellable *cancellable,
GError **error)
{
GInputStream *stream;
GBytes *bytes;
bytes = gdk_texture_save_to_png_bytes (GDK_TEXTURE (icon));
stream = g_memory_input_stream_new_from_bytes (bytes);
g_bytes_unref (bytes);
if (type)
*type = NULL;
return stream;
}
static void
gdk_texture_loadable_icon_load_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GInputStream *stream;
GBytes *bytes;
bytes = gdk_texture_save_to_png_bytes (source_object);
stream = g_memory_input_stream_new_from_bytes (bytes);
g_bytes_unref (bytes);
g_task_return_pointer (task, stream, g_object_unref);
}
static void
gdk_texture_loadable_icon_load_async (GLoadableIcon *icon,
int size,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
task = g_task_new (icon, cancellable, callback, user_data);
g_task_set_source_tag (task, gdk_texture_loadable_icon_load_async);
g_task_run_in_thread (task, gdk_texture_loadable_icon_load_in_thread);
g_object_unref (task);
}
static GInputStream *
gdk_texture_loadable_icon_load_finish (GLoadableIcon *icon,
GAsyncResult *res,
char **type,
GError **error)
{
GInputStream *result;
g_return_val_if_fail (g_task_is_valid (res, icon), NULL);
result = g_task_propagate_pointer (G_TASK (res), error);
if (result == NULL)
return NULL;
if (type)
*type = NULL;
return result;
}
static void
gdk_texture_loadable_icon_init (GLoadableIconIface *iface)
{
iface->load = gdk_texture_loadable_icon_load;
iface->load_async = gdk_texture_loadable_icon_load_async;
iface->load_finish = gdk_texture_loadable_icon_load_finish;
}
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GdkTexture, gdk_texture, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gdk_texture_paintable_init)
G_IMPLEMENT_INTERFACE (G_TYPE_ICON,
gdk_texture_icon_init)
G_IMPLEMENT_INTERFACE (G_TYPE_LOADABLE_ICON, gdk_texture_loadable_icon_init))
#define GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
g_critical ("Texture of type '%s' does not implement GdkTexture::" # method, G_OBJECT_TYPE_NAME (obj))
static void
gdk_texture_default_download (GdkTexture *texture,
guchar *data,
const GdkMemoryLayout *layout,
GdkColorState *color_state)
{
GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD (texture, download);
}
static void
gdk_texture_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GdkTexture *self = GDK_TEXTURE (gobject);
switch (prop_id)
{
case PROP_WIDTH:
self->width = g_value_get_int (value);
break ;
case PROP_HEIGHT:
self->height = g_value_get_int (value);
break ;
case PROP_COLOR_STATE:
self->color_state = g_value_dup_boxed (value);
g_assert (self->color_state);
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break ;
}
}
static void
gdk_texture_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GdkTexture *self = GDK_TEXTURE (gobject);
switch (prop_id)
{
case PROP_WIDTH:
g_value_set_int (value, self->width);
break ;
case PROP_HEIGHT:
g_value_set_int (value, self->height);
break ;
case PROP_COLOR_STATE:
g_value_set_boxed (value, self->color_state);
break ;
default :
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
break ;
}
}
static void
gdk_texture_dispose (GObject *object)
{
GdkTexture *self = GDK_TEXTURE (object);
if (self->chain)
{
g_mutex_lock (&self->chain->lock);
if (self->next_texture && self->previous_texture)
{
cairo_region_union (self->next_texture->diff_to_previous,
self->diff_to_previous);
self->next_texture->previous_texture = self->previous_texture;
self->previous_texture->next_texture = self->next_texture;
}
else if (self->next_texture)
self->next_texture->previous_texture = NULL;
else if (self->previous_texture)
self->previous_texture->next_texture = NULL;
self->next_texture = NULL;
self->previous_texture = NULL;
g_clear_pointer (&self->diff_to_previous, cairo_region_destroy);
g_mutex_unlock (&self->chain->lock);
g_clear_pointer (&self->chain, gdk_texture_chain_unref);
}
gdk_texture_clear_render_data (self);
G_OBJECT_CLASS (gdk_texture_parent_class)->dispose (object);
}
static void
gdk_texture_finalize (GObject *object)
{
GdkTexture *self = GDK_TEXTURE (object);
gdk_color_state_unref (self->color_state);
G_OBJECT_CLASS (gdk_texture_parent_class)->finalize (object);
}
static void
gdk_texture_class_init (GdkTextureClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
klass->download = gdk_texture_default_download;
gobject_class->set_property = gdk_texture_set_property;
gobject_class->get_property = gdk_texture_get_property;
gobject_class->dispose = gdk_texture_dispose;
gobject_class->finalize = gdk_texture_finalize;
/**
* GdkTexture : width :
*
* The width of the texture , in pixels .
*/
properties[PROP_WIDTH] =
g_param_spec_int ("width" , NULL, NULL,
1 ,
G_MAXINT,
1 ,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkTexture : height :
*
* The height of the texture , in pixels .
*/
properties[PROP_HEIGHT] =
g_param_spec_int ("height" , NULL, NULL,
1 ,
G_MAXINT,
1 ,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkTexture : color - state :
*
* The color state of the texture .
*
* Since : 4 . 16
*/
properties[PROP_COLOR_STATE] =
g_param_spec_boxed ("color-state" , NULL, NULL,
GDK_TYPE_COLOR_STATE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_NAME |
G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (gobject_class, N_PROPS, properties);
}
static void
gdk_texture_init (GdkTexture *self)
{
self->color_state = gdk_color_state_get_srgb ();
}
/*<private>
* gdk_texture_new_for_surface :
* @ surface : a cairo image surface
*
* Creates a new texture object representing the surface .
*
* The @ surface must be an image surface with a format supperted by GTK .
*
* The newly created texture will acquire a reference on the @ surface .
*
* Returns : a new ` GdkTexture `
*/
GdkTexture *
gdk_texture_new_for_surface (cairo_surface_t *surface)
{
GdkTexture *texture;
GBytes *bytes;
g_return_val_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE, NULL);
g_return_val_if_fail (cairo_image_surface_get_width (surface) > 0 , NULL);
g_return_val_if_fail (cairo_image_surface_get_height (surface) > 0 , NULL);
bytes = g_bytes_new_with_free_func (cairo_image_surface_get_data (surface),
cairo_image_surface_get_height (surface)
* cairo_image_surface_get_stride (surface),
(GDestroyNotify) cairo_surface_destroy,
cairo_surface_reference (surface));
texture = gdk_memory_texture_new (cairo_image_surface_get_width (surface),
cairo_image_surface_get_height (surface),
gdk_cairo_format_to_memory_format (cairo_image_surface_get_format (surface)),
bytes,
cairo_image_surface_get_stride (surface));
g_bytes_unref (bytes);
return texture;
}
/**
* gdk_texture_new_for_pixbuf :
* @ pixbuf : a ` GdkPixbuf `
*
* Creates a new texture object representing the ` GdkPixbuf ` .
*
* This function is threadsafe , so that you can e . g . use GTask
* and [ method @ Gio . Task . run_in_thread ] to avoid blocking the main
* thread while loading a big image .
*
* Returns : a new ` GdkTexture `
*
* Deprecated : 4 . 20 : Use e . g . libglycin , which can load many image
* formats into a ` GdkTexture `
*/
GdkTexture *
gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf)
{
GdkTexture *texture;
GBytes *bytes;
g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
bytes = g_bytes_new_with_free_func (gdk_pixbuf_read_pixels (pixbuf),
gdk_pixbuf_get_height (pixbuf)
* (gsize) gdk_pixbuf_get_rowstride (pixbuf),
g_object_unref,
g_object_ref (pixbuf));
texture = gdk_memory_texture_new (gdk_pixbuf_get_width (pixbuf),
gdk_pixbuf_get_height (pixbuf),
gdk_pixbuf_get_has_alpha (pixbuf)
? GDK_MEMORY_GDK_PIXBUF_ALPHA
: GDK_MEMORY_GDK_PIXBUF_OPAQUE,
bytes,
gdk_pixbuf_get_rowstride (pixbuf));
g_bytes_unref (bytes);
return texture;
}
/**
* gdk_texture_new_from_resource :
* @ resource_path : the path of the resource file
*
* Creates a new texture by loading an image from a resource .
*
* The file format is detected automatically . The supported formats
* are PNG , JPEG and TIFF , though more formats might be available .
*
* It is a fatal error if @ resource_path does not specify a valid
* image resource and the program will abort if that happens .
* If you are unsure about the validity of a resource , use
* [ ctor @ Gdk . Texture . new_from_file ] to load it .
*
* This function is threadsafe , so that you can e . g . use GTask
* and [ method @ Gio . Task . run_in_thread ] to avoid blocking the main thread
* while loading a big image .
*
* Return value : A newly - created ` GdkTexture `
*/
GdkTexture *
gdk_texture_new_from_resource (const char *resource_path)
{
GBytes *bytes;
GdkTexture *texture;
GError *error = NULL;
g_return_val_if_fail (resource_path != NULL, NULL);
bytes = g_resources_lookup_data (resource_path, 0 , &error);
if (bytes != NULL)
{
texture = gdk_texture_new_from_bytes (bytes, &error);
g_bytes_unref (bytes);
}
else
texture = NULL;
if (texture == NULL)
g_error ("Resource path %s is not a valid image: %s" , resource_path, error->message);
return texture;
}
/**
* gdk_texture_new_from_file :
* @ file : ` GFile ` to load
* @ error : Return location for an error
*
* Creates a new texture by loading an image from a file .
*
* The file format is detected automatically . The supported formats
* are PNG , JPEG and TIFF , though more formats might be available .
*
* If ` NULL ` is returned , then @ error will be set .
*
* This function is threadsafe , so that you can e . g . use GTask
* and [ method @ Gio . Task . run_in_thread ] to avoid blocking the main thread
* while loading a big image .
*
* : : : warning
* Note that this function should not be used with untrusted data .
* Use a proper image loading framework such as libglycin , which can
* load many image formats into a ` GdkTexture ` .
*
* Return value : A newly - created ` GdkTexture `
*/
GdkTexture *
gdk_texture_new_from_file (GFile *file,
GError **error)
{
GBytes *bytes;
GdkTexture *texture;
g_return_val_if_fail (G_IS_FILE (file), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
bytes = g_file_load_bytes (file, NULL, NULL, error);
if (bytes == NULL)
return NULL;
texture = gdk_texture_new_from_bytes (bytes, error);
g_bytes_unref (bytes);
return texture;
}
gboolean
gdk_texture_can_load (GBytes *bytes)
{
return gdk_is_png (bytes) ||
gdk_is_jpeg (bytes) ||
gdk_is_tiff (bytes);
}
static GdkTexture *
gdk_texture_new_from_bytes_internal (GBytes *bytes,
GError **error)
{
if (gdk_is_png (bytes))
{
return gdk_load_png (bytes, NULL, error);
}
else if (gdk_is_jpeg (bytes))
{
return gdk_load_jpeg (bytes, error);
}
else if (gdk_is_tiff (bytes))
{
return gdk_load_tiff (bytes, error);
}
else
{
g_set_error_literal (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT,
_("Unknown image format." ));
return NULL;
}
}
static GdkTexture *
gdk_texture_new_from_bytes_pixbuf (GBytes *bytes,
GError **error)
{
GInputStream *stream;
GdkPixbuf *pixbuf;
GdkTexture *texture;
stream = g_memory_input_stream_new_from_bytes (bytes);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
g_object_unref (stream);
if (pixbuf == NULL)
return NULL;
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
texture = gdk_texture_new_for_pixbuf (pixbuf);
G_GNUC_END_IGNORE_DEPRECATIONS
g_object_unref (pixbuf);
return texture;
}
/**
* gdk_texture_new_from_bytes :
* @ bytes : a ` GBytes ` containing the data to load
* @ error : Return location for an error
*
* Creates a new texture by loading an image from memory ,
*
* The file format is detected automatically . The supported formats
* are PNG , JPEG and TIFF , though more formats might be available .
*
* If ` NULL ` is returned , then @ error will be set .
*
* This function is threadsafe , so that you can e . g . use GTask
* and [ method @ Gio . Task . run_in_thread ] to avoid blocking the main thread
* while loading a big image .
*
* : : : warning
* Note that this function should not be used with untrusted data .
* Use a proper image loading framework such as libglycin , which can
* load many image formats into a ` GdkTexture ` .
*
* Return value : A newly - created ` GdkTexture `
*
* Since : 4 . 6
*/
GdkTexture *
gdk_texture_new_from_bytes (GBytes *bytes,
GError **error)
{
GdkTexture *texture;
GError *internal_error = NULL;
g_return_val_if_fail (bytes != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
texture = gdk_texture_new_from_bytes_internal (bytes, &internal_error);
if (texture)
return texture;
if (!g_error_matches (internal_error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT) &&
!g_error_matches (internal_error, GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT))
{
g_propagate_error (error, internal_error);
return NULL;
}
g_clear_error (&internal_error);
return gdk_texture_new_from_bytes_pixbuf (bytes, error);
}
/**
* gdk_texture_new_from_filename :
* @ path : ( type filename ) : the filename to load
* @ error : Return location for an error
*
* Creates a new texture by loading an image from a file .
*
* The file format is detected automatically . The supported formats
* are PNG , JPEG and TIFF , though more formats might be available .
*
* If ` NULL ` is returned , then @ error will be set .
*
* This function is threadsafe , so that you can e . g . use GTask
* and [ method @ Gio . Task . run_in_thread ] to avoid blocking the main thread
* while loading a big image .
*
* : : : warning
* Note that this function should not be used with untrusted data .
* Use a proper image loading framework such as libglycin , which can
* load many image formats into a ` GdkTexture ` .
*
* Return value : A newly - created ` GdkTexture `
*
* Since : 4 . 6
*/
GdkTexture *
gdk_texture_new_from_filename (const char *path,
GError **error)
{
GdkTexture *texture;
GFile *file;
g_return_val_if_fail (path, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
file = g_file_new_for_path (path);
texture = gdk_texture_new_from_file (file, error);
g_object_unref (file);
return texture;
}
/**
* gdk_texture_get_width :
* @ texture : a ` GdkTexture `
*
* Returns the width of @ texture , in pixels .
*
* Returns : the width of the ` GdkTexture `
*/
int
gdk_texture_get_width (GdkTexture *texture)
{
g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0 );
return texture->width;
}
/**
* gdk_texture_get_height :
* @ texture : a ` GdkTexture `
*
* Returns the height of the @ texture , in pixels .
*
* Returns : the height of the ` GdkTexture `
*/
int
gdk_texture_get_height (GdkTexture *texture)
{
g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0 );
return texture->height;
}
/**
* gdk_texture_get_color_state :
* @ self : a ` GdkTexture `
*
* Returns the color state associated with the texture .
*
* Returns : ( transfer none ) : the color state of the ` GdkTexture `
*
* Since : 4 . 16
*/
GdkColorState *
gdk_texture_get_color_state (GdkTexture *self)
{
g_return_val_if_fail (GDK_IS_TEXTURE (self), NULL);
return self->color_state;
}
void
gdk_texture_do_download (GdkTexture *texture,
guchar *data,
const GdkMemoryLayout *layout,
GdkColorState *color_state)
{
GDK_TEXTURE_GET_CLASS (texture)->download (texture, data, layout, color_state);
}
GBytes *
gdk_texture_download_bytes (GdkTexture *self,
GdkMemoryLayout *out_layout)
{
if (GDK_IS_MEMORY_TEXTURE (self))
{
GdkMemoryTexture *memtex = GDK_MEMORY_TEXTURE (self);
*out_layout = *gdk_memory_texture_get_layout (memtex);
return g_bytes_ref (gdk_memory_texture_get_bytes (memtex));
}
else
{
guchar *data;
gdk_memory_layout_init (out_layout,
self->format,
self->width,
self->height,
1 );
data = g_malloc (out_layout->size);
gdk_texture_do_download (self, data, out_layout, self->color_state);
return g_bytes_new_take (data, out_layout->size);
}
}
static gboolean
gdk_texture_has_ancestor (GdkTexture *self,
GdkTexture *other)
{
GdkTexture *texture;
for (texture = self->previous_texture;
texture != NULL;
texture = texture->previous_texture)
{
if (texture == other)
return TRUE ;
}
return FALSE ;
}
static void
gdk_texture_diff_from_known_ancestor (GdkTexture *self,
GdkTexture *ancestor,
cairo_region_t *region)
{
GdkTexture *texture;
for (texture = self; texture != ancestor; texture = texture->previous_texture)
cairo_region_union (region, texture->diff_to_previous);
}
void
gdk_texture_diff (GdkTexture *self,
GdkTexture *other,
cairo_region_t *region)
{
cairo_rectangle_int_t fill = {
.x = 0 ,
.y = 0 ,
.width = MAX (self->width, other->width),
.height = MAX (self->height, other->height),
};
GdkTextureChain *chain;
if (other == self)
return ;
chain = g_atomic_pointer_get (&self->chain);
if (chain == NULL || chain != g_atomic_pointer_get (&other->chain))
{
cairo_region_union_rectangle (region, &fill);
return ;
}
g_mutex_lock (&chain->lock);
if (gdk_texture_has_ancestor (self, other))
gdk_texture_diff_from_known_ancestor (self, other, region);
else if (gdk_texture_has_ancestor (other, self))
gdk_texture_diff_from_known_ancestor (other, self, region);
else
cairo_region_union_rectangle (region, &fill);
g_mutex_unlock (&chain->lock);
}
void
gdk_texture_set_diff (GdkTexture *self,
GdkTexture *previous,
cairo_region_t *diff)
{
g_assert (self->diff_to_previous == NULL);
g_assert (self->chain == NULL);
self->chain = g_atomic_pointer_get (&previous->chain);
if (self->chain == NULL)
{
self->chain = gdk_texture_chain_new ();
if (!g_atomic_pointer_compare_and_exchange (&previous->chain,
NULL,
self->chain))
gdk_texture_chain_unref (self->chain);
self->chain = previous->chain;
}
gdk_texture_chain_ref (self->chain);
g_mutex_lock (&self->chain->lock);
if (previous->next_texture)
{
previous->next_texture->previous_texture = NULL;
g_clear_pointer (&previous->next_texture->diff_to_previous, cairo_region_destroy);
}
previous->next_texture = self;
self->previous_texture = previous;
self->diff_to_previous = diff;
g_mutex_unlock (&self->chain->lock);
}
cairo_surface_t *
gdk_texture_download_surface (GdkTexture *texture,
GdkColorState *color_state)
{
GdkMemoryDepth depth;
cairo_surface_t *surface;
cairo_status_t surface_status;
cairo_format_t surface_format;
GdkTextureDownloader downloader;
depth = gdk_texture_get_depth (texture);
#if 0
/* disabled for performance reasons. Enjoy living with some banding. */
if (!gdk_color_state_equal (texture->color_state, color_state))
depth = gdk_memory_depth_merge (depth, gdk_color_state_get_depth (color_state));
#endif
surface_format = gdk_cairo_format_for_depth (depth);
surface = cairo_image_surface_create (surface_format,
texture->width, texture->height);
surface_status = cairo_surface_status (surface);
if (surface_status != CAIRO_STATUS_SUCCESS)
{
g_warning ("%s: surface error: %s" , __FUNCTION__,
cairo_status_to_string (surface_status));
return surface;
}
gdk_texture_downloader_init (&downloader, texture);
gdk_texture_downloader_set_format (&downloader,
gdk_cairo_format_to_memory_format (surface_format));
gdk_texture_downloader_set_color_state (&downloader, color_state);
gdk_texture_downloader_download_into (&downloader,
cairo_image_surface_get_data (surface),
cairo_image_surface_get_stride (surface));
gdk_texture_downloader_finish (&downloader);
cairo_surface_mark_dirty (surface);
return surface;
}
/**
* gdk_texture_download :
* @ texture : a ` GdkTexture `
* @ data : ( array ) : pointer to enough memory to be filled with the
* downloaded data of @ texture
* @ stride : rowstride in bytes
*
* Downloads the @ texture into local memory .
*
* This may be an expensive operation , as the actual texture data
* may reside on a GPU or on a remote display server .
*
* The data format of the downloaded data is equivalent to
* % CAIRO_FORMAT_ARGB32 , so every downloaded pixel requires
* 4 bytes of memory .
*
* Downloading a texture into a Cairo image surface :
* ` ` ` c
* surface = cairo_image_surface_create ( CAIRO_FORMAT_ARGB32 ,
* gdk_texture_get_width ( texture ) ,
* gdk_texture_get_height ( texture ) ) ;
* gdk_texture_download ( texture ,
* cairo_image_surface_get_data ( surface ) ,
* cairo_image_surface_get_stride ( surface ) ) ;
* cairo_surface_mark_dirty ( surface ) ;
* ` ` `
*
* For more flexible download capabilities , see
* [ struct @ Gdk . TextureDownloader ] .
*/
void
gdk_texture_download (GdkTexture *texture,
guchar *data,
gsize stride)
{
g_return_if_fail (GDK_IS_TEXTURE (texture));
g_return_if_fail (data != NULL);
g_return_if_fail (stride >= gdk_texture_get_width (texture) * 4 );
gdk_texture_do_download (texture,
data,
&GDK_MEMORY_LAYOUT_SIMPLE (
GDK_MEMORY_DEFAULT,
texture->width,
texture->height,
stride
),
GDK_COLOR_STATE_SRGB);
}
/**
* gdk_texture_get_format :
* @ self : a GdkTexture
*
* Gets the memory format most closely associated with the data of
* the texture .
*
* Note that it may not be an exact match for texture data
* stored on the GPU or with compression .
*
* The format can give an indication about the bit depth and opacity
* of the texture and is useful to determine the best format for
* downloading the texture .
*
* Returns : the preferred format for the texture ' s data
*
* Since : 4 . 10
**/
GdkMemoryFormat
gdk_texture_get_format (GdkTexture *self)
{
g_return_val_if_fail (GDK_IS_TEXTURE (self), GDK_MEMORY_DEFAULT);
return self->format;
}
GdkMemoryDepth
gdk_texture_get_depth (GdkTexture *self)
{
return gdk_memory_format_get_depth (self->format);
}
gboolean
gdk_texture_set_render_data (GdkTexture *self,
gpointer key,
gpointer data,
GDestroyNotify notify)
{
g_return_val_if_fail (data != NULL, FALSE );
if (self->render_key != NULL)
return FALSE ;
self->render_key = key;
self->render_data = data;
self->render_notify = notify;
return TRUE ;
}
void
gdk_texture_steal_render_data (GdkTexture *self)
{
self->render_key = NULL;
self->render_data = NULL;
self->render_notify = NULL;
}
void
gdk_texture_clear_render_data (GdkTexture *self)
{
if (self->render_notify)
self->render_notify (self->render_data);
self->render_key = NULL;
self->render_data = NULL;
self->render_notify = NULL;
}
gpointer
gdk_texture_get_render_data (GdkTexture *self,
gpointer key)
{
if (self->render_key != key)
return NULL;
return self->render_data;
}
/**
* gdk_texture_save_to_png :
* @ texture : a ` GdkTexture `
* @ filename : ( type filename ) : the filename to store to
*
* Store the given @ texture to the @ filename as a PNG file .
*
* This is a utility function intended for debugging and testing .
* If you want more control over formats , proper error handling or
* want to store to a [ iface @ Gio . File ] or other location , you might
* want to use [ method @ Gdk . Texture . save_to_png_bytes ] or look into
* the libglycin library .
*
* Returns : % TRUE if saving succeeded , % FALSE on failure .
*/
gboolean
gdk_texture_save_to_png (GdkTexture *texture,
const char *filename)
{
GBytes *bytes;
gboolean result;
g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE );
g_return_val_if_fail (filename != NULL, FALSE );
bytes = gdk_save_png (texture, NULL);
result = g_file_set_contents (filename,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL);
g_bytes_unref (bytes);
return result;
}
/**
* gdk_texture_save_to_png_bytes :
* @ texture : a ` GdkTexture `
*
* Store the given @ texture in memory as a PNG file .
*
* Use [ ctor @ Gdk . Texture . new_from_bytes ] to read it back .
*
* If you want to serialize a texture , this is a convenient and
* portable way to do that .
*
* If you need more control over the generated image , such as
* attaching metadata , you should look into an image handling
* library such as the libglycin library .
*
* If you are dealing with high dynamic range float data , you
* might also want to consider [ method @ Gdk . Texture . save_to_tiff_bytes ]
* instead .
*
* Returns : a newly allocated ` GBytes ` containing PNG data
*
* Since : 4 . 6
*/
GBytes *
gdk_texture_save_to_png_bytes (GdkTexture *texture)
{
g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
return gdk_save_png (texture, NULL);
}
/**
* gdk_texture_save_to_tiff :
* @ texture : a ` GdkTexture `
* @ filename : ( type filename ) : the filename to store to
*
* Store the given @ texture to the @ filename as a TIFF file .
*
* GTK will attempt to store data without loss .
* Returns : % TRUE if saving succeeded , % FALSE on failure .
*
* Since : 4 . 6
*/
gboolean
gdk_texture_save_to_tiff (GdkTexture *texture,
const char *filename)
{
GBytes *bytes;
gboolean result;
g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE );
g_return_val_if_fail (filename != NULL, FALSE );
bytes = gdk_save_tiff (texture);
result = g_file_set_contents (filename,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL);
g_bytes_unref (bytes);
return result;
}
/**
* gdk_texture_save_to_tiff_bytes :
* @ texture : a ` GdkTexture `
*
* Store the given @ texture in memory as a TIFF file .
*
* Use [ ctor @ Gdk . Texture . new_from_bytes ] to read it back .
*
* This function is intended to store a representation of the
* texture ' s data that is as accurate as possible . This is
* particularly relevant when working with high dynamic range
* images and floating - point texture data .
*
* If that is not your concern and you are interested in a
* smaller size and a more portable format , you might want to
* use [ method @ Gdk . Texture . save_to_png_bytes ] .
*
* Returns : a newly allocated ` GBytes ` containing TIFF data
*
* Since : 4 . 6
*/
GBytes *
gdk_texture_save_to_tiff_bytes (GdkTexture *texture)
{
g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
return gdk_save_tiff (texture);
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland