/* * Copyright © 2024 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.1 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 . * * Authors: Benjamin Otte */ #include "config.h" #include "gdkmemorytexturebuilder.h" #include "gdkcolorstate.h" #include "gdkenumtypes.h" #include "gdkmemorytextureprivate.h" #include struct _GdkMemoryTextureBuilder { GObject parent_instance; GBytes *bytes; gsize stride; int width; int height; GdkMemoryFormat format; GdkColorState *color_state; GdkTexture *update_texture; cairo_region_t *update_region; }; struct _GdkMemoryTextureBuilderClass { GObjectClass parent_class; }; /** * GdkMemoryTextureBuilder: * * `GdkMemoryTextureBuilder` is a builder used to construct [class@Gdk.Texture] objects * from system memory provided via [struct@GLib.Bytes]. * * The operation is quite simple: Create a texture builder, set all the necessary * properties - keep in mind that the properties [property@Gdk.MemoryTextureBuilder:bytes], * [property@Gdk.MemoryTextureBuilder:stride], [property@Gdk.MemoryTextureBuilder:width], * and [property@Gdk.MemoryTextureBuilder:height] are mandatory - and then call * [method@Gdk.MemoryTextureBuilder.build] to create the new texture. * * `GdkMemoryTextureBuilder` can be used for quick one-shot construction of * textures as well as kept around and reused to construct multiple textures. * * Since: 4.16 */ enum { PROP_0, PROP_BYTES, PROP_COLOR_STATE, PROP_FORMAT, PROP_HEIGHT, PROP_STRIDE, PROP_UPDATE_REGION, PROP_UPDATE_TEXTURE, PROP_WIDTH, N_PROPS }; G_DEFINE_TYPE (GdkMemoryTextureBuilder, gdk_memory_texture_builder, G_TYPE_OBJECT) static GParamSpec *properties[N_PROPS] = { NULL, }; static void gdk_memory_texture_builder_dispose (GObject *object) { GdkMemoryTextureBuilder *self = GDK_MEMORY_TEXTURE_BUILDER (object); g_clear_pointer (&self->bytes, g_bytes_unref); g_clear_pointer (&self->color_state, gdk_color_state_unref); g_clear_object (&self->update_texture); g_clear_pointer (&self->update_region, cairo_region_destroy); G_OBJECT_CLASS (gdk_memory_texture_builder_parent_class)->dispose (object); } static void gdk_memory_texture_builder_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GdkMemoryTextureBuilder *self = GDK_MEMORY_TEXTURE_BUILDER (object); switch (property_id) { case PROP_BYTES: g_value_set_boxed (value, self->bytes); break; case PROP_COLOR_STATE: g_value_set_boxed (value, self->color_state); break; case PROP_FORMAT: g_value_set_enum (value, self->format); break; case PROP_HEIGHT: g_value_set_int (value, self->height); break; case PROP_STRIDE: g_value_set_uint64 (value, self->stride); break; case PROP_UPDATE_REGION: g_value_set_boxed (value, self->update_region); break; case PROP_UPDATE_TEXTURE: g_value_set_object (value, self->update_texture); break; case PROP_WIDTH: g_value_set_int (value, self->width); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gdk_memory_texture_builder_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GdkMemoryTextureBuilder *self = GDK_MEMORY_TEXTURE_BUILDER (object); switch (property_id) { case PROP_BYTES: gdk_memory_texture_builder_set_bytes (self, g_value_get_boxed (value)); break; case PROP_COLOR_STATE: gdk_memory_texture_builder_set_color_state (self, g_value_get_boxed (value)); break; case PROP_FORMAT: gdk_memory_texture_builder_set_format (self, g_value_get_enum (value)); break; case PROP_HEIGHT: gdk_memory_texture_builder_set_height (self, g_value_get_int (value)); break; case PROP_STRIDE: gdk_memory_texture_builder_set_stride (self, g_value_get_uint64 (value)); break; case PROP_UPDATE_REGION: gdk_memory_texture_builder_set_update_region (self, g_value_get_boxed (value)); break; case PROP_UPDATE_TEXTURE: gdk_memory_texture_builder_set_update_texture (self, g_value_get_object (value)); break; case PROP_WIDTH: gdk_memory_texture_builder_set_width (self, g_value_get_int (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gdk_memory_texture_builder_class_init (GdkMemoryTextureBuilderClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->dispose = gdk_memory_texture_builder_dispose; gobject_class->get_property = gdk_memory_texture_builder_get_property; gobject_class->set_property = gdk_memory_texture_builder_set_property; /** * GdkMemoryTextureBuilder:bytes: * * The bytes holding the data. * * Since: 4.16 */ properties[PROP_BYTES] = g_param_spec_boxed ("bytes", NULL, NULL, G_TYPE_BYTES, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:color-state: * * The colorstate describing the data. * * Since: 4.16 */ properties[PROP_COLOR_STATE] = g_param_spec_boxed ("color-state", NULL, NULL, GDK_TYPE_COLOR_STATE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:format: * * The format of the data. * * Since: 4.16 */ properties[PROP_FORMAT] = g_param_spec_enum ("format", NULL, NULL, GDK_TYPE_MEMORY_FORMAT, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:height: * * The height of the texture. * * Since: 4.16 */ properties[PROP_HEIGHT] = g_param_spec_int ("height", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:stride: * * The rowstride of the texture. * * The rowstride is the number of bytes between the first pixel * in a row of image data, and the first pixel in the next row. * * Since: 4.16 */ properties[PROP_STRIDE] = g_param_spec_uint64 ("stride", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:update-region: * * The update region for [property@Gdk.MemoryTextureBuilder:update-texture]. * * Since: 4.16 */ properties[PROP_UPDATE_REGION] = g_param_spec_boxed ("update-region", NULL, NULL, CAIRO_GOBJECT_TYPE_REGION, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:update-texture: * * The texture [property@Gdk.MemoryTextureBuilder:update-region] is an update for. * * Since: 4.16 */ properties[PROP_UPDATE_TEXTURE] = g_param_spec_object ("update-texture", NULL, NULL, GDK_TYPE_TEXTURE, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); /** * GdkMemoryTextureBuilder:width: * * The width of the texture. * * Since: 4.16 */ properties[PROP_WIDTH] = g_param_spec_int ("width", NULL, NULL, G_MININT, G_MAXINT, 0, G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (gobject_class, N_PROPS, properties); } static void gdk_memory_texture_builder_init (GdkMemoryTextureBuilder *self) { self->format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; self->color_state = gdk_color_state_ref (gdk_color_state_get_srgb ()); } /** * gdk_memory_texture_builder_new: (constructor): * * Creates a new texture builder. * * Returns: the new `GdkTextureBuilder` * * Since: 4.16 **/ GdkMemoryTextureBuilder * gdk_memory_texture_builder_new (void) { return g_object_new (GDK_TYPE_MEMORY_TEXTURE_BUILDER, NULL); } /** * gdk_memory_texture_builder_get_bytes: * @self: a `GdkMemoryTextureBuilder` * * Gets the bytes previously set via gdk_memory_texture_builder_set_bytes() * or %NULL if none was set. * * Returns: (transfer none) (nullable): The bytes * * Since: 4.16 */ GBytes * gdk_memory_texture_builder_get_bytes (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), NULL); return self->bytes; } /** * gdk_memory_texture_builder_set_bytes: * @self: a `GdkMemoryTextureBuilder` * @bytes: (nullable): The bytes the texture shows or %NULL to unset * * Sets the data to be shown but the texture. * * The bytes must be set before calling [method@Gdk.MemoryTextureBuilder.build]. * * Since: 4.16 */ void gdk_memory_texture_builder_set_bytes (GdkMemoryTextureBuilder *self, GBytes *bytes) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); g_return_if_fail (bytes != NULL); if (self->bytes == bytes) return; g_clear_pointer (&self->bytes, g_bytes_unref); self->bytes = bytes; if (bytes) g_bytes_ref (bytes); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_BYTES]); } /** * gdk_memory_texture_builder_get_color_state: * @self: a `GdkMemoryTextureBuilder` * * Gets the colorstate previously set via gdk_memory_texture_builder_set_color_state(). * * Returns: (transfer none): The colorstate * * Since: 4.16 */ GdkColorState * gdk_memory_texture_builder_get_color_state (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), NULL); return self->color_state; } /** * gdk_memory_texture_builder_set_color_state: * @self: a `GdkMemoryTextureBuilder` * @color_state: (nullable): The colorstate describing the data * * Sets the colorstate describing the data. * * By default, the sRGB colorstate is used. If you don't know * what colorstates are, this is probably the right thing. * * Since: 4.16 */ void gdk_memory_texture_builder_set_color_state (GdkMemoryTextureBuilder *self, GdkColorState *color_state) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); g_return_if_fail (color_state != NULL); if (self->color_state == color_state) return; g_clear_pointer (&self->color_state, gdk_color_state_unref); self->color_state = color_state; if (color_state) gdk_color_state_ref (color_state); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_COLOR_STATE]); } /** * gdk_memory_texture_builder_get_height: * @self: a `GdkMemoryTextureBuilder` * * Gets the height previously set via gdk_memory_texture_builder_set_height() * or 0 if the height wasn't set. * * Returns: The height * * Since: 4.16 */ int gdk_memory_texture_builder_get_height (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), 0); return self->height; } /** * gdk_memory_texture_builder_set_height: * @self: a `GdkMemoryTextureBuilder` * @height: The texture's height or 0 to unset * * Sets the height of the texture. * * The height must be set before calling [method@Gdk.MemoryTextureBuilder.build]. * * Since: 4.16 */ void gdk_memory_texture_builder_set_height (GdkMemoryTextureBuilder *self, int height) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); if (self->height == height) return; self->height = height; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_HEIGHT]); } /** * gdk_memory_texture_builder_get_width: * @self: a `GdkMemoryTextureBuilder` * * Gets the width previously set via gdk_memory_texture_builder_set_width() * or 0 if the width wasn't set. * * Returns: The width * * Since: 4.16 */ int gdk_memory_texture_builder_get_width (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), 0); return self->width; } /** * gdk_memory_texture_builder_set_width: * @self: a `GdkMemoryTextureBuilder` * @width: The texture's width or 0 to unset * * Sets the width of the texture. * * The width must be set before calling [method@Gdk.MemoryTextureBuilder.build]. * * Since: 4.16 */ void gdk_memory_texture_builder_set_width (GdkMemoryTextureBuilder *self, int width) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); if (self->width == width) return; self->width = width; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_WIDTH]); } /** * gdk_memory_texture_builder_get_stride: * @self: a `GdkMemoryTextureBuilder` * * Gets the stride previously set via gdk_memory_texture_builder_set_stride(). * * Returns: the stride * * Since: 4.16 */ gsize gdk_memory_texture_builder_get_stride (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), 0); return self->stride; } /** * gdk_memory_texture_builder_set_stride: * @self: a `GdkMemoryTextureBuilder` * @stride: the stride or 0 to unset * * Sets the rowstride of the bytes used. * * The rowstride must be set before calling [method@Gdk.MemoryTextureBuilder.build]. * * Since: 4.16 */ void gdk_memory_texture_builder_set_stride (GdkMemoryTextureBuilder *self, gsize stride) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); if (self->stride == stride) return; self->stride = stride; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_STRIDE]); } /** * gdk_memory_texture_builder_get_format: * @self: a `GdkMemoryTextureBuilder` * * Gets the format previously set via gdk_memory_texture_builder_set_format(). * * Returns: The format * * Since: 4.16 */ GdkMemoryFormat gdk_memory_texture_builder_get_format (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), GDK_MEMORY_R8G8B8A8_PREMULTIPLIED); return self->format; } /** * gdk_memory_texture_builder_set_format: * @self: a `GdkMemoryTextureBuilder` * @format: The texture's format * * Sets the format of the bytes. * * The default is `GDK_MEMORY_R8G8B8A8_PREMULTIPLIED`. * * Since: 4.16 */ void gdk_memory_texture_builder_set_format (GdkMemoryTextureBuilder *self, GdkMemoryFormat format) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); if (self->format == format) return; self->format = format; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FORMAT]); } /** * gdk_memory_texture_builder_get_update_texture: * @self: a `GdkMemoryTextureBuilder` * * Gets the texture previously set via gdk_memory_texture_builder_set_update_texture() * or %NULL if none was set. * * Returns: (transfer none) (nullable): The update texture * * Since: 4.16 */ GdkTexture * gdk_memory_texture_builder_get_update_texture (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), NULL); return self->update_texture; } /** * gdk_memory_texture_builder_set_update_texture: * @self: a `GdkMemoryTextureBuilder` * @texture: (nullable): the texture to update * * Sets the texture to be updated by this texture. * * See [method@Gdk.MemoryTextureBuilder.set_update_region] for an explanation. * * Since: 4.16 */ void gdk_memory_texture_builder_set_update_texture (GdkMemoryTextureBuilder *self, GdkTexture *texture) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); g_return_if_fail (texture == NULL || GDK_IS_TEXTURE (texture)); if (!g_set_object (&self->update_texture, texture)) return; g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPDATE_TEXTURE]); } /** * gdk_memory_texture_builder_get_update_region: * @self: a `GdkMemoryTextureBuilder` * * Gets the region previously set via gdk_memory_texture_builder_set_update_region() * or %NULL if none was set. * * Returns: (transfer none) (nullable): The update region * * Since: 4.16 */ cairo_region_t * gdk_memory_texture_builder_get_update_region (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), NULL); return self->update_region; } /** * gdk_memory_texture_builder_set_update_region: * @self: a `GdkMemoryTextureBuilder` * @region: (nullable): the region to update * * Sets the region to be updated by this texture. * * Together with [property@Gdk.MemoryTextureBuilder:update-texture], * this describes an update of a previous texture. * * When rendering animations of large textures, it is possible that * consecutive textures are only updating contents in parts of the texture. * It is then possible to describe this update via these two properties, * so that GTK can avoid rerendering parts that did not change. * * An example would be a screen recording where only the mouse pointer moves. * * Since: 4.16 */ void gdk_memory_texture_builder_set_update_region (GdkMemoryTextureBuilder *self, cairo_region_t *region) { g_return_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self)); if (self->update_region == region) return; g_clear_pointer (&self->update_region, cairo_region_destroy); if (region) self->update_region = cairo_region_reference (region); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_UPDATE_REGION]); } /** * gdk_memory_texture_builder_build: * @self: a `GdkMemoryTextureBuilder` * * Builds a new `GdkTexture` with the values set up in the builder. * * Note that it is a programming error to call this function if any mandatory * property has not been set. * * It is possible to call this function multiple times to create multiple textures, * possibly with changing properties in between. * * Returns: (transfer full): a newly built `GdkTexture` * * Since: 4.16 */ GdkTexture * gdk_memory_texture_builder_build (GdkMemoryTextureBuilder *self) { g_return_val_if_fail (GDK_IS_MEMORY_TEXTURE_BUILDER (self), NULL); g_return_val_if_fail (self->width > 0, NULL); g_return_val_if_fail (self->height > 0, NULL); g_return_val_if_fail (self->bytes != NULL, NULL); g_return_val_if_fail (self->stride >= self->width * gdk_memory_format_bytes_per_pixel (self->format), NULL); /* needs to be this complex to support subtexture of the bottom right part */ g_return_val_if_fail (g_bytes_get_size (self->bytes) >= gdk_memory_format_min_buffer_size (self->format, self->stride, self->width, self->height), NULL); return gdk_memory_texture_new_from_builder (self); }