gtk/gdk/gdktexture.c
Matthias Clasen 9f54ad3a55 Add GdkTexture::hdr-metadata
Allow textures to carry HDR metadata.

HDR metadata is optional since a) it isn't always available
and b) it isn't always relevant.

This information  can be used to improve the results when tone
or gamut mapping is applied to the texture content.
2024-08-26 12:29:08 -04:00

1266 lines
35 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 "gdkhdrmetadataprivate.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,
PROP_HDR_METADATA,
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;
case PROP_HDR_METADATA:
self->hdr_metadata = g_value_dup_boxed (value);
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;
case PROP_HDR_METADATA:
g_value_set_boxed (value, self->hdr_metadata);
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_clear_pointer (&self->hdr_metadata, gdk_hdr_metadata_unref);
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);
/**
* GdkTexture:hdr-metadata: (attributes org.gtk.Property.get=gdk_texture_get_hdr_metadata)
*
* HDR metadata for the texture, if known.
*
* Since: 4.16
*/
properties[PROP_HDR_METADATA] =
g_param_spec_boxed ("hdr-metadata", NULL, NULL,
GDK_TYPE_HDR_METADATA,
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;
}
/**
* gdk_texture_get_hdr_metadata:
* @self: a `GdkTexture`
*
* Returns HDR metadata associated with the texture, if known.
*
* Returns: (transfer none) (nullable): HDR metadata for the `GdkTexture`
*
* Since: 4.16
*/
GdkHdrMetadata *
gdk_texture_get_hdr_metadata (GdkTexture *self)
{
g_return_val_if_fail (GDK_IS_TEXTURE (self), NULL);
return self->hdr_metadata;
}
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);
}