Merge branch 'image-loading' into 'master'

Image loaders

See merge request GNOME/gtk!3939
This commit is contained in:
Benjamin Otte 2021-09-17 03:13:38 +00:00
commit 1d41dc716e
33 changed files with 2856 additions and 548 deletions

View File

@ -8,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
static GdkPixbuf *avatar_pixbuf_other;
static GdkTexture *avatar_texture_other;
static GtkWidget *window = NULL;
#define GTK_TYPE_MESSAGE (gtk_message_get_type ())
@ -196,12 +196,9 @@ gtk_message_row_update (GtkMessageRow *row)
gtk_button_set_label (GTK_BUTTON (priv->resent_by_button), priv->message->resent_by);
if (strcmp (priv->message->sender_nick, "@GTKtoolkit") == 0)
{
gtk_image_set_from_icon_name (priv->avatar_image, "org.gtk.Demo4");
gtk_image_set_icon_size (priv->avatar_image, GTK_ICON_SIZE_LARGE);
}
gtk_image_set_from_icon_name (priv->avatar_image, "org.gtk.Demo4");
else
gtk_image_set_from_pixbuf (priv->avatar_image, avatar_pixbuf_other);
gtk_image_set_from_paintable (priv->avatar_image, GDK_PAINTABLE (avatar_texture_other));
}
@ -344,7 +341,7 @@ do_listbox (GtkWidget *do_widget)
if (!window)
{
avatar_pixbuf_other = gdk_pixbuf_new_from_resource_at_scale ("/listbox/apple-red.png", 32, 32, FALSE, NULL);
avatar_texture_other = gdk_texture_new_from_resource ("/listbox/apple-red.png");
window = gtk_window_new ();
gtk_window_set_display (GTK_WINDOW (window),

View File

@ -25,6 +25,7 @@
<property name="margin-start">8</property>
<property name="margin-end">8</property>
<property name="icon-name">image-missing</property>
<property name="icon-size">large</property>
<layout>
<property name="column">0</property>
<property name="row">0</property>

View File

@ -603,8 +603,8 @@ bloat_pad_startup (GApplication *application)
g_object_unref (icon);
g_bytes_unref (bytes);
icon = G_ICON (gdk_pixbuf_new_from_resource ("/org/gtk/libgtk/icons/16x16/actions/folder-new.png", NULL));
item = g_menu_item_new ("Pixbuf", NULL);
icon = G_ICON (gdk_texture_new_from_resource ("/org/gtk/libgtk/icons/16x16/actions/folder-new.png"));
item = g_menu_item_new ("Texture", NULL);
g_menu_item_set_icon (item, icon);
g_menu_append_item (menu, item);
g_object_unref (item);

View File

@ -25,6 +25,8 @@
#include "filetransferportalprivate.h"
#include "gdktexture.h"
#include "gdkrgbaprivate.h"
#include "loaders/gdkpngprivate.h"
#include "loaders/gdktiffprivate.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
@ -655,6 +657,56 @@ pixbuf_deserializer (GdkContentDeserializer *deserializer)
deserializer);
}
static void
texture_deserializer_finish (GObject *source,
GAsyncResult *result,
gpointer deserializer)
{
GOutputStream *stream = G_OUTPUT_STREAM (source);
GBytes *bytes;
GError *error = NULL;
GdkTexture *texture = NULL;
gssize written;
written = g_output_stream_splice_finish (stream, result, &error);
if (written < 0)
{
gdk_content_deserializer_return_error (deserializer, error);
return;
}
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
texture = gdk_texture_new_from_bytes (bytes, &error);
g_bytes_unref (bytes);
if (texture == NULL)
{
gdk_content_deserializer_return_error (deserializer, error);
return;
}
g_value_take_object (gdk_content_deserializer_get_value (deserializer), texture);
gdk_content_deserializer_return_success (deserializer);
}
static void
texture_deserializer (GdkContentDeserializer *deserializer)
{
GOutputStream *output;
output = g_memory_output_stream_new_resizable ();
g_output_stream_splice_async (output,
gdk_content_deserializer_get_input_stream (deserializer),
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE
| G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
gdk_content_deserializer_get_priority (deserializer),
gdk_content_deserializer_get_cancellable (deserializer),
texture_deserializer_finish,
deserializer);
g_object_unref (output);
}
static void
string_deserializer_finish (GObject *source,
GAsyncResult *result,
@ -863,48 +915,71 @@ init (void)
initialized = TRUE;
gdk_content_register_deserializer ("image/png",
GDK_TYPE_TEXTURE,
texture_deserializer,
NULL,
NULL);
gdk_content_register_deserializer ("image/tiff",
GDK_TYPE_TEXTURE,
texture_deserializer,
NULL,
NULL);
gdk_content_register_deserializer ("image/jpeg",
GDK_TYPE_TEXTURE,
texture_deserializer,
NULL,
NULL);
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
break;
}
g_free (name);
}
}
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char **mimes, **m;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
mimes = gdk_pixbuf_format_get_mime_types (fmt);
for (m = mimes; *m; m++)
{
gdk_content_register_deserializer (*m,
GDK_TYPE_TEXTURE,
pixbuf_deserializer,
NULL,
NULL);
{
/* Turning pngs, jpegs and tiffs into textures is handled above */
if (!g_str_equal (name, "png") &&
!g_str_equal (name, "jpeg") &&
!g_str_equal (name, "tiff"))
gdk_content_register_deserializer (*m,
GDK_TYPE_TEXTURE,
pixbuf_deserializer,
NULL,
NULL);
gdk_content_register_deserializer (*m,
GDK_TYPE_PIXBUF,
pixbuf_deserializer,
NULL,
NULL);
}
}
g_strfreev (mimes);
g_free (name);
}
g_slist_free (formats);

View File

@ -26,6 +26,10 @@
#include "filetransferportalprivate.h"
#include "gdktextureprivate.h"
#include "gdkrgba.h"
#include "loaders/gdkpngprivate.h"
#include "loaders/gdktiffprivate.h"
#include "loaders/gdkjpegprivate.h"
#include "gdkmemorytextureprivate.h"
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <string.h>
@ -606,6 +610,7 @@ gdk_content_serialize_finish (GAsyncResult *result,
/*** SERIALIZERS ***/
static void
pixbuf_serializer_finish (GObject *source,
GAsyncResult *res,
@ -658,6 +663,77 @@ pixbuf_serializer (GdkContentSerializer *serializer)
g_object_unref (pixbuf);
}
static void
texture_serializer_finish (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
GdkContentSerializer *serializer = GDK_CONTENT_SERIALIZER (source);
GError *error = NULL;
if (!g_task_propagate_boolean (G_TASK (res), &error))
gdk_content_serializer_return_error (serializer, error);
else
gdk_content_serializer_return_success (serializer);
}
static void
serialize_texture_in_thread (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GdkContentSerializer *serializer = source_object;
const GValue *value;
GdkTexture *texture;
GBytes *bytes = NULL;
GError *error = NULL;
gboolean result = FALSE;
GInputStream *input;
gssize spliced;
value = gdk_content_serializer_get_value (serializer);
texture = g_value_get_object (value);
if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/png") == 0)
bytes = gdk_save_png (texture);
else if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/tiff") == 0)
bytes = gdk_save_tiff (texture);
else if (strcmp (gdk_content_serializer_get_mime_type (serializer), "image/jpeg") == 0)
bytes = gdk_save_jpeg (texture);
else
g_assert_not_reached ();
input = g_memory_input_stream_new_from_bytes (bytes);
spliced = g_output_stream_splice (gdk_content_serializer_get_output_stream (serializer),
input,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
gdk_content_serializer_get_cancellable (serializer),
&error);
g_object_unref (input);
g_bytes_unref (bytes);
result = spliced != -1;
if (result)
g_task_return_boolean (task, result);
else
g_task_return_error (task, error);
}
static void
texture_serializer (GdkContentSerializer *serializer)
{
GTask *task;
task = g_task_new (serializer,
gdk_content_serializer_get_cancellable (serializer),
texture_serializer_finish,
NULL);
g_task_run_in_thread (task, serialize_texture_in_thread);
g_object_unref (task);
}
static void
string_serializer_finish (GObject *source,
GAsyncResult *result,
@ -877,51 +953,72 @@ init (void)
initialized = TRUE;
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
"image/png",
texture_serializer,
NULL, NULL);
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
"image/tiff",
texture_serializer,
NULL, NULL);
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
"image/jpeg",
texture_serializer,
NULL, NULL);
formats = gdk_pixbuf_get_formats ();
/* Make sure png comes first */
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char *name;
char *name;
name = gdk_pixbuf_format_get_name (fmt);
if (g_str_equal (name, "png"))
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
{
formats = g_slist_delete_link (formats, f);
formats = g_slist_prepend (formats, fmt);
g_free (name);
break;
}
g_free (name);
break;
}
g_free (name);
}
}
for (f = formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
char **mimes, **m;
char *name;
if (!gdk_pixbuf_format_is_writable (fmt))
continue;
continue;
name = gdk_pixbuf_format_get_name (fmt);
mimes = gdk_pixbuf_format_get_mime_types (fmt);
for (m = mimes; *m; m++)
{
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
*m,
pixbuf_serializer,
gdk_pixbuf_format_get_name (fmt),
g_free);
{
/* Turning textures into pngs, tiffs or jpegs is handled above */
if (!g_str_equal (name, "png") &&
!g_str_equal (name, "tiff") &&
!g_str_equal (name, "jpeg"))
gdk_content_register_serializer (GDK_TYPE_TEXTURE,
*m,
pixbuf_serializer,
gdk_pixbuf_format_get_name (fmt),
g_free);
gdk_content_register_serializer (GDK_TYPE_PIXBUF,
*m,
pixbuf_serializer,
gdk_pixbuf_format_get_name (fmt),
g_free);
}
}
g_strfreev (mimes);
g_free (name);
}
g_slist_free (formats);

View File

@ -41,11 +41,17 @@
#include "gdktextureprivate.h"
#include "gdkinternals.h"
#include "gdkintl.h"
#include "gdkmemorytextureprivate.h"
#include "gdkpaintable.h"
#include "gdksnapshot.h"
#include <graphene.h>
#include "loaders/gdkpngprivate.h"
#include "loaders/gdktiffprivate.h"
#include "loaders/gdkjpegprivate.h"
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
@ -108,9 +114,110 @@ gdk_texture_paintable_init (GdkPaintableInterface *iface)
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))
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))
@ -349,18 +456,23 @@ gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf)
GdkTexture *
gdk_texture_new_from_resource (const char *resource_path)
{
GError *error = NULL;
GBytes *bytes;
GdkTexture *texture;
GdkPixbuf *pixbuf;
GError *error = NULL;
g_return_val_if_fail (resource_path != NULL, NULL);
pixbuf = gdk_pixbuf_new_from_resource (resource_path, &error);
if (pixbuf == NULL)
g_error ("Resource path %s is not a valid image: %s", resource_path, error->message);
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;
texture = gdk_texture_new_for_pixbuf (pixbuf);
g_object_unref (pixbuf);
if (texture == NULL)
g_error ("Resource path %s s not a valid image: %s", resource_path, error->message);
return texture;
}
@ -400,6 +512,59 @@ gdk_texture_new_from_file (GFile *file,
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, 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:
@ -421,24 +586,26 @@ GdkTexture *
gdk_texture_new_from_bytes (GBytes *bytes,
GError **error)
{
GInputStream *stream;
GdkPixbuf *pixbuf;
GdkTexture *texture;
GError *internal_error = NULL;
g_return_val_if_fail (bytes != NULL, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
stream = g_memory_input_stream_new_from_bytes (bytes);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
g_object_unref (stream);
texture = gdk_texture_new_from_bytes_internal (bytes, &internal_error);
if (texture)
return texture;
if (pixbuf == NULL)
return NULL;
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;
}
texture = gdk_texture_new_for_pixbuf (pixbuf);
g_object_unref (pixbuf);
g_clear_error (&internal_error);
return texture;
return gdk_texture_new_from_bytes_pixbuf (bytes, error);
}
/**
@ -671,7 +838,8 @@ gdk_texture_get_render_data (GdkTexture *self,
* 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 `GFile` or other location, you might want to
* look into using the gdk-pixbuf library.
* use [method@Gdk.Texture.save_to_png_bytes] or look into the
* gdk-pixbuf library.
*
* Returns: %TRUE if saving succeeded, %FALSE on failure.
*/
@ -679,30 +847,110 @@ gboolean
gdk_texture_save_to_png (GdkTexture *texture,
const char *filename)
{
cairo_surface_t *surface;
cairo_status_t status;
GBytes *bytes;
gboolean result;
g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
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);
status = cairo_surface_write_to_png (surface, filename);
if (status != CAIRO_STATUS_SUCCESS ||
cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
result = FALSE;
else
result = TRUE;
cairo_surface_destroy (surface);
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);
}

View File

@ -38,6 +38,30 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GdkTexture, g_object_unref)
typedef struct _GdkTextureClass GdkTextureClass;
#define GDK_TEXTURE_ERROR (gdk_texture_error_quark ())
GDK_AVAILABLE_IN_4_6
GQuark gdk_texture_error_quark (void);
/**
* GdkTextureError:
* @GDK_TEXTURE_ERROR_TOO_LARGE: Not enough memory to handle this image
* @GDK_TEXTURE_ERROR_CORRUPT_IMAGE: The image data appears corrupted
* @GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT: The image contains features
* that cannot be loaded
* @GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT: The image format is not supported
*
* Possible errors that can be returned by `GdkTexture` constructors.
*
* Since: 4.6
*/
typedef enum
{
GDK_TEXTURE_ERROR_TOO_LARGE,
GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
GDK_TEXTURE_ERROR_UNSUPPORTED_FORMAT,
} GdkTextureError;
GDK_AVAILABLE_IN_ALL
GType gdk_texture_get_type (void) G_GNUC_CONST;
@ -72,6 +96,13 @@ void gdk_texture_download_float (GdkTexture
GDK_AVAILABLE_IN_ALL
gboolean gdk_texture_save_to_png (GdkTexture *texture,
const char *filename);
GDK_AVAILABLE_IN_4_6
GBytes * gdk_texture_save_to_png_bytes (GdkTexture *texture);
GDK_AVAILABLE_IN_4_6
gboolean gdk_texture_save_to_tiff (GdkTexture *texture,
const char *filename);
GDK_AVAILABLE_IN_4_6
GBytes * gdk_texture_save_to_tiff_bytes (GdkTexture *texture);
G_END_DECLS

View File

@ -35,9 +35,8 @@ struct _GdkTextureClass {
gsize stride);
};
gpointer gdk_texture_new (const GdkTextureClass *klass,
int width,
int height);
gboolean gdk_texture_can_load (GBytes *bytes);
GdkTexture * gdk_texture_new_for_surface (cairo_surface_t *surface);
cairo_surface_t * gdk_texture_download_surface (GdkTexture *texture);
/* NB: GdkMemoryTexture */

329
gdk/loaders/gdkjpeg.c Normal file
View File

@ -0,0 +1,329 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#include "config.h"
#include "gdkjpegprivate.h"
#include "gdkintl.h"
#include "gdktexture.h"
#include "gdkmemorytextureprivate.h"
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>
/* {{{ Error handling */
/* No sigsetjmp on Windows */
#ifndef HAVE_SIGSETJMP
#define sigjmp_buf jmp_buf
#define sigsetjmp(jb, x) setjmp(jb)
#define siglongjmp longjmp
#endif
struct error_handler_data {
struct jpeg_error_mgr pub;
sigjmp_buf setjmp_buffer;
GError **error;
};
static void
fatal_error_handler (j_common_ptr cinfo)
{
struct error_handler_data *errmgr;
char buffer[JMSG_LENGTH_MAX];
errmgr = (struct error_handler_data *) cinfo->err;
cinfo->err->format_message (cinfo, buffer);
if (errmgr->error && *errmgr->error == NULL)
g_set_error (errmgr->error,
GDK_TEXTURE_ERROR,
GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
_("Error interpreting JPEG image file (%s)"), buffer);
siglongjmp (errmgr->setjmp_buffer, 1);
g_assert_not_reached ();
}
static void
output_message_handler (j_common_ptr cinfo)
{
/* do nothing */
}
/* }}} */
/* {{{ Format conversion */
static void
convert_rgba_to_rgb (guchar *data,
int width,
int height,
int stride)
{
gsize x, y;
guchar *src, *dest;
for (y = 0; y < height; y++)
{
src = data;
dest = data;
for (x = 0; x < width; x++)
{
guint32 pixel;
memcpy (&pixel, src, sizeof (guint32));
dest[0] = (pixel & 0x00ff0000) >> 16;
dest[1] = (pixel & 0x0000ff00) >> 8;
dest[2] = (pixel & 0x000000ff) >> 0;
dest += 3;
src += 4;
}
data += stride;
}
}
static void
convert_grayscale_to_rgb (guchar *data,
int width,
int height,
int stride)
{
gsize x, y;
guchar *dest, *src;
for (y = 0; y < height; y++)
{
src = data + width;
dest = data + 3 * width;
for (x = 0; x < width; x++)
{
dest -= 3;
src -= 1;
dest[0] = *src;
dest[1] = *src;
dest[2] = *src;
}
data += stride;
}
}
static void
convert_cmyk_to_rgba (guchar *data,
int width,
int height,
int stride)
{
gsize x, r;
guchar *dest;
for (r = 0; r < height; r++)
{
dest = data;
for (x = 0; x < width; x++)
{
int c, m, y, k;
c = dest[0];
m = dest[1];
y = dest[2];
k = dest[3];
dest[0] = k * c / 255;
dest[1] = k * m / 255;
dest[2] = k * y / 255;
dest[3] = 255;
dest += 4;
}
data += stride;
}
}
/* }}} */
/* {{{ Public API */
GdkTexture *
gdk_load_jpeg (GBytes *input_bytes,
GError **error)
{
struct jpeg_decompress_struct info;
struct error_handler_data jerr;
guint width, height, stride;
unsigned char *data;
unsigned char *row[1];
GBytes *bytes;
GdkTexture *texture;
GdkMemoryFormat format;
info.err = jpeg_std_error (&jerr.pub);
jerr.pub.error_exit = fatal_error_handler;
jerr.pub.output_message = output_message_handler;
jerr.error = error;
if (sigsetjmp (jerr.setjmp_buffer, 1))
{
jpeg_destroy_decompress (&info);
return NULL;
}
jpeg_create_decompress (&info);
jpeg_mem_src (&info,
g_bytes_get_data (input_bytes, NULL),
g_bytes_get_size (input_bytes));
jpeg_read_header (&info, TRUE);
jpeg_start_decompress (&info);
width = info.output_width;
height = info.output_height;
switch ((int)info.out_color_space)
{
case JCS_GRAYSCALE:
case JCS_RGB:
stride = 3 * width;
data = g_try_malloc_n (stride, height);
format = GDK_MEMORY_R8G8B8;
break;
case JCS_CMYK:
stride = 4 * width;
data = g_try_malloc_n (stride, height);
format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED;
break;
default:
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
_("Unsupported JPEG colorspace (%d)"), info.out_color_space);
jpeg_destroy_decompress (&info);
return NULL;
}
if (!data)
{
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE,
_("Not enough memory for image size %ux%u"), width, height);
jpeg_destroy_decompress (&info);
return NULL;
}
while (info.output_scanline < info.output_height)
{
row[0] = (unsigned char *)(&data[stride * info.output_scanline]);
jpeg_read_scanlines (&info, row, 1);
}
switch ((int)info.out_color_space)
{
case JCS_GRAYSCALE:
convert_grayscale_to_rgb (data, width, height, stride);
format = GDK_MEMORY_R8G8B8;
break;
case JCS_RGB:
break;
case JCS_CMYK:
convert_cmyk_to_rgba (data, width, height, stride);
break;
default:
g_assert_not_reached ();
}
jpeg_finish_decompress (&info);
jpeg_destroy_decompress (&info);
bytes = g_bytes_new_take (data, stride * height);
texture = gdk_memory_texture_new (width, height,
format,
bytes, stride);
g_bytes_unref (bytes);
return texture;
}
GBytes *
gdk_save_jpeg (GdkTexture *texture)
{
struct jpeg_compress_struct info;
struct error_handler_data jerr;
struct jpeg_error_mgr err;
guchar *data = NULL;
gulong size = 0;
guchar *input = NULL;
guchar *row;
int width, height, stride;
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
info.err = jpeg_std_error (&jerr.pub);
jerr.pub.error_exit = fatal_error_handler;
jerr.pub.output_message = output_message_handler;
jerr.error = NULL;
if (sigsetjmp (jerr.setjmp_buffer, 1))
{
free (data);
g_free (input);
jpeg_destroy_compress (&info);
return NULL;
}
info.err = jpeg_std_error (&err);
jpeg_create_compress (&info);
info.image_width = width;
info.image_height = height;
info.input_components = 3;
info.in_color_space = JCS_RGB;
jpeg_set_defaults (&info);
jpeg_set_quality (&info, 75, TRUE);
jpeg_mem_dest (&info, &data, &size);
stride = width * 4;
input = g_malloc (stride * height);
gdk_texture_download (texture, input, stride);
convert_rgba_to_rgb (data, width, height, stride);
jpeg_start_compress (&info, TRUE);
while (info.next_scanline < info.image_height)
{
row = &input[info.next_scanline * stride];
jpeg_write_scanlines (&info, &row, 1);
}
jpeg_finish_compress (&info);
g_free (input);
jpeg_destroy_compress (&info);
return g_bytes_new_with_free_func (data, size, (GDestroyNotify) free, NULL);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@ -0,0 +1,43 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#ifndef __GDK_JPEG_PRIVATE_H__
#define __GDK_JPEG_PRIVATE_H__
#include "gdkmemorytexture.h"
#include <gio/gio.h>
#define JPEG_SIGNATURE "\xff\xd8"
GdkTexture *gdk_load_jpeg (GBytes *bytes,
GError **error);
GBytes *gdk_save_jpeg (GdkTexture *texture);
static inline gboolean
gdk_is_jpeg (GBytes *bytes)
{
const char *data;
gsize size;
data = g_bytes_get_data (bytes, &size);
return size > strlen (JPEG_SIGNATURE) &&
memcmp (data, JPEG_SIGNATURE, strlen (JPEG_SIGNATURE)) == 0;
}
#endif

568
gdk/loaders/gdkpng.c Normal file
View File

@ -0,0 +1,568 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#include "config.h"
#include "gdkpngprivate.h"
#include "gdkintl.h"
#include "gdkmemorytextureprivate.h"
#include "gdktexture.h"
#include "gdktextureprivate.h"
#include "gsk/ngl/fp16private.h"
#include <png.h>
#include <stdio.h>
/* The main difference between the png load/save code here and
* gdk-pixbuf is that we can support loading 16-bit data in the
* future, and we can extract gamma and colorspace information
* to produce linear, color-corrected data.
*/
/* {{{ Callbacks */
/* No sigsetjmp on Windows */
#ifndef HAVE_SIGSETJMP
#define sigjmp_buf jmp_buf
#define sigsetjmp(jb, x) setjmp(jb)
#define siglongjmp longjmp
#endif
typedef struct
{
guchar *data;
gsize size;
gsize position;
} png_io;
static void
png_read_func (png_structp png,
png_bytep data,
png_size_t size)
{
png_io *io;
io = png_get_io_ptr (png);
if (io->position + size > io->size)
png_error (png, "Read past EOF");
memcpy (data, io->data + io->position, size);
io->position += size;
}
static void
png_write_func (png_structp png,
png_bytep data,
png_size_t size)
{
png_io *io;
io = png_get_io_ptr (png);
if (io->position > io->size ||
io->size - io->position < size)
{
io->size = io->position + size;
io->data = g_realloc (io->data, io->size);
}
memcpy (io->data + io->position, data, size);
io->position += size;
}
static void
png_flush_func (png_structp png)
{
}
static png_voidp
png_malloc_callback (png_structp o,
png_size_t size)
{
return g_try_malloc (size);
}
static void
png_free_callback (png_structp o,
png_voidp x)
{
g_free (x);
}
static void
png_simple_error_callback (png_structp png,
png_const_charp error_msg)
{
GError **error = png_get_error_ptr (png);
if (error && !*error)
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
_("Error reading png (%s)"), error_msg);
longjmp (png_jmpbuf (png), 1);
}
static void
png_simple_warning_callback (png_structp png,
png_const_charp error_msg)
{
}
/* }}} */
/* {{{ Format conversion */
static void
unpremultiply (guchar *data,
int width,
int height)
{
gsize x, y;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
guchar *b = &data[x * 4];
guint32 pixel;
guchar alpha;
memcpy (&pixel, b, sizeof (guint32));
alpha = (pixel & 0xff000000) >> 24;
if (alpha == 0)
{
b[0] = 0;
b[1] = 0;
b[2] = 0;
b[3] = 0;
}
else
{
b[0] = (((pixel & 0x00ff0000) >> 16) * 255 + alpha / 2) / alpha;
b[1] = (((pixel & 0x0000ff00) >> 8) * 255 + alpha / 2) / alpha;
b[2] = (((pixel & 0x000000ff) >> 0) * 255 + alpha / 2) / alpha;
b[3] = alpha;
}
}
data += width * 4;
}
}
static void
unpremultiply_float_to_16bit (guchar *data,
int width,
int height)
{
gsize x, y;
float *src = (float *)data;;
guint16 *dest = (guint16 *)data;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
float r, g, b, a;
r = src[0];
g = src[1];
b = src[2];
a = src[3];
if (a == 0)
{
dest[0] = 0;
dest[1] = 0;
dest[2] = 0;
dest[3] = 0;
}
else
{
dest[0] = (guint16) CLAMP (65536.f * r / a, 0.f, 65535.f);
dest[1] = (guint16) CLAMP (65536.f * g / a, 0.f, 65535.f);
dest[2] = (guint16) CLAMP (65536.f * b / a, 0.f, 65535.f);
dest[3] = (guint16) CLAMP (65536.f * a, 0.f, 65535.f);
}
dest += 4;
src += 4;
}
}
}
static inline int
multiply_alpha (int alpha, int color)
{
int temp = (alpha * color) + 0x80;
return ((temp + (temp >> 8)) >> 8);
}
static void
premultiply_data (png_structp png,
png_row_infop row_info,
png_bytep data)
{
unsigned int i;
for (i = 0; i < row_info->rowbytes; i += 4)
{
uint8_t *base = &data[i];
uint8_t alpha = base[3];
uint32_t p;
if (alpha == 0)
{
p = 0;
}
else
{
uint8_t red = base[0];
uint8_t green = base[1];
uint8_t blue = base[2];
if (alpha != 0xff)
{
red = multiply_alpha (alpha, red);
green = multiply_alpha (alpha, green);
blue = multiply_alpha (alpha, blue);
}
p = ((uint32_t)alpha << 24) | (red << 16) | (green << 8) | (blue << 0);
}
memcpy (base, &p, sizeof (uint32_t));
}
}
static void
convert_bytes_to_data (png_structp png,
png_row_infop row_info,
png_bytep data)
{
unsigned int i;
for (i = 0; i < row_info->rowbytes; i += 4)
{
uint8_t *base = &data[i];
uint8_t red = base[0];
uint8_t green = base[1];
uint8_t blue = base[2];
uint32_t pixel;
pixel = (0xffu << 24) | (red << 16) | (green << 8) | (blue << 0);
memcpy (base, &pixel, sizeof (uint32_t));
}
}
static void
premultiply_16bit (guchar *data,
int width,
int height,
int stride)
{
gsize x, y;
guint16 *src;
for (y = 0; y < height; y++)
{
src = (guint16 *)data;
for (x = 0; x < width; x++)
{
float alpha = src[x * 4 + 3] / 65535.f;
src[x * 4 ] = (guint16) CLAMP (src[x * 4 ] * alpha, 0.f, 65535.f);
src[x * 4 + 1] = (guint16) CLAMP (src[x * 4 + 1] * alpha, 0.f, 65535.f);
src[x * 4 + 2] = (guint16) CLAMP (src[x * 4 + 2] * alpha, 0.f, 65535.f);
}
data += stride;
}
}
/* }}} */
/* {{{ Public API */
GdkTexture *
gdk_load_png (GBytes *bytes,
GError **error)
{
png_io io;
png_struct *png = NULL;
png_info *info;
guint width, height;
int depth, color_type;
int interlace, stride;
GdkMemoryFormat format;
guchar *buffer = NULL;
guchar **row_pointers = NULL;
GBytes *out_bytes;
GdkTexture *texture;
int bpp;
io.data = (guchar *)g_bytes_get_data (bytes, &io.size);
io.position = 0;
png = png_create_read_struct_2 (PNG_LIBPNG_VER_STRING,
error,
png_simple_error_callback,
png_simple_warning_callback,
NULL,
png_malloc_callback,
png_free_callback);
if (png == NULL)
g_error ("Out of memory");
info = png_create_info_struct (png);
if (info == NULL)
g_error ("Out of memory");
png_set_read_fn (png, &io, png_read_func);
if (sigsetjmp (png_jmpbuf (png), 1))
{
g_free (buffer);
g_free (row_pointers);
png_destroy_read_struct (&png, &info, NULL);
return NULL;
}
png_read_info (png, info);
png_get_IHDR (png, info,
&width, &height, &depth,
&color_type, &interlace, NULL, NULL);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb (png);
if (color_type == PNG_COLOR_TYPE_GRAY)
png_set_expand_gray_1_2_4_to_8 (png);
if (png_get_valid (png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha (png);
if (depth == 8)
png_set_filler (png, 0xff, PNG_FILLER_AFTER);
if (depth < 8)
png_set_packing (png);
if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb (png);
if (interlace != PNG_INTERLACE_NONE)
png_set_interlace_handling (png);
png_read_update_info (png, info);
png_get_IHDR (png, info,
&width, &height, &depth,
&color_type, &interlace, NULL, NULL);
if ((depth != 8 && depth != 16) ||
!(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_RGB_ALPHA))
{
png_destroy_read_struct (&png, &info, NULL);
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_UNSUPPORTED_CONTENT,
_("Failed to parse png image"));
return NULL;
}
switch (color_type)
{
case PNG_COLOR_TYPE_RGB_ALPHA:
if (depth == 8)
{
format = GDK_MEMORY_DEFAULT;
png_set_read_user_transform_fn (png, premultiply_data);
}
else
{
format = GDK_MEMORY_R16G16B16A16_PREMULTIPLIED;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
png_set_swap (png);
#endif
}
break;
case PNG_COLOR_TYPE_RGB:
if (depth == 8)
{
format = GDK_MEMORY_DEFAULT;
png_set_read_user_transform_fn (png, convert_bytes_to_data);
}
else
{
format = GDK_MEMORY_R16G16B16;
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
png_set_swap (png);
#endif
}
break;
default:
g_assert_not_reached ();
}
bpp = gdk_memory_format_bytes_per_pixel (format);
stride = width * bpp;
if (stride % 8)
stride += 8 - stride % 8;
buffer = g_try_malloc_n (height, stride);
row_pointers = g_try_malloc_n (height, sizeof (char *));
if (!buffer || !row_pointers)
{
g_free (buffer);
g_free (row_pointers);
png_destroy_read_struct (&png, &info, NULL);
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE,
_("Not enough memory for image size %ux%u"), width, height);
return NULL;
}
for (int i = 0; i < height; i++)
row_pointers[i] = &buffer[i * stride];
png_read_image (png, row_pointers);
png_read_end (png, info);
if (format == GDK_MEMORY_R16G16B16A16_PREMULTIPLIED)
premultiply_16bit (buffer, width, height, stride);
out_bytes = g_bytes_new_take (buffer, height * stride);
texture = gdk_memory_texture_new (width, height, format, out_bytes, stride);
g_bytes_unref (out_bytes);
g_free (row_pointers);
png_destroy_read_struct (&png, &info, NULL);
return texture;
}
GBytes *
gdk_save_png (GdkTexture *texture)
{
png_struct *png = NULL;
png_info *info;
png_io io = { NULL, 0, 0 };
guint width, height, stride;
guchar *data = NULL;
guchar *row;
int y;
GdkTexture *mtexture;
GdkMemoryFormat format;
int png_format;
int depth;
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
mtexture = gdk_texture_download_texture (texture);
format = gdk_memory_texture_get_format (GDK_MEMORY_TEXTURE (mtexture));
switch (format)
{
case GDK_MEMORY_B8G8R8A8_PREMULTIPLIED:
case GDK_MEMORY_A8R8G8B8_PREMULTIPLIED:
case GDK_MEMORY_R8G8B8A8_PREMULTIPLIED:
case GDK_MEMORY_B8G8R8A8:
case GDK_MEMORY_A8R8G8B8:
case GDK_MEMORY_R8G8B8A8:
case GDK_MEMORY_A8B8G8R8:
case GDK_MEMORY_R8G8B8:
case GDK_MEMORY_B8G8R8:
stride = width * 4;
data = g_malloc_n (stride, height);
gdk_texture_download (mtexture, data, stride);
unpremultiply (data, width, height);
png_format = PNG_COLOR_TYPE_RGB_ALPHA;
depth = 8;
break;
case GDK_MEMORY_R16G16B16:
case GDK_MEMORY_R16G16B16A16_PREMULTIPLIED:
case GDK_MEMORY_R16G16B16_FLOAT:
case GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED:
case GDK_MEMORY_R32G32B32_FLOAT:
case GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED:
data = g_malloc_n (width * 16, height);
gdk_texture_download_float (mtexture, (float *)data, width * 4);
unpremultiply_float_to_16bit (data, width, height);
png_format = PNG_COLOR_TYPE_RGB_ALPHA;
stride = width * 8;
depth = 16;
break;
case GDK_MEMORY_N_FORMATS:
default:
g_assert_not_reached ();
}
png = png_create_write_struct_2 (PNG_LIBPNG_VER_STRING, NULL,
png_simple_error_callback,
png_simple_warning_callback,
NULL,
png_malloc_callback,
png_free_callback);
if (!png)
return NULL;
info = png_create_info_struct (png);
if (!info)
{
png_destroy_read_struct (&png, NULL, NULL);
return NULL;
}
if (sigsetjmp (png_jmpbuf (png), 1))
{
g_free (data);
g_free (io.data);
png_destroy_read_struct (&png, &info, NULL);
return NULL;
}
png_set_write_fn (png, &io, png_write_func, png_flush_func);
png_set_IHDR (png, info, width, height, depth,
png_format,
PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT);
png_write_info (png, info);
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
png_set_swap (png);
#endif
for (y = 0, row = data; y < height; y++, row += stride)
png_write_rows (png, &row, 1);
png_write_end (png, info);
png_destroy_write_struct (&png, &info);
g_free (data);
return g_bytes_new_take (io.data, io.size);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@ -0,0 +1,43 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#ifndef __GDK_PNG_PRIVATE_H__
#define __GDK_PNG_PRIVATE_H__
#include "gdktexture.h"
#include <gio/gio.h>
#define PNG_SIGNATURE "\x89PNG"
GdkTexture *gdk_load_png (GBytes *bytes,
GError **error);
GBytes *gdk_save_png (GdkTexture *texture);
static inline gboolean
gdk_is_png (GBytes *bytes)
{
const char *data;
gsize size;
data = g_bytes_get_data (bytes, &size);
return size > strlen (PNG_SIGNATURE) &&
memcmp (data, PNG_SIGNATURE, strlen (PNG_SIGNATURE)) == 0;
}
#endif

513
gdk/loaders/gdktiff.c Normal file
View File

@ -0,0 +1,513 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#include "config.h"
#include "gdktiffprivate.h"
#include "gdkintl.h"
#include "gdkmemorytextureprivate.h"
#include "gdktexture.h"
#include "gdktextureprivate.h"
#include <tiffio.h>
/* Our main interest in tiff as an image format is that it is
* flexible enough to save all our texture formats without
* lossy conversions.
*
* The loader isn't meant to be a very versatile. It just aims
* to load the subset that we're saving ourselves. For anything
* else, we fall back to TIFFRGBAImage, which is the same api
* that gdk-pixbuf uses.
*/
/* {{{ IO handling */
typedef struct
{
GBytes **out_bytes;
gchar *data;
gsize size;
gsize position;
} TiffIO;
static void
tiff_io_warning (const char *module,
const char *fmt,
va_list ap) G_GNUC_PRINTF(2, 0);
static void
tiff_io_warning (const char *module,
const char *fmt,
va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
}
static void
tiff_io_error (const char *module,
const char *fmt,
va_list ap) G_GNUC_PRINTF(2, 0);
static void
tiff_io_error (const char *module,
const char *fmt,
va_list ap)
{
g_logv (G_LOG_DOMAIN, G_LOG_LEVEL_MESSAGE, fmt, ap);
}
static tsize_t
tiff_io_no_read (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
return (tsize_t) -1;
}
static tsize_t
tiff_io_read (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
TiffIO *io = (TiffIO *) handle;
gsize read;
if (io->position >= io->size)
return 0;
read = MIN (size, io->size - io->position);
memcpy (buffer, io->data + io->position, read);
io->position += read;
return (tsize_t) read;
}
static tsize_t
tiff_io_no_write (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
return (tsize_t) -1;
}
static tsize_t
tiff_io_write (thandle_t handle,
tdata_t buffer,
tsize_t size)
{
TiffIO *io = (TiffIO *) handle;
if (io->position > io->size ||
io->size - io->position < size)
{
io->size = io->position + size;
io->data = g_realloc (io->data, io->size);
}
memcpy (io->data + io->position, buffer, size);
io->position += size;
return (tsize_t) size;
}
static toff_t
tiff_io_seek (thandle_t handle,
toff_t offset,
int whence)
{
TiffIO *io = (TiffIO *) handle;
switch (whence)
{
default:
return -1;
case SEEK_SET:
break;
case SEEK_CUR:
offset += io->position;
break;
case SEEK_END:
offset += io->size;
break;
}
if (offset < 0)
return -1;
io->position = offset;
return offset;
}
static int
tiff_io_close (thandle_t handle)
{
TiffIO *io = (TiffIO *) handle;
if (io->out_bytes)
*io->out_bytes = g_bytes_new_take (io->data, io->size);
g_free (io);
return 0;
}
static toff_t
tiff_io_get_file_size (thandle_t handle)
{
TiffIO *io = (TiffIO *) handle;
return io->size;
}
static TIFF *
tiff_open_read (GBytes *bytes)
{
TiffIO *io;
TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning);
TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error);
io = g_new0 (TiffIO, 1);
io->data = (char *) g_bytes_get_data (bytes, &io->size);
return TIFFClientOpen ("GTK-read", "r",
(thandle_t) io,
tiff_io_read,
tiff_io_no_write,
tiff_io_seek,
tiff_io_close,
tiff_io_get_file_size,
NULL, NULL);
}
static TIFF *
tiff_open_write (GBytes **result)
{
TiffIO *io;
TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning);
TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error);
io = g_new0 (TiffIO, 1);
io->out_bytes = result;
return TIFFClientOpen ("GTK-write", "w",
(thandle_t) io,
tiff_io_no_read,
tiff_io_write,
tiff_io_seek,
tiff_io_close,
tiff_io_get_file_size,
NULL, NULL);
}
/* }}} */
/* {{{ Format conversion */
static void
flip_02 (guchar *data,
int width,
int height,
int stride)
{
gsize x, y;
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
guchar tmp;
tmp = data[x * 4];
data[x * 4] = data[x * 4 + 2];
data[x * 4 + 2] = tmp;
}
data += stride;
}
}
/* }}} */
/* {{{ Public API */
static struct {
GdkMemoryFormat format;
guint16 bits_per_sample;
guint16 samples_per_pixel;
guint16 sample_format;
} format_data[] = {
{ GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, 8, 4, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R8G8B8, 8, 3, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R16G16B16, 16, 3, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_UINT },
{ GDK_MEMORY_R16G16B16_FLOAT, 16, 3, SAMPLEFORMAT_IEEEFP },
{ GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED, 16, 4, SAMPLEFORMAT_IEEEFP },
{ GDK_MEMORY_R32G32B32_FLOAT, 32, 3, SAMPLEFORMAT_IEEEFP },
{ GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED, 32, 4, SAMPLEFORMAT_IEEEFP },
};
GBytes *
gdk_save_tiff (GdkTexture *texture)
{
TIFF *tif;
int width, height, stride;
guint16 bits_per_sample = 0;
guint16 samples_per_pixel = 0;
guint16 sample_format = 0;
const guchar *line;
const guchar *data;
guchar *new_data = NULL;
GBytes *result = NULL;
GdkTexture *memory_texture;
GdkMemoryFormat format;
tif = tiff_open_write (&result);
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
memory_texture = gdk_texture_download_texture (texture);
format = gdk_memory_texture_get_format (GDK_MEMORY_TEXTURE (memory_texture));
for (int i = 0; i < G_N_ELEMENTS (format_data); i++)
{
if (format == format_data[i].format)
{
data = gdk_memory_texture_get_data (GDK_MEMORY_TEXTURE (memory_texture));
stride = gdk_memory_texture_get_stride (GDK_MEMORY_TEXTURE (memory_texture));
bits_per_sample = format_data[i].bits_per_sample;
samples_per_pixel = format_data[i].samples_per_pixel;
sample_format = format_data[i].sample_format;
break;
}
}
if (bits_per_sample == 0)
{
/* An 8-bit format we don't have in the table, handle
* it by converting to R8G8B8A8_PREMULTIPLIED
*/
stride = width * 4;
new_data = g_malloc (stride * height);
gdk_texture_download (memory_texture, new_data, stride);
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
flip_02 (new_data, width, height, stride);
#endif
data = new_data;
bits_per_sample = 8;
samples_per_pixel = 4;
sample_format = SAMPLEFORMAT_UINT;
}
TIFFSetField (tif, TIFFTAG_SOFTWARE, "GTK");
TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField (tif, TIFFTAG_IMAGELENGTH, height);
TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, bits_per_sample);
TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, samples_per_pixel);
TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, sample_format);
TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
TIFFSetField (tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
// TODO: save gamma / colorspace
if (samples_per_pixel > 3)
{
guint16 extra_samples[] = { EXTRASAMPLE_ASSOCALPHA };
TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, extra_samples);
}
TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
line = (const guchar *)data;
for (int y = 0; y < height; y++)
{
if (TIFFWriteScanline (tif, (void *)line, y, 0) == -1)
{
TIFFClose (tif);
g_free (new_data);
g_object_unref (memory_texture);
return NULL;
}
line += stride;
}
TIFFFlushData (tif);
TIFFClose (tif);
g_assert (result);
g_free (new_data);
g_object_unref (memory_texture);
return result;
}
static GdkTexture *
load_fallback (TIFF *tif,
GError **error)
{
int width, height;
guchar *data;
GBytes *bytes;
GdkTexture *texture;
TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &height);
data = g_malloc (width * height * 4);
if (!TIFFReadRGBAImageOriented (tif, width, height, (uint32 *)data, ORIENTATION_TOPLEFT, 1))
{
g_set_error_literal (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
_("Failed to load RGB data from TIFF file"));
g_free (data);
return NULL;
}
bytes = g_bytes_new_take (data, width * height * 4);
texture = gdk_memory_texture_new (width, height,
GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
bytes,
width * 4);
g_bytes_unref (bytes);
return texture;
}
GdkTexture *
gdk_load_tiff (GBytes *input_bytes,
GError **error)
{
TIFF *tif;
guint16 samples_per_pixel;
guint16 bits_per_sample;
guint16 photometric;
guint16 planarconfig;
guint16 sample_format;
guint16 orientation;
guint32 width, height;
GdkMemoryFormat format;
guchar *data, *line;
gsize stride;
int bpp;
GBytes *bytes;
GdkTexture *texture;
tif = tiff_open_read (input_bytes);
TIFFSetDirectory (tif, 0);
TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLEFORMAT, &sample_format);
TIFFGetFieldDefaulted (tif, TIFFTAG_PHOTOMETRIC, &photometric);
TIFFGetFieldDefaulted (tif, TIFFTAG_PLANARCONFIG, &planarconfig);
TIFFGetFieldDefaulted (tif, TIFFTAG_ORIENTATION, &orientation);
TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGELENGTH, &height);
if (samples_per_pixel == 4)
{
guint16 extra;
guint16 *extra_types;
if (!TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types))
extra = 0;
if (extra == 0 || extra_types[0] != EXTRASAMPLE_ASSOCALPHA)
{
texture = load_fallback (tif, error);
TIFFClose (tif);
return texture;
}
}
format = 0;
for (int i = 0; i < G_N_ELEMENTS (format_data); i++)
{
if (format_data[i].sample_format == sample_format &&
format_data[i].bits_per_sample == bits_per_sample &&
format_data[i].samples_per_pixel == samples_per_pixel)
{
format = format_data[i].format;
break;
}
}
if (format == 0 ||
photometric != PHOTOMETRIC_RGB ||
planarconfig != PLANARCONFIG_CONTIG ||
TIFFIsTiled (tif) ||
orientation != ORIENTATION_TOPLEFT)
{
texture = load_fallback (tif, error);
TIFFClose (tif);
return texture;
}
stride = width * gdk_memory_format_bytes_per_pixel (format);
g_assert (TIFFScanlineSize (tif) == stride);
data = g_try_malloc_n (height, stride);
if (!data)
{
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_TOO_LARGE,
_("Not enough memory for image size %ux%u"), width, height);
TIFFClose (tif);
return NULL;
}
line = data;
for (int y = 0; y < height; y++)
{
if (TIFFReadScanline (tif, line, y, 0) == -1)
{
g_set_error (error,
GDK_TEXTURE_ERROR, GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
_("Reading data failed at row %d"), y);
TIFFClose (tif);
g_free (data);
return NULL;
}
line += stride;
}
bpp = gdk_memory_format_bytes_per_pixel (format);
bytes = g_bytes_new_take (data, width * height * bpp);
texture = gdk_memory_texture_new (width, height,
format,
bytes, width * bpp);
g_bytes_unref (bytes);
TIFFClose (tif);
return texture;
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@ -0,0 +1,46 @@
/* GDK - The GIMP Drawing Kit
* Copyright (C) 2021 Red Hat, Inc.
*
* 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/>.
*/
#ifndef __GDK_TIFF_PRIVATE_H__
#define __GDK_TIFF_PRIVATE_H__
#include "gdktexture.h"
#include <gio/gio.h>
#define TIFF_SIGNATURE1 "MM\x00\x2a"
#define TIFF_SIGNATURE2 "II\x2a\x00"
GdkTexture *gdk_load_tiff (GBytes *bytes,
GError **error);
GBytes * gdk_save_tiff (GdkTexture *texture);
static inline gboolean
gdk_is_tiff (GBytes *bytes)
{
const char *data;
gsize size;
data = g_bytes_get_data (bytes, &size);
return (size > strlen (TIFF_SIGNATURE1) &&
memcmp (data, TIFF_SIGNATURE1, strlen (TIFF_SIGNATURE1)) == 0) ||
(size > strlen (TIFF_SIGNATURE2) &&
memcmp (data, TIFF_SIGNATURE2, strlen (TIFF_SIGNATURE2)) == 0);
}
#endif

View File

@ -51,6 +51,9 @@ gdk_public_sources = files([
'gdktoplevelsize.c',
'gdktoplevel.c',
'gdkdragsurface.c',
'loaders/gdkpng.c',
'loaders/gdktiff.c',
'loaders/gdkjpeg.c',
])
gdk_public_headers = files([
@ -201,6 +204,9 @@ gdk_deps = [
platform_gio_dep,
pangocairo_dep,
vulkan_dep,
png_dep,
tiff_dep,
jpeg_dep,
]
if profiler_enabled
@ -257,7 +263,7 @@ endif
libgdk = static_library('gdk',
sources: [gdk_sources, gdk_backends_gen_headers, gdkconfig],
dependencies: gdk_deps + [libgtk_css_dep],
link_with: [libgtk_css, ],
link_with: [libgtk_css],
include_directories: [confinc, gdkx11_inc, wlinc],
c_args: libgdk_c_args + common_cflags,
link_whole: gdk_backends,

View File

@ -85,24 +85,17 @@ parse_texture (GtkCssParser *parser,
scheme = g_uri_parse_scheme (url);
if (scheme && g_ascii_strcasecmp (scheme, "data") == 0)
{
GInputStream *stream;
GdkPixbuf *pixbuf;
GBytes *bytes;
texture = NULL;
bytes = gtk_css_data_url_parse (url, NULL, &error);
if (bytes)
{
stream = g_memory_input_stream_new_from_bytes (bytes);
texture = gdk_texture_new_from_bytes (bytes, &error);
g_bytes_unref (bytes);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &error);
g_object_unref (stream);
if (pixbuf != NULL)
{
texture = gdk_texture_new_for_pixbuf (pixbuf);
g_object_unref (pixbuf);
}
}
else
{
texture = NULL;
}
}
else
@ -2699,26 +2692,23 @@ render_node_print (Printer *p,
case GSK_TEXTURE_NODE:
{
GdkTexture *texture = gsk_texture_node_get_texture (node);
cairo_surface_t *surface;
GByteArray *array;
GBytes *bytes;
start_node (p, "texture");
append_rect_param (p, "bounds", &node->bounds);
surface = gdk_texture_download_surface (texture);
array = g_byte_array_new ();
cairo_surface_write_to_png_stream (surface, cairo_write_array, array);
bytes = gdk_texture_save_to_png_bytes (texture);
_indent (p);
g_string_append (p->str, "texture: url(\"data:image/png;base64,");
b64 = base64_encode_with_linebreaks (array->data, array->len);
b64 = base64_encode_with_linebreaks (g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes));
append_escaping_newlines (p->str, b64);
g_free (b64);
g_string_append (p->str, "\");\n");
end_node (p);
g_byte_array_free (array, TRUE);
cairo_surface_destroy (surface);
g_bytes_unref (bytes);
}
break;

View File

@ -20,6 +20,8 @@
#include "gdkpixbufutilsprivate.h"
#include "gtkscalerprivate.h"
#include "gdk/gdktextureprivate.h"
static GdkPixbuf *
load_from_stream (GdkPixbufLoader *loader,
GInputStream *stream,
@ -91,7 +93,6 @@ size_prepared_cb (GdkPixbufLoader *loader,
*/
GdkPixbuf *
_gdk_pixbuf_new_from_stream_scaled (GInputStream *stream,
const char *format,
double scale,
GCancellable *cancellable,
GError **error)
@ -99,14 +100,7 @@ _gdk_pixbuf_new_from_stream_scaled (GInputStream *stream,
GdkPixbufLoader *loader;
GdkPixbuf *pixbuf;
if (format)
{
loader = gdk_pixbuf_loader_new_with_type (format, error);
if (!loader)
return NULL;
}
else
loader = gdk_pixbuf_loader_new ();
loader = gdk_pixbuf_loader_new ();
if (scale != 0)
g_signal_connect (loader, "size-prepared",
@ -153,7 +147,6 @@ size_prepared_cb2 (GdkPixbufLoader *loader,
GdkPixbuf *
_gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream,
const char *format,
int width,
int height,
gboolean aspect,
@ -164,14 +157,7 @@ _gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream,
GdkPixbuf *pixbuf;
int scales[3];
if (format)
{
loader = gdk_pixbuf_loader_new_with_type (format, error);
if (!loader)
return NULL;
}
else
loader = gdk_pixbuf_loader_new ();
loader = gdk_pixbuf_loader_new ();
scales[0] = width;
scales[1] = height;
@ -188,11 +174,10 @@ _gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream,
GdkPixbuf *
_gdk_pixbuf_new_from_stream (GInputStream *stream,
const char *format,
GCancellable *cancellable,
GError **error)
{
return _gdk_pixbuf_new_from_stream_scaled (stream, format, 0, cancellable, error);
return _gdk_pixbuf_new_from_stream_scaled (stream, 0, cancellable, error);
}
/* Like gdk_pixbuf_new_from_resource_at_scale, but
@ -201,7 +186,6 @@ _gdk_pixbuf_new_from_stream (GInputStream *stream,
*/
GdkPixbuf *
_gdk_pixbuf_new_from_resource_scaled (const char *resource_path,
const char *format,
double scale,
GError **error)
{
@ -212,7 +196,7 @@ _gdk_pixbuf_new_from_resource_scaled (const char *resource_path,
if (stream == NULL)
return NULL;
pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, format, scale, NULL, error);
pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, scale, NULL, error);
g_object_unref (stream);
return pixbuf;
@ -220,15 +204,13 @@ _gdk_pixbuf_new_from_resource_scaled (const char *resource_path,
GdkPixbuf *
_gdk_pixbuf_new_from_resource (const char *resource_path,
const char *format,
GError **error)
{
return _gdk_pixbuf_new_from_resource_scaled (resource_path, format, 0, error);
return _gdk_pixbuf_new_from_resource_scaled (resource_path, 0, error);
}
GdkPixbuf *
_gdk_pixbuf_new_from_resource_at_scale (const char *resource_path,
const char *format,
int width,
int height,
gboolean preserve_aspect,
@ -241,7 +223,7 @@ _gdk_pixbuf_new_from_resource_at_scale (const char *resource_path,
if (stream == NULL)
return NULL;
pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, format, width, height, preserve_aspect, NULL, error);
pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, width, height, preserve_aspect, NULL, error);
g_object_unref (stream);
return pixbuf;
@ -539,7 +521,7 @@ gtk_load_symbolic_texture_from_file (GFile *file)
if (stream == NULL)
return NULL;
pixbuf = _gdk_pixbuf_new_from_stream (stream, "png", NULL, NULL);
pixbuf = _gdk_pixbuf_new_from_stream (stream, NULL, NULL);
g_object_unref (stream);
if (pixbuf == NULL)
return NULL;
@ -597,42 +579,44 @@ GdkPaintable *
gdk_paintable_new_from_bytes_scaled (GBytes *bytes,
int scale_factor)
{
GdkPixbufLoader *loader;
GdkPixbuf *pixbuf = NULL;
LoaderData loader_data;
GdkTexture *texture;
GdkPaintable *paintable;
loader_data.scale_factor = scale_factor;
loader = gdk_pixbuf_loader_new ();
g_signal_connect (loader, "size-prepared",
G_CALLBACK (on_loader_size_prepared), &loader_data);
if (gdk_texture_can_load (bytes))
{
/* We know these formats can't be scaled */
texture = gdk_texture_new_from_bytes (bytes, NULL);
if (texture == NULL)
return NULL;
}
else
{
GdkPixbufLoader *loader;
gboolean success;
if (!gdk_pixbuf_loader_write_bytes (loader, bytes, NULL))
goto out;
loader = gdk_pixbuf_loader_new ();
g_signal_connect (loader, "size-prepared",
G_CALLBACK (on_loader_size_prepared), &loader_data);
if (!gdk_pixbuf_loader_close (loader, NULL))
goto out;
success = gdk_pixbuf_loader_write_bytes (loader, bytes, NULL);
/* close even when writing failed */
success &= gdk_pixbuf_loader_close (loader, NULL);
pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
if (pixbuf != NULL)
g_object_ref (pixbuf);
if (!success)
return NULL;
out:
gdk_pixbuf_loader_close (loader, NULL);
g_object_unref (loader);
texture = gdk_texture_new_for_pixbuf (gdk_pixbuf_loader_get_pixbuf (loader));
g_object_unref (loader);
}
if (!pixbuf)
return NULL;
texture = gdk_texture_new_for_pixbuf (pixbuf);
if (loader_data.scale_factor != 1)
paintable = gtk_scaler_new (GDK_PAINTABLE (texture), loader_data.scale_factor);
else
paintable = g_object_ref ((GdkPaintable *)texture);
g_object_unref (pixbuf);
g_object_unref (texture);
return paintable;

View File

@ -23,32 +23,26 @@
G_BEGIN_DECLS
GdkPixbuf *_gdk_pixbuf_new_from_stream (GInputStream *stream,
const char *format,
GCancellable *cancellable,
GError **error);
GdkPixbuf *_gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream,
const char *format,
int width,
int height,
gboolean aspect,
GCancellable *cancellable,
GError **error);
GdkPixbuf *_gdk_pixbuf_new_from_stream_scaled (GInputStream *stream,
const char *format,
double scale,
GCancellable *cancellable,
GError **error);
GdkPixbuf *_gdk_pixbuf_new_from_resource (const char *resource_path,
const char *format,
GError **error);
GdkPixbuf *_gdk_pixbuf_new_from_resource_at_scale (const char *resource_path,
const char *format,
int width,
int height,
gboolean preserve_aspect,
GError **error);
GdkPixbuf *_gdk_pixbuf_new_from_resource_scaled (const char *resource_path,
const char *format,
double scale,
GError **error);

View File

@ -210,7 +210,6 @@
#include "gtkbuilderprivate.h"
#include "gdkpixbufutilsprivate.h"
#include "gtkbuildableprivate.h"
#include "gtkbuilderlistitemfactory.h"
#include "gtkbuilderscopeprivate.h"
@ -226,7 +225,6 @@
#include "gtktypebuiltins.h"
#include "gtkicontheme.h"
#include "gtkiconthemeprivate.h"
#include "gdkpixbufutilsprivate.h"
#include "gtkdebug.h"
@ -2296,9 +2294,63 @@ gtk_builder_value_from_string_type (GtkBuilder *builder,
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF) ||
G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE) ||
if (G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE) ||
G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE))
{
GObject *object = g_hash_table_lookup (priv->objects, string);
char *filename;
GError *tmp_error = NULL;
GdkTexture *texture = NULL;
if (object)
{
if (g_type_is_a (G_OBJECT_TYPE (object), G_VALUE_TYPE (value)))
{
g_value_set_object (value, object);
return TRUE;
}
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not load image '%s': "
" '%s' is already used as object id for a %s",
string, string, G_OBJECT_TYPE_NAME (object));
return FALSE;
}
}
filename = _gtk_builder_get_resource_path (builder, string);
if (filename != NULL)
{
texture = gdk_texture_new_from_resource (filename);
}
else
{
GFile *file;
filename = _gtk_builder_get_absolute_filename (builder, string);
file = g_file_new_for_path (filename);
texture = gdk_texture_new_from_file (file, &tmp_error);
g_object_unref (file);
}
g_free (filename);
if (!texture)
{
g_warning ("Could not load image '%s': %s", string, tmp_error->message);
g_error_free (tmp_error);
texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH);
}
g_value_take_object (value, texture);
ret = TRUE;
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF))
{
char *filename;
GError *tmp_error = NULL;
@ -2344,28 +2396,13 @@ gtk_builder_value_from_string_type (GtkBuilder *builder,
if (pixbuf == NULL)
{
g_warning ("Could not load image '%s': %s",
string, tmp_error->message);
g_warning ("Could not load image '%s': %s", string, tmp_error->message);
g_error_free (tmp_error);
pixbuf = _gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, "png", NULL);
pixbuf = gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, NULL);
}
if (pixbuf)
{
if (G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE) ||
G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE))
{
GdkTexture *texture = gdk_texture_new_for_pixbuf (pixbuf);
g_value_set_object (value, texture);
g_object_unref (texture);
}
else
{
g_value_set_object (value, pixbuf);
}
g_object_unref (G_OBJECT (pixbuf));
}
g_value_take_object (value, pixbuf);
g_free (filename);

View File

@ -26,10 +26,9 @@
#include "gtkcssimageinvalidprivate.h"
#include "gtkcssimagepaintableprivate.h"
#include "gtkstyleproviderprivate.h"
#include "gdkpixbufutilsprivate.h"
#include "gtk/css/gtkcssdataurlprivate.h"
G_DEFINE_TYPE (GtkCssImageUrl, _gtk_css_image_url, GTK_TYPE_CSS_IMAGE)
static GtkCssImage *
@ -181,28 +180,28 @@ gtk_css_image_url_parse (GtkCssImage *image,
if (scheme && g_ascii_strcasecmp (scheme, "data") == 0)
{
GBytes *bytes;
GdkPaintable *paintable;
GError *error = NULL;
bytes = gtk_css_data_url_parse (url, NULL, &error);
if (bytes)
{
paintable = gdk_paintable_new_from_bytes_scaled (bytes, 1);
GdkTexture *texture;
texture = gdk_texture_new_from_bytes (bytes, &error);
g_bytes_unref (bytes);
if (paintable == NULL)
if (texture)
{
GdkPaintable *paintable = GDK_PAINTABLE (texture);
self->loaded_image = gtk_css_image_paintable_new (paintable, paintable);
}
else
{
error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
"Failed to load image from '%s'", url);
gtk_css_parser_emit_error (parser,
gtk_css_parser_get_start_location (parser),
gtk_css_parser_get_end_location (parser),
error);
g_clear_error (&error);
}
else
{
self->loaded_image = gtk_css_image_paintable_new (paintable, paintable);
}
}
else
{

View File

@ -3742,31 +3742,6 @@ gtk_icon_paintable_is_symbolic (GtkIconPaintable *icon)
return icon->is_symbolic;
}
static GLoadableIcon *
icon_get_loadable (GtkIconPaintable *icon)
{
GFile *file;
GLoadableIcon *loadable;
if (icon->loadable)
return g_object_ref (icon->loadable);
if (icon->is_resource)
{
char *uri = g_strconcat ("resource://", icon->filename, NULL);
file = g_file_new_for_uri (uri);
g_free (uri);
}
else
file = g_file_new_for_path (icon->filename);
loadable = G_LOADABLE_ICON (g_file_icon_new (file));
g_object_unref (file);
return loadable;
}
/* This function contains the complicated logic for deciding
* on the size at which to load the icon and loading it at
* that size.
@ -3775,7 +3750,6 @@ static void
icon_ensure_texture__locked (GtkIconPaintable *icon,
gboolean in_thread)
{
GdkPixbuf *source_pixbuf;
gint64 before;
int pixel_size;
GError *load_error = NULL;
@ -3795,11 +3769,10 @@ icon_ensure_texture__locked (GtkIconPaintable *icon,
/* At this point, we need to actually get the icon; either from the
* builtin image or by loading the file
*/
source_pixbuf = NULL;
#ifdef G_OS_WIN32
if (icon->win32_icon)
{
source_pixbuf = g_object_ref (icon->win32_icon);
icon->texture = gdk_texture_new_for_pixbuf (icon->win32_icon);
}
else
#endif
@ -3807,6 +3780,8 @@ icon_ensure_texture__locked (GtkIconPaintable *icon,
{
if (icon->is_svg)
{
GdkPixbuf *source_pixbuf;
if (gtk_icon_paintable_is_symbolic (icon))
source_pixbuf = gtk_make_symbolic_pixbuf_from_resource (icon->filename,
pixel_size, pixel_size,
@ -3814,33 +3789,67 @@ icon_ensure_texture__locked (GtkIconPaintable *icon,
&load_error);
else
source_pixbuf = _gdk_pixbuf_new_from_resource_at_scale (icon->filename,
"svg",
pixel_size, pixel_size,
TRUE, &load_error);
if (source_pixbuf)
{
icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf);
g_object_unref (source_pixbuf);
}
}
else
source_pixbuf = _gdk_pixbuf_new_from_resource (icon->filename,
g_str_has_suffix (icon->filename, ".xpm") ? "xpm" : "png",
&load_error);
if (source_pixbuf == NULL)
icon->texture = gdk_texture_new_from_resource (icon->filename);
}
else if (icon->filename)
{
if (icon->is_svg)
{
g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message);
g_clear_error (&load_error);
GdkPixbuf *source_pixbuf;
if (gtk_icon_paintable_is_symbolic (icon))
source_pixbuf = gtk_make_symbolic_pixbuf_from_path (icon->filename,
pixel_size, pixel_size,
icon->desired_scale,
&load_error);
else
{
GFile *file = g_file_new_for_path (icon->filename);
GInputStream *stream = G_INPUT_STREAM (g_file_read (file, NULL, &load_error));
g_object_unref (file);
if (stream)
{
source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream,
pixel_size, pixel_size,
TRUE, NULL,
&load_error);
g_object_unref (stream);
}
else
source_pixbuf = NULL;
}
if (source_pixbuf)
{
icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf);
g_object_unref (source_pixbuf);
}
}
else
{
icon->texture = gdk_texture_new_from_filename (icon->filename, &load_error);
}
}
else
{
GLoadableIcon *loadable;
GInputStream *stream;
GdkPixbuf *source_pixbuf;
loadable = icon_get_loadable (icon);
stream = g_loadable_icon_load (loadable,
g_assert (icon->loadable);
stream = g_loadable_icon_load (icon->loadable,
pixel_size,
NULL, NULL,
&load_error);
g_object_unref (loadable);
if (stream)
{
/* SVG icons are a special case - we just immediately scale them
@ -3848,46 +3857,32 @@ icon_ensure_texture__locked (GtkIconPaintable *icon,
*/
if (icon->is_svg)
{
if (gtk_icon_paintable_is_symbolic (icon))
source_pixbuf = gtk_make_symbolic_pixbuf_from_path (icon->filename,
source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream,
pixel_size, pixel_size,
icon->desired_scale,
TRUE, NULL,
&load_error);
else
source_pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream,
"svg",
pixel_size, pixel_size,
TRUE, NULL,
&load_error);
}
else
source_pixbuf = _gdk_pixbuf_new_from_stream (stream,
g_str_has_suffix (icon->filename, ".xpm") ? "xpm" : "png",
NULL, &load_error);
g_object_unref (stream);
}
if (source_pixbuf == NULL)
{
g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message);
g_clear_error (&load_error);
if (source_pixbuf)
{
icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf);
g_object_unref (source_pixbuf);
}
}
}
if (!source_pixbuf)
if (!icon->texture)
{
source_pixbuf = _gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, "png", NULL);
g_warning ("Failed to load icon %s: %s", icon->filename, load_error->message);
g_clear_error (&load_error);
icon->texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH);
icon->icon_name = g_strdup ("image-missing");
icon->is_symbolic = FALSE;
g_assert (source_pixbuf != NULL);
}
/* Actual scaling is done during rendering, so just keep the source pixbuf as a texture */
icon->texture = gdk_texture_new_for_pixbuf (source_pixbuf);
g_object_unref (source_pixbuf);
g_assert (icon->texture != NULL);
if (GDK_PROFILER_IS_RUNNING)
{
gint64 end = GDK_PROFILER_CURRENT_TIME;
@ -4089,28 +4084,6 @@ gtk_icon_paintable_new_for_file (GFile *file,
return icon;
}
static GtkIconPaintable *
gtk_icon_paintable_new_for_pixbuf (GtkIconTheme *icon_theme,
GdkPixbuf *pixbuf,
int size,
int scale)
{
GtkIconPaintable *icon;
int width, height;
if (size <= 0)
{
width = gdk_pixbuf_get_width (pixbuf);
height = gdk_pixbuf_get_height (pixbuf);
size = MAX (width, height);
}
icon = icon_paintable_new (NULL, size, scale);
icon->texture = gdk_texture_new_for_pixbuf (pixbuf);
return icon;
}
/**
* gtk_icon_theme_lookup_by_gicon:
* @self: a `GtkIconTheme`
@ -4140,17 +4113,23 @@ gtk_icon_theme_lookup_by_gicon (GtkIconTheme *self,
g_return_val_if_fail (GTK_IS_ICON_THEME (self), NULL);
g_return_val_if_fail (G_IS_ICON (gicon), NULL);
g_return_val_if_fail (size > 0, NULL);
g_return_val_if_fail (scale > 0, NULL);
/* We can't render emblemed icons atm, but at least render the base */
while (G_IS_EMBLEMED_ICON (gicon))
gicon = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (gicon));
g_assert (gicon); /* shut up gcc -Wnull-dereference */
if (GDK_IS_PIXBUF (gicon))
if (GDK_IS_TEXTURE (gicon))
{
GdkPixbuf *pixbuf = GDK_PIXBUF (gicon);
icon = gtk_icon_paintable_new_for_pixbuf (self, pixbuf, size, scale);
icon = icon_paintable_new (NULL, size, scale);
icon->texture = g_object_ref (GDK_TEXTURE (icon));
}
else if (GDK_IS_PIXBUF (gicon))
{
icon = icon_paintable_new (NULL, size, scale);
icon->texture = gdk_texture_new_for_pixbuf (GDK_PIXBUF (icon));
}
else if (G_IS_FILE_ICON (gicon))
{

View File

@ -207,6 +207,17 @@ foreach func : check_functions
endif
endforeach
# We use links() because sigsetjmp() is often a macro hidden behind other macros
cdata.set('HAVE_SIGSETJMP',
cc.links('''#define _POSIX_SOURCE
#include <setjmp.h>
int main (void) {
sigjmp_buf env;
sigsetjmp (env, 0);
return 0;
}''', name: 'sigsetjmp'),
)
# Check for __uint128_t (gcc) by checking for 128-bit division
uint128_t_src = '''int main() {
static __uint128_t v1 = 100;
@ -389,6 +400,16 @@ pangocairo_dep = dependency('pangocairo', version: pango_req,
pixbuf_dep = dependency('gdk-pixbuf-2.0', version: gdk_pixbuf_req,
fallback : ['gdk-pixbuf', 'gdkpixbuf_dep'],
default_options: ['png=enabled', 'jpeg=enabled', 'builtin_loaders=png,jpeg', 'man=false'])
png_dep = dependency('libpng',
fallback: ['libpng', 'libpng_dep'],
required: true)
tiff_dep = dependency('libtiff-4',
fallback: ['libtiff', 'libtiff4_dep'],
required: true)
jpeg_dep = dependency('libjpeg',
fallback: ['libjpeg-turbo', 'jpeg_dep'],
required: true)
epoxy_dep = dependency('epoxy', version: epoxy_req,
fallback: ['libepoxy', 'libepoxy_dep'])
harfbuzz_dep = dependency('harfbuzz', version: '>= 2.1.0', required: false,

12
subprojects/libpng.wrap Normal file
View File

@ -0,0 +1,12 @@
[wrap-file]
directory = libpng-1.6.37
source_url = https://github.com/glennrp/libpng/archive/v1.6.37.tar.gz
source_filename = libpng-1.6.37.tar.gz
source_hash = ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307
patch_url = https://wrapdb.mesonbuild.com/v2/libpng_1.6.37-3/get_patch
patch_filename = libpng-1.6.37-3-wrap.zip
patch_hash = 6c9f32fd9150b3a96ab89be52af664e32207e10aa9f5fb9aa015989ee2dd7100
[provide]
libpng = libpng_dep

12
subprojects/libtiff.wrap Normal file
View File

@ -0,0 +1,12 @@
[wrap-file]
directory = tiff-4.1.0
source_url = http://download.osgeo.org/libtiff/tiff-4.1.0.zip
source_filename = tiff-4.1.0.zip
source_hash = 6f3dbed9d2ecfed33c7192b5c01884078970657fa21b4ad28e3cdf3438eb2419
patch_filename = libtiff_4.1.0-4_patch.zip
patch_url = https://wrapdb.mesonbuild.com/v2/libtiff_4.1.0-4/get_patch
patch_hash = c0fe078d06e5a7f2480a96c3897a7b3b9fa9a42c08fb76ae5f1dd59e0519a14e
[provide]
libtiff-4 = libtiff4_dep

View File

@ -30,6 +30,49 @@ compare_rgba_values (GValue *v1, GValue *v2)
(GdkRGBA *)g_value_get_boxed (v2));
}
static gboolean
textures_equal (GdkTexture *t1, GdkTexture *t2)
{
guchar *d1, *d2;
int width, height;
gboolean ret;
width = gdk_texture_get_width (t1);
height = gdk_texture_get_height (t1);
if (width != gdk_texture_get_width (t2))
return FALSE;
if (height != gdk_texture_get_height (t2))
return FALSE;
d1 = g_malloc (width * height * 4);
d2 = g_malloc (width * height * 4);
gdk_texture_download (t1, d1, width * 4);
gdk_texture_download (t2, d2, width * 4);
ret = memcmp (d1, d2, width * height * 4) == 0;
if (!ret)
{
gdk_texture_save_to_png (t1, "texture1.png");
gdk_texture_save_to_png (t2, "texture2.png");
}
g_free (d1);
g_free (d2);
return ret;
}
static gboolean
compare_texture_values (GValue *v1, GValue *v2)
{
return G_VALUE_TYPE (v1) == GDK_TYPE_TEXTURE &&
G_VALUE_TYPE (v2) == GDK_TYPE_TEXTURE &&
textures_equal ((GdkTexture *)g_value_get_object (v1),
(GdkTexture *)g_value_get_object (v2));
}
static gboolean
compare_file_values (GValue *v1, GValue *v2)
{
@ -125,7 +168,7 @@ test_content_roundtrip (const GValue *value,
TestData data = { 0, };
data.ostream = g_memory_output_stream_new_resizable ();
data.mime_type = g_strdup (mime_type);
data.mime_type = mime_type;
g_value_init (&data.value, G_VALUE_TYPE (value));
g_value_copy (value, &data.value);
data.compare = compare;
@ -182,6 +225,30 @@ test_content_color (void)
g_value_unset (&value);
}
static void
test_content_texture (gconstpointer data)
{
const char *mimetype = data;
GValue value = G_VALUE_INIT;
char *path;
GFile *file;
GdkTexture *texture;
GError *error = NULL;
path = g_test_build_filename (G_TEST_DIST, "image-data", "image.png", NULL);
file = g_file_new_for_path (path);
texture = gdk_texture_new_from_file (file, &error);
g_assert_no_error (error);
g_object_unref (file);
g_free (path);
g_value_init (&value, GDK_TYPE_TEXTURE);
g_value_set_object (&value, texture);
test_content_roundtrip (&value, mimetype, compare_texture_values);
g_value_unset (&value);
g_object_unref (texture);
}
static void
test_content_file (void)
{
@ -406,6 +473,8 @@ main (int argc, char *argv[])
g_test_add_func ("/content/text_plain_utf8", test_content_text_plain_utf8);
g_test_add_func ("/content/text_plain", test_content_text_plain);
g_test_add_func ("/content/color", test_content_color);
g_test_add_data_func ("/content/texture/png", "image/png", test_content_texture);
g_test_add_data_func ("/content/texture/tiff", "image/tiff", test_content_texture);
g_test_add_func ("/content/file", test_content_file);
g_test_add_func ("/content/files", test_content_files);
g_test_add_func ("/content/custom", test_custom_format);

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

129
testsuite/gdk/image.c Normal file
View File

@ -0,0 +1,129 @@
#include <gtk/gtk.h>
#include "gdk/loaders/gdkpngprivate.h"
#include "gdk/loaders/gdktiffprivate.h"
#include "gdk/loaders/gdkjpegprivate.h"
static void
assert_texture_equal (GdkTexture *t1,
GdkTexture *t2)
{
int width;
int height;
int stride;
guchar *d1;
guchar *d2;
width = gdk_texture_get_width (t1);
height = gdk_texture_get_height (t1);
stride = 4 * width;
g_assert_cmpint (width, ==, gdk_texture_get_width (t2));
g_assert_cmpint (height, ==, gdk_texture_get_height (t2));
d1 = g_malloc (stride * height);
d2 = g_malloc (stride * height);
gdk_texture_download (t1, d1, stride);
gdk_texture_download (t2, d2, stride);
g_assert_cmpmem (d1, stride * height, d2, stride * height);
g_free (d1);
g_free (d2);
}
static void
test_load_image (gconstpointer data)
{
const char *filename = data;
GdkTexture *texture;
char *path;
GFile *file;
GBytes *bytes;
GError *error = NULL;
path = g_test_build_filename (G_TEST_DIST, "image-data", filename, NULL);
file = g_file_new_for_path (path);
bytes = g_file_load_bytes (file, NULL, NULL, &error);
g_assert_no_error (error);
if (g_str_has_suffix (filename, ".png"))
texture = gdk_load_png (bytes, &error);
else if (g_str_has_suffix (filename, ".tiff"))
texture = gdk_load_tiff (bytes, &error);
else if (g_str_has_suffix (filename, ".jpeg"))
texture = gdk_load_jpeg (bytes, &error);
else
g_assert_not_reached ();
g_assert_no_error (error);
g_assert_true (GDK_IS_TEXTURE (texture));
g_assert_cmpint (gdk_texture_get_width (texture), ==, 32);
g_assert_cmpint (gdk_texture_get_height (texture), ==, 32);
g_object_unref (texture);
g_bytes_unref (bytes);
g_object_unref (file);
g_free (path);
}
static void
test_save_image (gconstpointer test_data)
{
const char *filename = test_data;
char *path;
GFile *file;
GdkTexture *texture;
GFile *file2;
GdkTexture *texture2;
GError *error = NULL;
GBytes *bytes = NULL;
GIOStream *stream;
path = g_test_build_filename (G_TEST_DIST, "image-data", filename, NULL);
file = g_file_new_for_path (path);
texture = gdk_texture_new_from_file (file, &error);
g_assert_no_error (error);
if (g_str_has_suffix (filename, ".png"))
bytes = gdk_save_png (texture);
else if (g_str_has_suffix (filename, ".tiff"))
bytes = gdk_save_tiff (texture);
else
g_assert_not_reached ();
file2 = g_file_new_tmp ("imageXXXXXX", (GFileIOStream **)&stream, NULL);
g_object_unref (stream);
g_file_replace_contents (file2,
g_bytes_get_data (bytes, NULL),
g_bytes_get_size (bytes),
NULL, FALSE, 0,
NULL, NULL, &error);
g_assert_no_error (error);
texture2 = gdk_texture_new_from_file (file2, &error);
g_assert_no_error (error);
assert_texture_equal (texture, texture2);
g_bytes_unref (bytes);
g_object_unref (texture2);
g_object_unref (file2);
g_object_unref (texture);
g_object_unref (file);
g_free (path);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_data_func ("/image/load/png", "image.png", test_load_image);
g_test_add_data_func ("/image/load/tiff", "image.tiff", test_load_image);
g_test_add_data_func ("/image/load/jpeg", "image.jpeg", test_load_image);
g_test_add_data_func ("/image/save/png", "image.png", test_save_image);
g_test_add_data_func ("/image/save/tiff", "image.tiff", test_save_image);
return g_test_run ();
}

View File

@ -12,6 +12,10 @@ typedef enum {
TEXTURE_METHOD_LOCAL,
TEXTURE_METHOD_GL,
TEXTURE_METHOD_GL_RELEASED,
TEXTURE_METHOD_PNG,
TEXTURE_METHOD_PNG_PIXBUF,
TEXTURE_METHOD_TIFF,
TEXTURE_METHOD_TIFF_PIXBUF,
N_TEXTURE_METHODS
} TextureMethod;
@ -448,6 +452,68 @@ create_texture (GdkMemoryFormat format,
gdk_gl_texture_release (GDK_GL_TEXTURE (texture));
break;
case TEXTURE_METHOD_PNG:
{
GBytes *bytes = gdk_texture_save_to_png_bytes (texture);
g_assert (bytes);
g_object_unref (texture);
texture = gdk_texture_new_from_bytes (bytes, NULL);
g_assert (texture);
g_bytes_unref (bytes);
}
break;
case TEXTURE_METHOD_PNG_PIXBUF:
{
GInputStream *stream;
GdkPixbuf *pixbuf;
GBytes *bytes;
bytes = gdk_texture_save_to_png_bytes (texture);
g_assert (bytes);
g_object_unref (texture);
stream = g_memory_input_stream_new_from_bytes (bytes);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
g_object_unref (stream);
g_assert (pixbuf);
texture = gdk_texture_new_for_pixbuf (pixbuf);
g_assert (texture);
g_object_unref (pixbuf);
g_bytes_unref (bytes);
}
break;
case TEXTURE_METHOD_TIFF:
{
GBytes *bytes = gdk_texture_save_to_tiff_bytes (texture);
g_assert (bytes);
g_object_unref (texture);
texture = gdk_texture_new_from_bytes (bytes, NULL);
g_assert (texture);
g_bytes_unref (bytes);
}
break;
case TEXTURE_METHOD_TIFF_PIXBUF:
{
GInputStream *stream;
GdkPixbuf *pixbuf;
GBytes *bytes;
bytes = gdk_texture_save_to_png_bytes (texture);
g_assert (bytes);
g_object_unref (texture);
stream = g_memory_input_stream_new_from_bytes (bytes);
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, NULL);
g_object_unref (stream);
g_assert (pixbuf);
texture = gdk_texture_new_for_pixbuf (pixbuf);
g_assert (texture);
g_object_unref (pixbuf);
g_bytes_unref (bytes);
}
break;
case N_TEXTURE_METHODS:
default:
g_assert_not_reached ();
@ -584,7 +650,7 @@ add_test (const char *name,
{
for (method = 0; method < N_TEXTURE_METHODS; method++)
{
const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released" };
const char *method_names[N_TEXTURE_METHODS] = { "local", "gl", "gl-released", "png", "png-pixbuf", "tiff", "tiff-pixbuf" };
char *test_name = g_strdup_printf ("%s/%s/%s",
name,
g_enum_get_value (enum_class, format)->value_nick,

View File

@ -49,6 +49,31 @@ foreach t : tests
)
endforeach
internal_tests = [
'image'
]
foreach t : internal_tests
test_exe = executable(t, '@0@.c'.format(t),
c_args: common_cflags,
dependencies: libgtk_static_dep,
install: get_option('install-tests'),
install_dir: testexecdir,
)
test(t, test_exe,
args: [ '--tap', '-k' ],
protocol: 'tap',
env: [
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
'DBUS_SESSION_BUS_ADDRESS=',
],
suite: 'gdk',
)
endforeach
if get_option('install-tests')
foreach t : tests
test_cdata = configuration_data()
@ -63,4 +88,5 @@ if get_option('install-tests')
endforeach
install_subdir('clipboard-data', install_dir: testexecdir)
install_subdir('image-data', install_dir: testexecdir)
endif

View File

@ -1,7 +1,6 @@
texture {
bounds: 0 0 50 50;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAABmJLR0QA/wD/AP+gvaeTAAAAKUlE\
QVQYlWP8z3DmPwMaYGQwYUQXY0IXwAUGUCGGoxkYGBiweXAoeAYAz44F3e3U1xUAAAAASUVORK5C\
YII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAKUlEQVQYlWP8z3DmPwMaYGQwYUQX\
Y0IXwAUGUCGGoxkYGBiweXAoeAYAz44F3e3U1xUAAAAASUVORK5CYII=\
");
}

View File

@ -39,129 +39,129 @@ transform {
offset: 0.180392 0.203922 0.211765 0;
child: texture {
bounds: 1068.6 0 256 256;
texture: url("data:image/png;base64,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\
texture: url("data:image/png;base64,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\
Wk3S1gAAAABJRU5ErkJggg==\
");
}
}
@ -277,8 +277,8 @@ GIZhGIZhGIZhGIYh/D/GS6zNWk3S1gAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 4 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAH0lE\
QVQ4jWNgGPKAEY3/n1R9TNRzy0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAH0lEQVQ4jWNgGPKAEY3/n1R9TNRz\
y0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\
");
}
}
@ -294,10 +294,10 @@ QVQ4jWNgGPKAEY3/n1R9TNRzy0CB0TAYDQOqAADB/QMKPEUKLAAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAfUlE\
QVQ4je3RMQoCMRBG4Q+voEIKC/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1Fti\
H2d/uUTwHJIVuuj1LYINbklyivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8\
g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4je3RMQoCMRBG4Q+voEIK\
C/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1FtiH2d/uUTwHJIVuuj1LYINbkly\
ivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8g/m2YvyJmYoPk2IlW2Wofj0A\
AAAASUVORK5CYII=\
");
}
}
@ -373,9 +373,9 @@ transform {
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -430,9 +430,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -473,11 +473,11 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.654902 0.666667 0.666667 0;
child: texture {
bounds: 0 8 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAyklE\
QVQ4jc3S3UpCURAF4K8w8ypQA1EfpkcI8SmEEh9STYzQQNCE3sELiQiiLhy1I/ucbl0wDHt+Fmv2\
DOeGCh4xxic2eIrYVdSM8JNqbmMeyZS9oPnnnUEZr5FY4h63aKCDdeSe8wgGEXxDNaGujvcTRRns\
mTuJ5v3Mp5bBRwQbCYK8PwGl8N/hvxIEF4nYAZfhF+HvioqL8BCyVqgl8jVM5ewfrjGLgjW6uAnr\
Oq5xWqSi5XgLeYfU+m+UMvqYYGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAyklEQVQ4jc3S3UpCURAF4K8w8ypQ\
A1EfpkcI8SmEEh9STYzQQNCE3sELiQiiLhy1I/ucbl0wDHt+Fmv2DOeGCh4xxic2eIrYVdSM8JNq\
bmMeyZS9oPnnnUEZr5FY4h63aKCDdeSe8wgGEXxDNaGujvcTRRnsmTuJ5v3Mp5bBRwQbCYK8PwGl\
8N/hvxIEF4nYAZfhF+HvioqL8BCyVqgl8jVM5ewfrjGLgjW6uAnrOq5xWqSi5XgLeYfU+m+UMvqY\
YGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\
");
}
}
@ -557,10 +557,10 @@ Oq5xWqSi5XgLeYfU+m+UMvqYYGt3ykP0QuUZ4hei9keg1mtPrwAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 4 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAfUlE\
QVQ4je3RMQoCMRBG4Q+voEIKC/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1Fti\
H2d/uUTwHJIVuuj1LYINbklyivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8\
g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4je3RMQoCMRBG4Q+voEIK\
C/H+d7Cwd1GULSzEUyg2azOyQ1gxB9gHgcnknxdCmJliwAHr1FtiH2d/uUTwHJIVuuj1LYINbkly\
ivqObYuglgx4YDcVXPwQvPFM+1esJgquxjf3qS4tgmM1UJKkaxF8g/m2YvyJmYoPk2IlW2Wofj0A\
AAAASUVORK5CYII=\
");
}
}
@ -600,9 +600,9 @@ g/m2YvyJmYoPk2IlW2Wofj0AAAAASUVORK5CYII=\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -638,9 +638,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -679,9 +679,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -747,8 +747,8 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.615686 0.623529 0.623529 0;
child: texture {
bounds: 4 8 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAHElE\
QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4jWNgGAXDADCi8f+Tqo+J\
em4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
");
}
}
@ -767,9 +767,8 @@ QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
offset: 0.615686 0.623529 0.623529 0;
child: texture {
bounds: 4 8 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAKElE\
QVQ4jWNgGPbgPxTjBEyU2jDwBjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJg\
gg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKElEQVQ4jWNgGPbgPxTjBEyU2jDw\
BjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJggg==\
");
}
}
@ -819,8 +818,8 @@ gg==\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 4 8 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAHElE\
QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHElEQVQ4jWNgGAXDADCi8f+Tqo+J\
em4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
");
}
}
@ -846,9 +845,8 @@ QVQ4jWNgGAXDADCi8f+Tqo+Jem4ZBUMYAABDXwEEvj+CVwAAAABJRU5ErkJggg==\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 4 8 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAKElE\
QVQ4jWNgGPbgPxTjBEyU2jDwBjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJg\
gg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKElEQVQ4jWNgGPbgPxTjBEyU2jDw\
BjCi8fH6F5s+il1ACIyEWBgGAADypgUMy1PhdwAAAABJRU5ErkJggg==\
");
}
}
@ -881,10 +879,10 @@ gg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\
QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\
YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\
i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\
XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\
OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\
TkSuQmCC\
");
}
}
@ -947,9 +945,9 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\
QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\
GmjkhFgCAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\
/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\
\
");
}
}
@ -985,10 +983,10 @@ GmjkhFgCAAAAAElFTkSuQmCC\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\
QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\
YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\
i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\
XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\
OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\
TkSuQmCC\
");
}
}
@ -1051,9 +1049,9 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\
QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\
GmjkhFgCAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\
/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\
\
");
}
}
@ -1152,9 +1150,9 @@ GmjkhFgCAAAAAElFTkSuQmCC\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\
QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\
GmjkhFgCAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\
/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\
\
");
}
}
@ -1193,9 +1191,9 @@ GmjkhFgCAAAAAElFTkSuQmCC\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAASklE\
QVQokWNgGJmAjYGBoZWBgeERFLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwD\
sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAASklEQVQokWNgGJmAjYGBoZWBgeER\
FLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwDsDJA/PQQihsZiAyc4QYASeYT\
s7b/ALUAAAAASUVORK5CYII=\
");
}
}
@ -1264,9 +1262,9 @@ sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 0 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAOUlE\
QVQokWNgGAVEAW8GBoYnDAwM/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4\
GmjkhFgCAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAOUlEQVQokWNgGAVEAW8GBoYnDAwM\
/3HgxwwMDF7YND7GowmGH8EUM1HDqV4EbH3EwMDgSQ2Lhj0AABu4GmjkhFgCAAAAAElFTkSuQmCC\
\
");
}
}
@ -1292,13 +1290,12 @@ GmjkhFgCAAAAAElFTkSuQmCC\
transform: translate(8, 11.5) rotate(12.2788) translate(-8, -11.5);
child: texture {
bounds: 0 3.5 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABE0lE\
QVQ4jbXSPy9DURjH8U9bSqhEmog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HOb\
G2muRuJJbnJynvP9nt9zc/iHKuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T\
8B3mMs6OoYh8evMSnRDsZcCFkJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBG\
hqAcgtlI0hecxjgNHKRuSVcRKyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv\
+AhhHRfR6yeg9+4rWE0J8sjhLQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAA\
SUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jbXSPy9DURjH8U9bSqhE\
mog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HObG2muRuJJbnJynvP9nt9zc/iH\
KuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T8B3mMs6OoYh8evMSnRDsZcCF\
kJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBGhqAcgtlI0hecxjgNHKRuSVcR\
KyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv+AhhHRfR6yeg9+4rWE0J8sjh\
LQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAASUVORK5CYII=\
");
}
}
@ -1315,13 +1312,12 @@ SUVORK5CYII=\
transform: translate(8, 11.5) rotate(12.2788) translate(-8, -11.5);
child: texture {
bounds: 0 3.5 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABE0lE\
QVQ4jbXSPy9DURjH8U9bSqhEmog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HOb\
G2muRuJJbnJynvP9nt9zc/iHKuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T\
8B3mMs6OoYh8evMSnRDsZcCFkJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBG\
hqAcgtlI0hecxjgNHKRuSVcRKyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv\
+AhhHRfR6yeg9+4rWE0J8sjhLQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAA\
SUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jbXSPy9DURjH8U9bSqhE\
mog/A6PFJGGRCInRZrQZTN6B1XsysJgMRklJDRYGtVBtWkRr6HObG2muRuJJbnJynvP9nt9zc/iH\
KuEIZ3jEFeaHhbfwgO6P73AYeAftAfANFn+Dp1FLQffYx8yw0Y9T8B3mMs6OoYh8evMSnRDsZcCF\
kJf1fraRaCygFevzDEE3JBMYRSMR1CPaVwYsYk/Fup1swC2e0MBGhqAcgtlI0hecxjgNHKRuSVcR\
KyHJi5EL0axiNxodrEWiFsaxhE1MBvOOCjq51A3rOMFrSNp4RhMv+AhhHRfR6yeg9+4rWE0J8sjh\
LQTNgGsJlE6QVAnbWI65mzFOFdf4HMD8vb4BZVRHF0lPimAAAAAASUVORK5CYII=\
");
}
}
@ -1459,9 +1455,9 @@ SUVORK5CYII=\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -1500,9 +1496,9 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.831373 0.811765 0.792157 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAOklE\
QVQ4jWNgGAWDD5QzMDD8J4DrKTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACH\
ZRnQomjWaAAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAOklEQVQ4jWNgGAWDD5QzMDD8J4Dr\
KTGEoGZ8hhCtGZshODUz4zHgKAMDAyMDA8NBBgaGRlJtHwUkAACHZRnQomjWaAAAAABJRU5ErkJg\
gg==\
");
}
}
@ -1615,10 +1611,10 @@ ZRnQomjWaAAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 0 4 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAeUlE\
QVQ4jcWTQQqAIBBFn9HV6i4uOmaL7hF1hVbZRkF0tMmCPnwYdeahH4RcM+AEH4AV+jNJw8EnMGkB\
pf0MsiRDd4AAsdKBFhAywSTN8doIkBgGYLpKk0qvAVB+s0q9AHuk/zP4BLD7uiXIFWD0Re0TSd6A\
of3uXhd1yT8mvMGiQgAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAeUlEQVQ4jcWTQQqAIBBFn9HV6i4u\
OmaL7hF1hVbZRkF0tMmCPnwYdeahH4RcM+AEH4AV+jNJw8EnMGkBpf0MsiRDd4AAsdKBFhAywSTN\
8doIkBgGYLpKk0qvAVB+s0q9AHuk/zP4BLD7uiXIFWD0Re0TSd6Aof3uXhd1yT8mvMGiQgAAAABJ\
RU5ErkJggg==\
");
}
}
@ -2087,19 +2083,19 @@ of3uXhd1yT8mvMGiQgAAAABJRU5ErkJggg==\
transform: translate(225, -10);
child: texture {
bounds: 1 1 20 25;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAABmJLR0QA/wD/AP+gvaeTAAAChklE\
QVQ4jbXVTWsTQRgH8P88u2myu4ZEKVJqEj9Ar0V6kIQtPRWs1EpAPfegiFD8Av0C4sVbz1qt2IMe\
fUmTXhTzKbKbEhRslrCzsyYz46Ev1jRNX4L/47Dz22eGmXkYBqReryc6nfaSaZoPtMYNpdRlADAM\
7CrFvgHypeNkN6enp7v9c1n/QKVSuUuEF6lUys5mM2nLcpBImACAbrcLzjmCIOjEsQgB/bhYnH03\
ENR6lba33WdEbHly8ppj2/ag4g8TRRzNph9KqdaKxepTxlbVP2C1WnmeTCaXC4WCQ2QMxQ6ilESj\
0QjjOF4rldwVACAAqNW+LBkGnQsDACID+XzBMQxa3tr6vAgArF6vJzjvNHK5wsRpyzwpnIfwfX9H\
Sn2dwrB9J5lMnrpnw2LbDsbGxtKMqUVizLyfyWTTF9b2k8lk04Zh3CPG1Mwo1R3EcRxojRnSGlnT\
TIwMmqYJpeQVUgqMHTveFwtjDEREu91ub2Ss1+uBiLUJYN+jiI8Mcs6hNX0lrbvrQRB0RgWDoN1R\
qveGxsd/vRVCRKNUyXmEOBaC83iTpqbKv7VWK81mM1RKnhuTUmJnxwsB+WR+fj4mACiVZl8pJdd9\
3+eAPgen0Wz6XCn5qlicew3sPw57f8JDIcSW53nh2VANz2tEcSyqUrJHB6OHoOu6vVbrx0IU8U+N\
RoNrPQzV8H2PCxFVLevSbdd1D88dHf2sXC7LVuvnkhDRR887Cd3DOOfblpVe6G8DA+/IxsaGMTFx\
dTOVSs3l8wX771XS8DwviiJes+30rTP1lH7UslJzuVzBBgDfH44NBYG97sd554NtOze11iSEqFmW\
cyJ2KngEfQ8Atn18z/57/gBelEdqMNUvpgAAAABJRU5ErkJggg==\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAZCAYAAAAxFw7TAAAChklEQVQ4jbXVTWsTQRgH8P88u2my\
u4ZEKVJqEj9Ar0V6kIQtPRWs1EpAPfegiFD8Av0C4sVbz1qt2IMefUmTXhTzKbKbEhRslrCzsyYz\
46Ev1jRNX4L/47Dz22eGmXkYBqReryc6nfaSaZoPtMYNpdRlADAM7CrFvgHypeNkN6enp7v9c1n/\
QKVSuUuEF6lUys5mM2nLcpBImACAbrcLzjmCIOjEsQgB/bhYnH03ENR6lba33WdEbHly8ppj2/ag\
4g8TRRzNph9KqdaKxepTxlbVP2C1WnmeTCaXC4WCQ2QMxQ6ilESj0QjjOF4rldwVACAAqNW+LBkG\
nQsDACID+XzBMQxa3tr6vAgArF6vJzjvNHK5wsRpyzwpnIfwfX9HSn2dwrB9J5lMnrpnw2LbDsbG\
xtKMqUVizLyfyWTTF9b2k8lk04Zh3CPG1Mwo1R3EcRxojRnSGlnTTIwMmqYJpeQVUgqMHTveFwtj\
DEREu91ub2Ss1+uBiLUJYN+jiI8Mcs6hNX0lrbvrQRB0RgWDoN1RqveGxsd/vRVCRKNUyXmEOBaC\
83iTpqbKv7VWK81mM1RKnhuTUmJnxwsB+WR+fj4mACiVZl8pJdd93+eAPgen0Wz6XCn5qlicew3s\
Pw57f8JDIcSW53nh2VANz2tEcSyqUrJHB6OHoOu6vVbrx0IU8U+NRoNrPQzV8H2PCxFVLevSbdd1\
D88dHf2sXC7LVuvnkhDRR887Cd3DOOfblpVe6G8DA+/IxsaGMTFxdTOVSs3l8wX771XS8DwviiJe\
s+30rTP1lH7UslJzuVzBBgDfH44NBYG97sd554NtOze11iSEqFmWcyJ2KngEfQ8Atn18z/57/gBe\
lEdqMNUvpgAAAABJRU5ErkJggg==\
");
}
}
@ -2442,10 +2438,10 @@ cyJ2KngEfQ8Atn18z/57/gBelEdqMNUvpgAAAABJRU5ErkJggg==\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 14 29 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\
QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\
YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\
i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\
XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\
OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\
TkSuQmCC\
");
}
}
@ -2459,12 +2455,12 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
offset: 0.196078 0.196078 0.196078 0;
child: texture {
bounds: 54 28 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA8klE\
QVQ4jbXSwSrEURTH8Y/YsZiUhfIGnkFJUrKSlIeQJ5AaUlPWdrKUJ9DMwhMQyZKVhSxEg7Lwz1i4\
f925c/9jSk7dbuee8/3dczqHf7BxbOEcL3jFFeqY/A1exBM6FecZq1XwPD76wOUpMJvCY3gYAO7g\
CCOpwMaA8CGGc+W3Msmnib+PoYjpErpNkhvhfTf4ewlcw0kscBPBx0l1S4k/hWvf4/2xZiRQYC3X\
J6ZxF/Iu48B60kJOZEb3jmzHwVHc9xFZxnsUa2MiLW9O7yIVOAh3+faJlYoWLeBR70jjnyvh0mrY\
xFkA3nCBnVzZf7Yvt2xyJ4TFGjYAAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA8klEQVQ4jbXSwSrEURTH8Y/YsZiU\
hfIGnkFJUrKSlIeQJ5AaUlPWdrKUJ9DMwhMQyZKVhSxEg7Lwz1i4f925c/9jSk7dbuee8/3dczqH\
f7BxbOEcL3jFFeqY/A1exBM6FecZq1XwPD76wOUpMJvCY3gYAO7gCCOpwMaA8CGGc+W3Msmnib+P\
oYjpErpNkhvhfTf4ewlcw0kscBPBx0l1S4k/hWvf4/2xZiRQYC3XJ6ZxF/Iu48B60kJOZEb3jmzH\
wVHc9xFZxnsUa2MiLW9O7yIVOAh3+faJlYoWLeBR70jjnyvh0mrYxFkA3nCBnVzZf7Yvt2xyJ4TF\
GjYAAAAASUVORK5CYII=\
");
}
}
@ -2520,11 +2516,11 @@ xFkA3nCBnVzZf7Yvt2xyJ4TFGjYAAAAASUVORK5CYII=\
offset: 0.196078 0.196078 0.196078 0;
child: texture {
bounds: 54 51 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAAz0lE\
QVQ4jbXSTU4CQRAF4A/iFlbKOVywMoRDGLyAe70D4o3kBqz9uQAXICxwcM2wmAY6Y3djjL6kku7q\
qtevfvgHDPCMd3wFe8MsvBVxhwp1xipMSsm7VsIYw5ZvlyIZZH4+oO3/xBV0Q8Ajeufqi9DHQ+z4\
SPxSUlBrGnvE9hcEVVxCHPxT1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+9\
6CZKqEPsOiVrolmS3CpvcFsoC82GPeFVM95tOE8Psv8Ue0ISW4s5Tmr/AAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAz0lEQVQ4jbXSTU4CQRAF4A/iFlbK\
OVywMoRDGLyAe70D4o3kBqz9uQAXICxwcM2wmAY6Y3djjL6kku7qqtevfvgHDPCMd3wFe8MsvBVx\
hwp1xipMSsm7VsIYw5ZvlyIZZH4+oO3/xBV0Q8Ajeufqi9DHQ+z4SPxSUlBrGnvE9hcEVVxCHPxT\
1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+96CZKqEPsOiVrolmS3CpvcFso\
C82GPeFVM95tOE8Psv8Ue0ISW4s5Tmr/AAAAAElFTkSuQmCC\
");
}
}
@ -2575,10 +2571,10 @@ SPxSUlBrGnvE9hcEVVxCHPxT1HARLktcJ4JG6GQIljHBPEOwKCh4iS+5Md4EFW3/BpecerDCve+9\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 14 75 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAAeElE\
QVQokb3QXwpAQBDH8e9uzscRXEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUp\
YBXUB+gEC19cAmyAQaAmRJloOiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAe\
i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAeElEQVQokb3QXwpAQBDH8e9uzscR\
XEXyJFE8KFoXcxheVm1j0Cp+tQ87M5/2D/yc8g2qgC0GGKDzSIUpYBXUB+gEC19cAmyAQaAmRJlo\
OiABJlGv5WnWD4dDq9i32tuOa41i+FiT719Gw/MT0rDj/Mu3sUAei77LDp72K7jAZx9tAAAAAElF\
TkSuQmCC\
");
}
}
@ -2592,12 +2588,12 @@ i77LDp72K7jAZx9tAAAAAElFTkSuQmCC\
offset: 0.196078 0.196078 0.196078 0;
child: texture {
bounds: 54 74 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA/klE\
QVQ4jaXTzypFURTH8Q9CN2FyXYmRjqmpiZkykoF0n+J6Bxl4CQ/gASTlAZjwApIRMhDiSkd0Dc6W\
3bHPPcmv1mCv/Vvf/W9t/qmBRG4aHYzhNORecIML5LF5qFTcxjFWcYQnDKKBFjI8hyBMxsX7mAzj\
u8TuhrGMuTJgBnsl4FsCQHHsJYzGgC1MJIxVGsFCDFhLmMrAsmZjQJYwzNcAxmPAZ8KwXgPoxYCr\
hCHDYh9ANwYcVJg6aFbMXfNz0y1cCucq6QG7uI1yOQ6Rf3fiq6JNN/1+vgZW8BFWfccJHlPb2lC0\
b68i7hVN1FdN7OBc8Ym6OMM2puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4jaXTzypFURTH8Q9CN2Fy\
XYmRjqmpiZkykoF0n+J6Bxl4CQ/gASTlAZjwApIRMhDiSkd0Dc6W3bHPPcmv1mCv/Vvf/W9t/qmB\
RG4aHYzhNORecIML5LF5qFTcxjFWcYQnDKKBFjI8hyBMxsX7mAzju8TuhrGMuTJgBnsl4FsCQHHs\
JYzGgC1MJIxVGsFCDFhLmMrAsmZjQJYwzNcAxmPAZ8KwXgPoxYCrhCHDYh9ANwYcVJg6aFbMXfNz\
0y1cCucq6QG7uI1yOQ6Rf3fiq6JNN/1+vgZW8BFWfccJHlPb2lC0b68i7hVN1FdN7OBc8Ym6OMM2\
puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\
");
}
}
@ -2659,9 +2655,9 @@ b68i7hVN1FdN7OBc8Ym6OMM2puqK/6wvIccweWvwsr0AAAAASUVORK5CYII=\
offset: 0.572549 0.584314 0.584314 0;
child: texture {
bounds: 14 98 14 14;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAABmJLR0QA/wD/AP+gvaeTAAAASklE\
QVQokWNgGJmAjYGBoZWBgeERFLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwD\
sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAASklEQVQokWNgGJmAjYGBoZWBgeER\
FLcwMDCwEqOxlYGB4T8abiZG42MsGh+iK2LCovE/MWLYNC4iUgwDsDJA/PQQihsZiAyc4QYASeYT\
s7b/ALUAAAAASUVORK5CYII=\
");
}
}
@ -2675,14 +2671,13 @@ sDJA/PQQihsZiAyc4QYASeYTs7b/ALUAAAAASUVORK5CYII=\
offset: 0.196078 0.196078 0.196078 0;
child: texture {
bounds: 54 97 16 16;
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABT0lE\
QVQ4jZXTv0vXcRDH8cdXzQQTEZFCUVAU1xZ/LCIYrrVkoTi6NLTpX+DoGrTnkoMgqOjk8hXBhqbA\
MALTSB1EpDQp/DF874Nvv4hfPTg4uHu+7n13n0+52+01/mAYu/hdov6atWIBS+i4D1iHSXzBRfgn\
vMWDUnAnfiZgsa+h+iawEhX4fAuc+TuUBwNe4BgHSdEpevG/KL7ACX4E8xw20Y+xRGAzxL8XxVn+\
FbqwAUeowkBS8Bft+FcUZ/mnMcZuDrPIxUzdmMMytvAL52hAE/owGq86REUOtZjAXixxCD1oxuMQ\
2MdOXOFj7KQRU9ki67F4hwtkPoMaia1EYv0O8CrOYnRlCl9Xfwh9xXgInSYNjpHHG3wLbjB9wXzS\
IY8RtMVodWhR+LHySd2HVOARpmNhpUY4w3s8pHC+1JrxEs+i65MQ3cO2qytsZcAlA5qEWoLbkBcA\
AAAASUVORK5CYII=\
texture: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABT0lEQVQ4jZXTv0vXcRDH8cdXzQQT\
EZFCUVAU1xZ/LCIYrrVkoTi6NLTpX+DoGrTnkoMgqOjk8hXBhqbAMALTSB1EpDQp/DF874Nvv4hf\
PTg4uHu+7n13n0+52+01/mAYu/hdov6atWIBS+i4D1iHSXzBRfgnvMWDUnAnfiZgsa+h+iawEhX4\
fAuc+TuUBwNe4BgHSdEpevG/KL7ACX4E8xw20Y+xRGAzxL8XxVn+FbqwAUeowkBS8Bft+FcUZ/mn\
McZuDrPIxUzdmMMytvAL52hAE/owGq86REUOtZjAXixxCD1oxuMQ2MdOXOFj7KQRU9ki67F4hwtk\
PoMaia1EYv0O8CrOYnRlCl9Xfwh9xXgInSYNjpHHG3wLbjB9wXzSIY8RtMVodWhR+LHySd2HVOAR\
pmNhpUY4w3s8pHC+1JrxEs+i65MQ3cO2qytsZcAlA5qEWoLbkBcAAAAASUVORK5CYII=\
");
}
}