gtk/gdk/gdktexture.c
Matthias Clasen 45c5b250b9 texture: Don't mess up color states
gdk_texture_download_surface was converting twice from the texture
color state to the target color state, with predictably bad results.
2024-07-26 17:27:20 -04:00

1223 lines
34 KiB
C

/* 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:
*
* `GdkTexture` is the basic element used to refer to pixel data.
*
* 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 thread-safe object.
*/
#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_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,
GdkMemoryFormat format,
GdkColorState *color_state,
guchar *data,
gsize stride)
{
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: (attributes org.gtk.Property.get=gdk_texture_get_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_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkTexture:height: (attributes org.gtk.Property.get=gdk_texture_get_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_STRINGS |
G_PARAM_EXPLICIT_NOTIFY);
/**
* GdkTexture:color-state: (attributes org.gtk.Property.get=gdk_texture_get_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_STRINGS |
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 ();
}
static GdkMemoryFormat
cairo_format_to_memory_format (cairo_format_t format)
{
switch (format)
{
case CAIRO_FORMAT_ARGB32:
return GDK_MEMORY_DEFAULT;
case CAIRO_FORMAT_RGB24:
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
return GDK_MEMORY_B8G8R8X8;
#elif G_BYTE_ORDER == G_BIG_ENDIAN
return GDK_MEMORY_X8R8G8B8;
#else
#error "Unknown byte order for Cairo format"
#endif
case CAIRO_FORMAT_A8:
return GDK_MEMORY_A8;
case CAIRO_FORMAT_RGB96F:
return GDK_MEMORY_R32G32B32_FLOAT;
case CAIRO_FORMAT_RGBA128F:
return GDK_MEMORY_R32G32B32A32_FLOAT;
case CAIRO_FORMAT_RGB16_565:
case CAIRO_FORMAT_RGB30:
case CAIRO_FORMAT_INVALID:
case CAIRO_FORMAT_A1:
default:
g_assert_not_reached ();
return GDK_MEMORY_DEFAULT;
}
}
/*<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),
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`
*/
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_get_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 and JPEG, 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.
*
* 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;
texture = gdk_texture_new_for_pixbuf (pixbuf);
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.
*
* 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.
*
* 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: (attributes org.gtk.Method.get_property=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: (attributes org.gtk.Method.get_property=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: (attributes org.gtk.Method.get_property=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,
GdkMemoryFormat format,
GdkColorState *color_state,
guchar *data,
gsize stride)
{
GDK_TEXTURE_GET_CLASS (texture)->download (texture, format, color_state, data, stride);
}
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));
#else
if (depth == GDK_MEMORY_U8_SRGB)
depth = GDK_MEMORY_U8;
#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,
GDK_MEMORY_DEFAULT,
GDK_COLOR_STATE_SRGB,
data,
stride);
}
/**
* 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,
gdk_color_state_get_no_srgb_tf (self->color_state) != NULL);
}
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
* gdk-pixbuf 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);
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 gdk-pixbuf 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);
}
/**
* 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);
}