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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
|
/*:*
* ggaze — GdkPixbuf loader backend unit test
*
* Loads committed fixtures via loader_load() and asserts the resulting
* GdkTexture dimensions, including the rotated-EXIF case (decision #26): an
* 8x4 JPEG with Orientation=6 must load as 4x8 after
* gdk_pixbuf_apply_embedded_orientation. No display needed (texture creation
* from a pixbuf is headless). Fixture dir comes from $GGAZE_FIXTURES_DIR
* (set by meson). See ./sample-images for the optional realistic corpus.
*
* Copyright (c) 2026 ggaze contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*:*/
#include "loader/loader.h"
#include <gdk/gdk.h>
#include <gio/gio.h>
#include <glib.h>
#include <unistd.h>
static GdkTexture *
load_fixture(const gchar *c_name) {
const gchar *c_dir = g_getenv("GGAZE_FIXTURES_DIR");
g_assert_nonnull(c_dir);
gchar *c_path = g_build_filename(c_dir, c_name, NULL);
GFile *p_file = g_file_new_for_path(c_path);
GError *p_err = NULL;
GdkTexture *p_tex = loader_load(p_file, NULL, &p_err);
g_assert_no_error(p_err);
g_object_unref(p_file);
g_free(c_path);
return (p_tex);
}
static void
test_plain_jpeg(void) {
/* 6x3, Orientation = 1 -> 6x3 (no rotation applied). */
GdkTexture *p_tex = load_fixture("plain.jpg");
g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 6);
g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 3);
g_object_unref(p_tex);
}
static void
test_rotated_exif_jpeg(void) {
/* 8x4, Orientation = 6 (rotate 90 CW) -> upright 4x8 (decision #26). */
GdkTexture *p_tex = load_fixture("rot6.jpg");
g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 4);
g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 8);
g_object_unref(p_tex);
}
static void
test_png(void) {
/* 5x2 PNG, no orientation. */
GdkTexture *p_tex = load_fixture("small.png");
g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 5);
g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 2);
g_object_unref(p_tex);
}
static void
test_missing_file_errors(void) {
const gchar *c_dir = g_getenv("GGAZE_FIXTURES_DIR");
g_assert_nonnull(c_dir);
gchar *c_path = g_build_filename(c_dir, "does-not-exist.jpg", NULL);
GFile *p_file = g_file_new_for_path(c_path);
GError *p_err = NULL;
GdkTexture *p_tex = loader_load(p_file, NULL, &p_err);
g_assert_null(p_tex);
g_assert_nonnull(p_err);
g_error_free(p_err);
g_object_unref(p_file);
g_free(c_path);
}
/* Write raw bytes to a temp file and load them (magic-byte / corrupt cases). */
static GdkTexture *
load_bytes(const guint8 *p_buf, gsize u_len, GError **p_err) {
gchar *c_path = NULL;
GError *p_sub = NULL;
gint i_fd = g_file_open_tmp("ggaze-XXXXXX", &c_path, &p_sub);
g_assert_no_error(p_sub);
g_assert_cmpint(i_fd, >=, 0);
gsize u_off = 0;
while (u_off < u_len) {
gssize n = write(i_fd, p_buf + u_off, u_len - u_off);
g_assert_cmpint(n, >, 0);
u_off += (gsize)n;
}
close(i_fd);
GFile *p_file = g_file_new_for_path(c_path);
GdkTexture *p_tex = loader_load(p_file, NULL, p_err);
g_object_unref(p_file);
unlink(c_path);
g_free(c_path);
return (p_tex);
}
static void
assert_unsupported(const guint8 *p_buf, gsize u_len) {
GError *p_err = NULL;
GdkTexture *p_tex = load_bytes(p_buf, u_len, &p_err);
g_assert_null(p_tex);
g_assert_nonnull(p_err);
g_assert_cmpint(p_err->code, ==, G_IO_ERROR_NOT_SUPPORTED);
g_error_free(p_err);
}
static void
test_unsupported_jxl(void) {
const guint8 h[] = {0xFF, 0x0A, 0x10, 0x00}; /* JXL codestream magic */
assert_unsupported(h, G_N_ELEMENTS(h));
}
static void
test_unsupported_avif(void) {
const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'};
assert_unsupported(h, G_N_ELEMENTS(h));
}
static void
test_unsupported_heif(void) {
const guint8 h[] = {0, 0, 0, 0, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'};
assert_unsupported(h, G_N_ELEMENTS(h));
}
static void
test_corrupt_jpeg(void) {
/* Truncated JPEG: SOI + APP0 marker, no image data. GdkPixbuf fails. */
const guint8 h[] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 'J', 'F',
'I', 'F', 0, 1, 1, 0, 0, 0};
GError *p_err = NULL;
GdkTexture *p_tex = load_bytes(h, G_N_ELEMENTS(h), &p_err);
g_assert_null(p_tex);
g_assert_nonnull(p_err);
g_error_free(p_err);
}
static void
test_rgba_png(void) {
/* 5x2 RGBA PNG -> exercises the has-alpha branch of texture_from_pixbuf. */
GdkTexture *p_tex = load_fixture("rgba.png");
g_assert_cmpint(gdk_texture_get_width(p_tex), ==, 5);
g_assert_cmpint(gdk_texture_get_height(p_tex), ==, 2);
g_object_unref(p_tex);
}
int
main(int i_argc, char **c_argv) {
g_test_init(&i_argc, &c_argv, NULL);
g_test_add_func("/loader/pixbuf/plain_jpeg", test_plain_jpeg);
g_test_add_func("/loader/pixbuf/rotated_exif", test_rotated_exif_jpeg);
g_test_add_func("/loader/pixbuf/png", test_png);
g_test_add_func("/loader/pixbuf/missing_file", test_missing_file_errors);
g_test_add_func("/loader/pixbuf/unsupported_jxl", test_unsupported_jxl);
g_test_add_func("/loader/pixbuf/unsupported_avif", test_unsupported_avif);
g_test_add_func("/loader/pixbuf/unsupported_heif", test_unsupported_heif);
g_test_add_func("/loader/pixbuf/corrupt_jpeg", test_corrupt_jpeg);
g_test_add_func("/loader/pixbuf/rgba_png", test_rgba_png);
return (g_test_run());
}
|