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 | */ |
35 | typedef 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. */ |
43 | static guint32 |
44 | get_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. */ |
61 | static gboolean |
62 | pixbuf_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 | |
105 | static void |
106 | callback_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. */ |
123 | static void |
124 | callback_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 */ |
131 | static void |
132 | callback_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. */ |
147 | static gsize |
148 | loader_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 | |
175 | static void |
176 | test_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. */ |
212 | static void |
213 | update_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 | |
245 | static void |
246 | callback_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 */ |
265 | static void |
266 | callback_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 */ |
274 | static void |
275 | callback_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 | |
293 | static void |
294 | test_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 | |
326 | int 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 | |