/* Copyright (C) 2016 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 <gsk/gsk.h>
#include "gdktextureutilsprivate.h"
#include "gdk/gdktextureprivate.h"
#include "gtksnapshot.h"
#include "svg/gtksvg.h"
#include "gtksymbolicpaintable.h"
#include "gtkprivate.h"
/* {{{ svg helpers */
static GdkTexture *
svg_to_texture (GtkSvg *svg,
int width,
int height,
GdkRGBA *colors,
size_t n_colors)
{
GtkSnapshot *snapshot;
GskRenderNode *node;
cairo_surface_t *surface;
cairo_t *cr;
GdkTexture *texture;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
snapshot = gtk_snapshot_new ();
gtk_symbolic_paintable_snapshot_symbolic (GTK_SYMBOLIC_PAINTABLE (svg),
snapshot,
width, height,
colors, n_colors);
node = gtk_snapshot_free_to_node (snapshot);
if (node)
{
cr = cairo_create (surface);
gsk_render_node_draw (node, cr);
cairo_destroy (cr);
gsk_render_node_unref (node);
}
texture = gdk_texture_new_for_surface (surface);
cairo_surface_destroy (surface);
return texture;
}
static void
svg_load_error (GtkSvg *svg,
const GError *error,
gpointer user_data)
{
char **unsupported = user_data;
if (g_error_matches (error, GTK_SVG_ERROR, GTK_SVG_ERROR_NOT_IMPLEMENTED))
{
if (unsupported && !*unsupported)
*unsupported = g_strdup (error->message);
}
}
static GtkSvg *
svg_from_bytes (GBytes *bytes,
gboolean is_symbolic,
char **unsupported)
{
GtkSvg *svg;
gulong handler_id;
svg = gtk_svg_new ();
if (is_symbolic)
gtk_svg_set_features (svg, GTK_SVG_DEFAULT_FEATURES | GTK_SVG_TRADITIONAL_SYMBOLIC);
handler_id = g_signal_connect (svg, "error" , G_CALLBACK (svg_load_error), unsupported);
gtk_svg_load_from_bytes (svg, bytes);
g_signal_handler_disconnect (svg, handler_id);
return svg;
}
/* }}} */
/* {{{ Symbolic processing */
static GdkTexture *
load_symbolic_svg (GtkSvg *svg,
int width,
int height,
const char *fg_color,
const char *success_color,
const char *warning_color,
const char *error_color,
GError **error)
{
GdkRGBA colors[4 ];
gdk_rgba_parse (&colors[GTK_SYMBOLIC_COLOR_FOREGROUND], fg_color);
gdk_rgba_parse (&colors[GTK_SYMBOLIC_COLOR_SUCCESS], success_color);
gdk_rgba_parse (&colors[GTK_SYMBOLIC_COLOR_WARNING], warning_color);
gdk_rgba_parse (&colors[GTK_SYMBOLIC_COLOR_ERROR], error_color);
return svg_to_texture (svg, width, height, colors, 4 );
}
static gboolean
extract_plane (GdkTexture *src,
guchar *dst_data,
gsize dst_width,
gsize dst_height,
int from_plane,
int to_plane)
{
const guchar *src_data, *src_row;
int width, height;
gsize src_stride, dst_stride;
guchar *dst_row;
int x, y;
gboolean all_clear = TRUE ;
GdkTextureDownloader *downloader;
GBytes *bytes;
width = gdk_texture_get_width (src);
height = gdk_texture_get_height (src);
g_assert (width <= dst_width);
g_assert (height <= dst_height);
downloader = gdk_texture_downloader_new (src);
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R8G8B8A8);
bytes = gdk_texture_downloader_download_bytes (downloader, &src_stride);
src_data = g_bytes_get_data (bytes, NULL);
dst_stride = dst_width * 4 ;
for (y = 0 ; y < height; y++)
{
src_row = src_data + src_stride * y;
dst_row = dst_data + dst_stride * y;
for (x = 0 ; x < width; x++)
{
if (src_row[from_plane] != 0 )
all_clear = FALSE ;
dst_row[to_plane] = src_row[from_plane];
src_row += 4 ;
dst_row += 4 ;
}
}
gdk_texture_downloader_free (downloader);
g_bytes_unref (bytes);
return all_clear;
}
static GdkTexture *
keep_alpha (GdkTexture *src)
{
GdkTextureDownloader *downloader;
GBytes *bytes;
gsize stride;
gsize width, height;
guchar *data;
gsize size;
GdkTexture *res;
width = gdk_texture_get_width (src);
height = gdk_texture_get_height (src);
downloader = gdk_texture_downloader_new (src);
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R8G8B8A8);
data = g_bytes_unref_to_data (gdk_texture_downloader_download_bytes (downloader, &stride), &size);
for (gsize y = 0 ; y < height; y++)
{
guchar *row = data + stride * y;
for (gsize x = 0 ; x < width; x++)
{
row[0 ] = row[1 ] = row[2 ] = 0 ;
row += 4 ;
}
}
bytes = g_bytes_new_take (data, size);
res = gdk_memory_texture_new (width, height, GDK_MEMORY_R8G8B8A8, bytes, stride);
g_bytes_unref (bytes);
g_object_unref (src);
gdk_texture_downloader_free (downloader);
return res;
}
static gboolean
svg_has_symbolic_classes (GBytes *bytes)
{
#ifdef HAVE_MEMMEM
const char *data;
gsize len;
data = g_bytes_get_data (bytes, &len);
/* Not super precise, but good enough */
return memmem (data, len, "class=\" ", strlen (" class =\"" )) != NULL;
#else
return TRUE ;
#endif
}
static GdkTexture *
gdk_texture_new_from_bytes_symbolic (GBytes *bytes,
int width,
int height,
gboolean *out_only_fg,
GError **error)
{
GtkSvg *svg;
const char *r_string = "rgb(255,0,0)" ;
const char *g_string = "rgb(0,255,0)" ;
gboolean only_fg;
guchar *data;
GBytes *data_bytes;
GdkTexture *texture;
svg = gtk_svg_new_from_bytes (bytes);
if (width == 0 && height == 0 )
{
width = gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (svg));
height = gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (svg));
if (width == 0 || height == 0 )
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Symbolic icon has no intrinsic size; please set one in its SVG" );
g_object_unref (svg);
return NULL;
}
}
if (!svg_has_symbolic_classes (bytes))
{
texture = svg_to_texture (svg, width, height, NULL, 0 );
if (texture)
texture = keep_alpha (texture);
if (out_only_fg)
*out_only_fg = TRUE ;
g_object_unref (svg);
return texture;
}
only_fg = TRUE ;
texture = NULL;
data = NULL;
data_bytes = NULL;
for (int plane = 0 ; plane < 3 ; plane++)
{
GdkTexture *loaded;
/* Here we render the svg with all colors solid, this should
* always make the alpha channel the same and it should match
* the final alpha channel for all possible renderings . We
* Just use it as - is for final alpha .
*
* For the 3 non - fg colors , we render once each with that
* color as red , and every other color as green . The resulting
* red will describe the amount of that color is in the
* opaque part of the color . We store these as the rgb
* channels , with the color of the fg being implicitly
* the " rest " , as all color fractions should add up to 1 .
*/
loaded = load_symbolic_svg (svg, width, height,
g_string,
plane == 0 ? r_string : g_string,
plane == 1 ? r_string : g_string,
plane == 2 ? r_string : g_string,
error);
if (loaded == NULL)
goto out;
if (plane == 0 )
{
data = g_new0 (guchar, 4 * width * height);
data_bytes = g_bytes_new_take (data, 4 * width * height);
extract_plane (loaded, data, width, height, 3 , 3 );
}
only_fg &= extract_plane (loaded, data, width, height, 0 , plane);
g_object_unref (loaded);
}
texture = gdk_memory_texture_new (width, height,
GDK_MEMORY_R8G8B8A8,
data_bytes,
4 * width);
out:
g_bytes_unref (data_bytes);
g_object_unref (svg);
if (out_only_fg)
*out_only_fg = only_fg;
return texture;
}
/* }}} */
/* {{{ Texture-from-stream API */
static GBytes *
input_stream_get_bytes (GInputStream *stream,
GError **error)
{
GOutputStream *out;
gssize res;
GBytes *bytes;
out = g_memory_output_stream_new_resizable ();
res = g_output_stream_splice (out, stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, error);
if (res == -1 )
{
g_object_unref (out);
return NULL;
}
bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (out));
g_object_unref (out);
return bytes;
}
GdkTexture *
gdk_texture_new_from_filename_at_scale (const char *filename,
int width,
int height,
GError **error)
{
GFile *file;
GInputStream *stream;
GBytes *bytes;
GtkSvg *svg;
GdkTexture *texture;
char *unsupported = NULL;
file = g_file_new_for_path (filename);
stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
g_object_unref (file);
if (!stream)
return NULL;
bytes = input_stream_get_bytes (stream, error);
g_object_unref (stream);
if (!bytes)
return NULL;
svg = svg_from_bytes (bytes, FALSE , &unsupported);
g_bytes_unref (bytes);
if (unsupported)
{
g_clear_object (&svg);
g_free (unsupported);
return NULL;
}
texture = svg_to_texture (svg, width, height, NULL, 0 );
g_object_unref (svg);
return texture;
}
/* }}} */
/* {{{ Paintable API */
static gboolean
is_symbolic (const char *path)
{
return g_str_has_suffix (path, "-symbolic.svg" ) ||
g_str_has_suffix (path, "-symbolic-ltr.svg" ) ||
g_str_has_suffix (path, "-symbolic-rtl.svg" );
}
static GdkPaintable *
gdk_paintable_new_from_bytes (GBytes *bytes,
const char *path,
gboolean is_symbolic)
{
GdkPaintable *paintable;
if (gdk_texture_can_load (bytes))
paintable = GDK_PAINTABLE (gdk_texture_new_from_bytes (bytes, NULL));
else
{
GtkSvg *svg;
char *unsupported = NULL;
svg = svg_from_bytes (bytes, is_symbolic, &unsupported);
if (unsupported)
{
gboolean only_fg;
if (is_symbolic)
paintable = GDK_PAINTABLE (gdk_texture_new_from_bytes_symbolic (bytes, 0 , 0 , &only_fg, NULL));
else
paintable = GDK_PAINTABLE (gdk_texture_new_from_bytes (bytes, NULL));
if (paintable)
{
GTK_DEBUG (ICONFALLBACK, "Falling back to a texture for %s: %s" , path, unsupported);
g_clear_object (&svg);
g_free (unsupported);
}
else
{
GTK_DEBUG (ICONFALLBACK, "Expect misrendering; %s uses unsupported features: %s" , path, unsupported);
g_free (unsupported);
paintable = GDK_PAINTABLE (svg);
}
}
else
paintable = GDK_PAINTABLE (svg);
}
return paintable;
}
GdkPaintable *
gdk_paintable_new_from_filename (const char *filename,
GError **error)
{
char *contents;
gsize length;
GBytes *bytes;
GdkPaintable *paintable;
if (!g_file_get_contents (filename, &contents, &length, error))
return NULL;
bytes = g_bytes_new_take (contents, length);
paintable = gdk_paintable_new_from_bytes (bytes, filename, is_symbolic (filename));
g_bytes_unref (bytes);
return paintable;
}
GdkPaintable *
gdk_paintable_new_from_resource (const char *path)
{
GBytes *bytes;
GdkPaintable *paintable;
bytes = g_resources_lookup_data (path, G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
if (!bytes)
return NULL;
paintable = gdk_paintable_new_from_bytes (bytes, path, is_symbolic (path));
g_bytes_unref (bytes);
return paintable;
}
GdkPaintable *
gdk_paintable_new_from_file (GFile *file,
GError **error)
{
GBytes *bytes;
GdkPaintable *paintable;
char *uri;
bytes = g_file_load_bytes (file, NULL, NULL, error);
if (!bytes)
return NULL;
uri = g_file_get_uri (file);
paintable = gdk_paintable_new_from_bytes (bytes, uri, is_symbolic (uri));
g_bytes_unref (bytes);
g_free (uri);
return paintable;
}
GdkPaintable *
gdk_paintable_new_from_stream (GInputStream *stream,
GCancellable *cancellable,
GError **error)
{
GBytes *bytes;
GdkPaintable *paintable;
bytes = input_stream_get_bytes (stream, error);
if (!bytes)
return NULL;
paintable = gdk_paintable_new_from_bytes (bytes, "?" , FALSE );
g_bytes_unref (bytes);
return paintable;
}
/* }}} */
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=98 H=94 G=95
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland