1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
|
/*:*
* ggaze — GdkPixbuf loader backend (fallback)
*
* Decodes any GdkPixbuf-supported format (PNG/JPEG/GIF/WebP/TIFF/ICO) via a
* GdkPixbufLoader, applies the embedded EXIF Orientation (decision #26) so the
* returned GdkTexture is upright, and hands the result to the caller. Acts as
* the fallback backend: can_load() returns TRUE for unknown formats too (let
* GdkPixbuf try) and FALSE only for formats owned by the JXL/AVIF/HEIF
* backends in M5.
*
* Copyright (c) 2026 ggaze contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*:*/
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <gdk/gdk.h>
#include <gio/gio.h>
#include "../detect.h"
#include "../loader.h"
static GdkTexture *_texture_from_pixbuf(GdkPixbuf *p_pix);
static gboolean
_pixbuf_can_load(const guint8 *p_head, gsize u_len) {
switch (detect_format(p_head, u_len)) {
case GGAZE_FMT_JXL:
case GGAZE_FMT_AVIF:
case GGAZE_FMT_HEIF:
return (FALSE); /* owned by specific backends (M5) */
case GGAZE_FMT_UNKNOWN:
case GGAZE_FMT_JPEG:
case GGAZE_FMT_PNG:
case GGAZE_FMT_GIF:
case GGAZE_FMT_WEBP:
case GGAZE_FMT_TIFF:
case GGAZE_FMT_ICO:
return (TRUE);
}
return (FALSE); /* unreachable; keeps -Wreturn-type calm */
}
static GdkTexture *
_pixbuf_load(GFile *p_file, GCancellable *p_cancel, GError **p_err) {
gchar *c_buf = NULL;
gsize u_len = 0;
if (!g_file_load_contents(p_file, p_cancel, &c_buf, &u_len, NULL, p_err)) {
return (NULL);
}
GdkPixbufLoader *p_loader = gdk_pixbuf_loader_new();
GError *p_sub = NULL;
if (!gdk_pixbuf_loader_write(p_loader, (const guchar *)c_buf, u_len,
&p_sub)) {
g_propagate_error(p_err, p_sub);
g_object_unref(p_loader);
g_free(c_buf);
return (NULL);
}
/* Close may fail on truncated data but a pixbuf may still be available. */
if (!gdk_pixbuf_loader_close(p_loader, &p_sub)) {
if (p_sub != NULL) {
g_error_free(p_sub);
}
}
GdkPixbuf *p_pix = gdk_pixbuf_loader_get_pixbuf(p_loader);
if (p_pix == NULL) {
g_set_error(p_err, G_IO_ERROR, G_IO_ERROR_FAILED,
"could not decode image (GdkPixbuf produced no pixbuf)");
g_object_unref(p_loader);
g_free(c_buf);
return (NULL);
}
/* Honor EXIF Orientation so the texture is upright (decision #26). */
GdkPixbuf *p_oriented = gdk_pixbuf_apply_embedded_orientation(p_pix);
GdkPixbuf *p_use =
(p_oriented != NULL) ? p_oriented : GDK_PIXBUF(g_object_ref(p_pix));
GdkTexture *p_tex = _texture_from_pixbuf(p_use);
g_object_unref(p_use);
g_object_unref(p_loader);
g_free(c_buf);
return (p_tex);
}
const GgazeLoaderBackend pixbuf_backend = {
.can_load = _pixbuf_can_load,
.load = _pixbuf_load,
};
/* Build a GdkTexture from a GdkPixbuf without the deprecated
* gdk_texture_new_for_pixbuf(). GdkPixbuf stores non-premultiplied R8G8B8A8
* when it has alpha; otherwise we add an alpha channel first. */
static GdkTexture *
_texture_from_pixbuf(GdkPixbuf *p_pix) {
g_return_val_if_fail(GDK_IS_PIXBUF(p_pix), NULL);
int i_w = gdk_pixbuf_get_width(p_pix);
int i_h = gdk_pixbuf_get_height(p_pix);
g_return_val_if_fail(i_w > 0 && i_h > 0, NULL);
GdkPixbuf *p_rgba = gdk_pixbuf_get_has_alpha(p_pix)
? GDK_PIXBUF(g_object_ref(p_pix))
: gdk_pixbuf_add_alpha(p_pix, FALSE, 0, 0, 0);
if (p_rgba == NULL) {
return (NULL);
}
int i_rowstride = gdk_pixbuf_get_rowstride(p_rgba);
guchar *p_pixels = gdk_pixbuf_get_pixels(p_rgba);
gsize u_len = (gsize)(i_h - 1) * (gsize)i_rowstride + (gsize)i_w * 4u;
GBytes *p_bytes = g_bytes_new_with_free_func(
p_pixels, u_len, (GDestroyNotify)g_object_unref, p_rgba);
GdkTexture *p_tex = gdk_memory_texture_new(i_w, i_h, GDK_MEMORY_R8G8B8A8,
p_bytes, (gsize)i_rowstride);
g_bytes_unref(p_bytes);
return (p_tex);
}
|