1/* -*- Mode: C; c-basic-offset: 2; -*- */
2/* GdkPixbuf library - test loaders
3 *
4 * Copyright (C) 2016 Red Hat, Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Author: Andrey Tsyvarev
20 * Bastien Nocera
21 */
22
23#define GLIB_DISABLE_DEPRECATION_WARNINGS
24#include <stdio.h>
25#include <gdk-pixbuf/gdk-pixbuf.h>
26
27/* maximum number of frames in animation(before repeating) */
28#define MAX_NUMBER_FRAMES 1000
29
30/* Information about currently loading frame of animation.
31 *
32 * pixbuf == NULL means that animation is fully loaded
33 * (no currently loading frames exist).
34 */
35typedef struct {
36 GdkPixbufAnimationIter* iter; /* iterator pointed to the frame */
37 GTimeVal time; /* time when this frame appears */
38 GdkPixbuf *pixbuf; /* current content of the frame */
39} FrameData;
40
41/* Auxiliary function, returns numeric representation of pixel.
42 * For RGB format it is rrggbb(in hex), for RGBA - rrggbbaa. */
43static guint32
44get_pixel (GdkPixbuf *pixbuf, int x, int y)
45{
46 guchar *colors;
47 guint32 pixel;
48
49 colors = ((guchar*)gdk_pixbuf_get_pixels(pixbuf)
50 + gdk_pixbuf_get_n_channels(pixbuf) * x
51 + gdk_pixbuf_get_rowstride(pixbuf) * y);
52 pixel = (colors[0] << 16) | (colors[1] << 8) | colors[2];
53
54 if (gdk_pixbuf_get_n_channels (pixbuf) == 4)
55 pixel = (pixel << 8) | colors[3];
56
57 return pixel;
58}
59/* Verify whether all pixels outside the updated area
60 * have the same values in pixbuf_old and pixbuf_new. */
61static gboolean
62pixbuf_not_changed_outside_area (GdkPixbuf *pixbuf_new,
63 GdkPixbuf *pixbuf_old,
64 int x,
65 int y,
66 int width,
67 int height)
68{
69 int pixbuf_width, pixbuf_height;
70 int x_curr, y_curr;
71
72 pixbuf_width = gdk_pixbuf_get_width (pixbuf: pixbuf_new);
73 pixbuf_height = gdk_pixbuf_get_height (pixbuf: pixbuf_new);
74
75 for (x_curr = 0; x_curr < pixbuf_width; x_curr++) {
76 for (y_curr = 0; y_curr < pixbuf_height; y_curr++) {
77 if ((x_curr >= x)
78 && (x_curr < x + width)
79 && (y_curr >= y)
80 && (y_curr < y + height)) {
81 continue; /* inside area - don't compare pixels. */
82 }
83 if (get_pixel (pixbuf: pixbuf_new, x: x_curr, y: y_curr) != get_pixel (pixbuf: pixbuf_old, x: x_curr, y: y_curr)) {
84 printf(format: "Pixel at (%d, %d) changed from %x to %x,\n",
85 x_curr, y_curr,
86 get_pixel (pixbuf: pixbuf_old, x: x_curr, y: y_curr),
87 get_pixel (pixbuf: pixbuf_new, x: x_curr, y: y_curr));
88 printf(format: "But it is outside of area with ");
89 printf(format: "x=%d, y=%d, width=%d, height=%d\n",
90 x, y, width, height);
91
92#if 0
93 gdk_pixbuf_save (pixbuf_old, "old.png", "png", NULL, NULL);
94 gdk_pixbuf_save (pixbuf_new, "new.png", "png", NULL, NULL);
95#endif
96
97 g_assert_not_reached ();
98 }
99 }
100 }
101
102 return TRUE;
103}
104
105static void
106callback_area_updated (GdkPixbufLoader *loader,
107 int x,
108 int y,
109 int width,
110 int height,
111 GdkPixbuf *pixbuf_old)
112{
113 GdkPixbuf *pixbuf_new;
114
115 pixbuf_new = gdk_pixbuf_loader_get_pixbuf (loader);
116
117 pixbuf_not_changed_outside_area (pixbuf_new, pixbuf_old, x, y, width, height);
118 /* update copy of pixbuf */
119 gdk_pixbuf_copy_area (src_pixbuf: pixbuf_new, src_x: x, src_y: y, width, height, dest_pixbuf: pixbuf_old, dest_x: x, dest_y: y);
120}
121
122/* free copy of pixbuf used in area-updated callback. */
123static void
124callback_closed (GdkPixbufLoader *loader,
125 GdkPixbuf *pixbuf_copy)
126{
127 g_object_unref (object: pixbuf_copy);
128}
129
130/* prepare copy of pixbuf and connect other callbacks */
131static void
132callback_area_prepared (GdkPixbufLoader *loader)
133{
134 GdkPixbuf *pixbuf_copy;
135
136 pixbuf_copy = gdk_pixbuf_copy (pixbuf: gdk_pixbuf_loader_get_pixbuf (loader));
137 /* connect callbacks for another signals for not use pointer to pointer in them. */
138 g_signal_connect (loader, "area-updated",
139 (GCallback) callback_area_updated, (gpointer) pixbuf_copy);
140 g_signal_connect (loader, "closed",
141 (GCallback) callback_closed, (gpointer) pixbuf_copy);
142}
143
144/* Read count_bytes from the channel and write them to the loader.
145 * Returns number of bytes written.
146 * count_bytes = G_MAXSIZE means read as many bytes as possible. */
147static gsize
148loader_write_from_channel (GdkPixbufLoader *loader,
149 GIOChannel *channel,
150 gsize count_bytes)
151{
152 guchar* buffer;
153 gsize bytes_read;
154 GIOStatus read_status;
155
156 if(count_bytes < G_MAXSIZE) {
157 /* read no more than 'count_bytes' bytes */
158 buffer = g_malloc(n_bytes: count_bytes);
159 read_status = g_io_channel_read_chars (channel, buf: (gchar*) buffer, count: count_bytes, bytes_read: &bytes_read, NULL);
160 } else {
161 /*read up to end */
162 read_status = g_io_channel_read_to_end (channel, str_return: (gchar**) &buffer, length: &bytes_read, NULL);
163 }
164
165 if ((read_status != G_IO_STATUS_NORMAL) && (read_status != G_IO_STATUS_EOF))
166 g_assert_not_reached ();
167
168 if (!gdk_pixbuf_loader_write(loader, buf: buffer, count: bytes_read, NULL))
169 g_assert_not_reached ();
170
171 g_free (mem: buffer);
172 return bytes_read;
173}
174
175static void
176test_area_updated_ico (gconstpointer data)
177{
178 const char *filename;
179 GIOChannel *channel;
180 GdkPixbufLoader *loader;
181
182 filename = g_test_get_filename (file_type: G_TEST_DIST, first_path: data, NULL);
183
184 /* create channel */
185 channel = g_io_channel_new_file(filename, mode: "r", NULL);
186 g_assert_nonnull (channel);
187 g_io_channel_set_encoding (channel, NULL, NULL);
188 /* create loader */
189 loader = gdk_pixbuf_loader_new ();
190 g_assert_nonnull (loader);
191
192 g_signal_connect(loader, "area-prepared",
193 (GCallback) callback_area_prepared, NULL);
194 /* callbacks for "area-updated" and "closed" signals will be connected
195 * in callback_area_prepared() */
196
197 /* read image byte by byte */
198 while (loader_write_from_channel(loader, channel, count_bytes: 1) == 1);
199 /* or read full image at once */
200#if 0
201 loader_write_from_channel(loader, channel, G_MAXSIZE);
202#endif
203
204 /* free resources */
205 g_io_channel_unref (channel);
206
207 gdk_pixbuf_loader_close (loader, NULL);
208 g_object_unref (object: loader);
209}
210
211/* Auxiliary function - look for frame that's currently loading. */
212static void
213update_currently_loaded_frame (FrameData* frame)
214{
215 int tmp_count;
216
217 if (gdk_pixbuf_animation_iter_on_currently_loading_frame(iter: frame->iter))
218 return; /* frame is currently being loaded */
219 /* clear old content of pixbuf */
220 if (frame->pixbuf)
221 g_object_unref (object: frame->pixbuf);
222 frame->pixbuf = NULL;
223
224 tmp_count = 0;
225 do {
226 int delay_time;
227
228 if (++tmp_count > MAX_NUMBER_FRAMES) {
229 /* protection against frames repeating */
230 return;
231 }
232
233 delay_time = gdk_pixbuf_animation_iter_get_delay_time (iter: frame->iter);
234 if (delay_time < 0) {
235 /* this is last frame in the animation */
236 return;
237 }
238 g_time_val_add (time_: &frame->time, microseconds: delay_time * 1000);
239 gdk_pixbuf_animation_iter_advance (iter: frame->iter, current_time: &frame->time);
240 } while (!gdk_pixbuf_animation_iter_on_currently_loading_frame (iter: frame->iter));
241 /* store current content of the frame */
242 frame->pixbuf = gdk_pixbuf_copy (pixbuf: gdk_pixbuf_animation_iter_get_pixbuf (iter: frame->iter));
243}
244
245static void
246callback_area_updated_anim (GdkPixbufLoader *loader,
247 int x,
248 int y,
249 int width,
250 int height,
251 FrameData *frame_old)
252{
253 GdkPixbuf *pixbuf_new;
254
255 /* "area-updated" signal was emitted after animation had fully loaded. */
256 g_assert_nonnull (frame_old->pixbuf);
257
258 pixbuf_new = gdk_pixbuf_animation_iter_get_pixbuf (iter: frame_old->iter);
259 pixbuf_not_changed_outside_area (pixbuf_new, pixbuf_old: frame_old->pixbuf, x, y, width, height);
260 gdk_pixbuf_copy_area (src_pixbuf: pixbuf_new, src_x: x, src_y: y, width, height, dest_pixbuf: frame_old->pixbuf, dest_x: x, dest_y: y);
261 update_currently_loaded_frame (frame: frame_old);
262}
263
264/* free resources used in callback_area_updated_anim */
265static void
266callback_closed_anim(GdkPixbufLoader *loader, FrameData *frame_copy)
267{
268 g_object_unref (object: frame_copy->iter);
269 if(frame_copy->pixbuf != NULL)
270 g_object_unref (object: frame_copy->pixbuf);
271 g_free (mem: frame_copy);
272}
273/* prepare frame information and register other callbacks */
274static void
275callback_area_prepared_anim (GdkPixbufLoader* loader)
276{
277 GdkPixbufAnimation *anim;
278 FrameData* frame_copy = g_new (FrameData, 1);
279
280 g_signal_connect (loader, "area-updated",
281 (GCallback) callback_area_updated_anim, (gpointer) frame_copy);
282 g_signal_connect (loader, "closed",
283 (GCallback) callback_closed_anim, (gpointer) frame_copy);
284
285 frame_copy->time.tv_sec = frame_copy->time.tv_usec = 0; /* some time */
286
287 anim = gdk_pixbuf_loader_get_animation (loader);
288 frame_copy->iter = gdk_pixbuf_animation_get_iter (animation: anim, start_time: &frame_copy->time);
289 frame_copy->pixbuf = gdk_pixbuf_copy (pixbuf: gdk_pixbuf_animation_iter_get_pixbuf (iter: frame_copy->iter));
290 update_currently_loaded_frame (frame: frame_copy);
291}
292
293static void
294test_area_updated_anim (gconstpointer data)
295{
296 const char *filename;
297 GIOChannel *channel;
298 GdkPixbufLoader *loader;
299
300 filename = g_test_get_filename (file_type: G_TEST_DIST, first_path: data, NULL);
301
302 channel = g_io_channel_new_file (filename, mode: "r", NULL);
303 g_assert_nonnull (channel);
304 g_io_channel_set_encoding(channel, NULL, NULL);
305 /*create loader */
306 loader = gdk_pixbuf_loader_new ();
307 g_assert_nonnull (loader);
308
309 g_signal_connect (loader, "area-prepared",
310 (GCallback) callback_area_prepared_anim, NULL);
311 /* other callbacks will be registered inside callback_area_prepared_anim */
312
313 /*read animation by portions of bytes */
314#if 0
315 while(loader_write_from_channel(loader, channel, 20) == 20);
316#endif
317 /* or read it at once */
318 loader_write_from_channel (loader, channel, G_MAXSIZE);
319
320 /* free resources */
321 g_io_channel_unref (channel);
322 gdk_pixbuf_loader_close (loader, NULL);
323 g_object_unref (object: loader);
324}
325
326int main(int argc, char **argv)
327{
328 g_test_init (argc: &argc, argv: &argv, NULL);
329
330 g_test_add_data_func (testpath: "/pixbuf/area-updated/ico", test_data: (gconstpointer) "test-images/reftests/squares.ico", test_func: test_area_updated_ico);
331 g_test_add_data_func (testpath: "/pixbuf/area-updated/gif", test_data: (gconstpointer) "aero.gif", test_func: test_area_updated_anim);
332 g_test_add_data_func (testpath: "/pixbuf/area-updated/gif2", test_data: (gconstpointer) "1_partyanimsm2.gif", test_func: test_area_updated_anim);
333 g_test_add_data_func (testpath: "/pixbuf/area-updated/gif3", test_data: (gconstpointer) "test-animation.gif", test_func: test_area_updated_anim);
334
335 return g_test_run ();
336}
337

source code of gtk/subprojects/gdk-pixbuf/tests/pixbuf-area-updated.c