mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-09 18:30:08 +00:00
45c5b250b9
gdk_texture_download_surface was converting twice from the texture color state to the target color state, with predictably bad results.
1223 lines
34 KiB
C
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);
|
|
}
|
|
|