1 | /* -*- Mode: C; c-basic-offset: 2; -*- */ |
2 | /* GdkPixbuf library - test loaders |
3 | * |
4 | * Copyright (C) 2013 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: Matthias Clasen |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #include "gdk-pixbuf/gdk-pixbuf.h" |
24 | #include "test-common.h" |
25 | |
26 | static void |
27 | test_scale (gconstpointer data) |
28 | { |
29 | const gchar *filename = data; |
30 | const gchar *path; |
31 | GError *error = NULL; |
32 | GdkPixbuf *ref; |
33 | GdkPixbuf *pixbuf; |
34 | gint width, height; |
35 | |
36 | if (!format_supported (filename)) |
37 | { |
38 | g_test_skip (msg: "format not supported" ); |
39 | return; |
40 | } |
41 | |
42 | path = g_test_get_filename (file_type: G_TEST_DIST, first_path: filename, NULL); |
43 | ref = gdk_pixbuf_new_from_file (filename: path, error: &error); |
44 | g_assert_no_error (error); |
45 | |
46 | width = gdk_pixbuf_get_width (pixbuf: ref); |
47 | height = gdk_pixbuf_get_height (pixbuf: ref); |
48 | |
49 | pixbuf = gdk_pixbuf_new_from_file_at_scale (filename: path, width: 2 * width, height: 3 * height, FALSE, error: &error); |
50 | g_assert_no_error (error); |
51 | |
52 | g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, 2 * width); |
53 | g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, 3 * height); |
54 | |
55 | g_object_unref (object: pixbuf); |
56 | |
57 | pixbuf = gdk_pixbuf_new_from_file_at_scale (filename: path, width: 4 * width, height: 2 * height, TRUE, error: &error); |
58 | g_assert_no_error (error); |
59 | |
60 | g_assert_cmpint (gdk_pixbuf_get_width (pixbuf), ==, 2 * width); |
61 | g_assert_cmpint (gdk_pixbuf_get_height (pixbuf), ==, 2 * height); |
62 | |
63 | g_object_unref (object: pixbuf); |
64 | |
65 | g_object_unref (object: ref); |
66 | } |
67 | |
68 | static void |
69 | test_scale_down (gconstpointer data) |
70 | { |
71 | const gchar *filename = data; |
72 | const gchar *path; |
73 | GError *error = NULL; |
74 | GdkPixbuf *ref; |
75 | GdkPixbuf *pixbuf; |
76 | gint width, height; |
77 | |
78 | if (!format_supported (filename)) |
79 | { |
80 | g_test_skip (msg: "format not supported" ); |
81 | return; |
82 | } |
83 | |
84 | path = g_test_get_filename (file_type: G_TEST_DIST, first_path: filename, NULL); |
85 | ref = gdk_pixbuf_new_from_file (filename: path, error: &error); |
86 | |
87 | if (skip_if_insufficient_memory (err: &error)) |
88 | return; |
89 | g_assert_no_error (error); |
90 | |
91 | width = gdk_pixbuf_get_width (pixbuf: ref); |
92 | height = gdk_pixbuf_get_height (pixbuf: ref); |
93 | |
94 | pixbuf = gdk_pixbuf_scale_simple (src: ref, dest_width: width / 10, dest_height: height / 10, interp_type: GDK_INTERP_BILINEAR); |
95 | g_assert (pixbuf != NULL); |
96 | |
97 | g_object_unref (object: ref); |
98 | } |
99 | |
100 | static void |
101 | test_add_alpha (gconstpointer data) |
102 | { |
103 | const gchar *filename = data; |
104 | const gchar *path; |
105 | GError *error = NULL; |
106 | GdkPixbuf *ref; |
107 | GdkPixbuf *pixbuf; |
108 | |
109 | if (!format_supported (filename)) |
110 | { |
111 | g_test_skip (msg: "format not supported" ); |
112 | return; |
113 | } |
114 | |
115 | path = g_test_get_filename (file_type: G_TEST_DIST, first_path: filename, NULL); |
116 | ref = gdk_pixbuf_new_from_file (filename: path, error: &error); |
117 | |
118 | if (skip_if_insufficient_memory (err: &error)) |
119 | return; |
120 | g_assert_no_error (error); |
121 | |
122 | pixbuf = gdk_pixbuf_add_alpha (pixbuf: ref, FALSE, r: 0, g: 0, b: 0); |
123 | |
124 | if (pixbuf == NULL) |
125 | { |
126 | g_test_skip (msg: "Couldn't add alpha to the image - your system probably lacks sufficient memory." ); |
127 | g_object_unref (object: ref); |
128 | return; |
129 | } |
130 | |
131 | g_object_unref (object: pixbuf); |
132 | |
133 | pixbuf = gdk_pixbuf_add_alpha (pixbuf: ref, TRUE, r: 0, g: 0, b: 255); |
134 | g_assert (pixbuf != NULL); |
135 | g_object_unref (object: pixbuf); |
136 | |
137 | g_object_unref (object: ref); |
138 | } |
139 | |
140 | static void |
141 | test_rotate (gconstpointer data) |
142 | { |
143 | const gchar *filename = data; |
144 | const gchar *path; |
145 | GError *error = NULL; |
146 | GdkPixbuf *ref; |
147 | GdkPixbuf *pixbuf; |
148 | |
149 | if (!format_supported (filename)) |
150 | { |
151 | g_test_skip (msg: "format not supported" ); |
152 | return; |
153 | } |
154 | |
155 | path = g_test_get_filename (file_type: G_TEST_DIST, first_path: filename, NULL); |
156 | ref = gdk_pixbuf_new_from_file (filename: path, error: &error); |
157 | |
158 | if (skip_if_insufficient_memory (err: &error)) |
159 | return; |
160 | g_assert_no_error (error); |
161 | |
162 | pixbuf = gdk_pixbuf_rotate_simple (src: ref, angle: GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); |
163 | |
164 | if (pixbuf == NULL) |
165 | g_test_skip (msg: "Couldn't rotate the image - your system probably lacks sufficient memory." ); |
166 | else |
167 | g_object_unref (object: pixbuf); |
168 | |
169 | g_object_unref (object: ref); |
170 | } |
171 | |
172 | /* Test images creation functions */ |
173 | |
174 | /* Create a 256x256 checkerboard of (1,1,1) and (255,255,255) pixels, |
175 | * scale it to exactly half size and check that all pixels are (128,128,128). */ |
176 | static void |
177 | test_halve_checkerboard (gconstpointer data) |
178 | { |
179 | GdkInterpType interp_type = *(GdkInterpType *) data; |
180 | const GdkPixbuf *source; /* Source image */ |
181 | gint width = 256, height = 256; /* Size of source image */ |
182 | gint scaled_width, scaled_height; /* Size of scaled image */ |
183 | GdkPixbuf *scaled; /* Scaled version */ |
184 | guchar *row; /* Pointer to start of row of pixels within the image */ |
185 | guchar *pixel; /* Pointer to current pixel data in row */ |
186 | guint x, y; |
187 | guchar expected; /* Expected color of all pixels */ |
188 | |
189 | expected = (interp_type == GDK_INTERP_NEAREST) ? 255 : 128; |
190 | |
191 | source = make_checkerboard (width, height); |
192 | |
193 | scaled = gdk_pixbuf_scale_simple (src: source, dest_width: width / 2, dest_height: height / 2, interp_type); |
194 | scaled_width = gdk_pixbuf_get_width (pixbuf: scaled); |
195 | scaled_height = gdk_pixbuf_get_height (pixbuf: scaled); |
196 | g_assert_cmpint (scaled_width, >, 0); |
197 | g_assert_cmpint (scaled_height, >, 0); |
198 | |
199 | /* Check that the result is all gray (or all white in the case of NEAREST) */ |
200 | for (y = 0, row = gdk_pixbuf_get_pixels (pixbuf: scaled); |
201 | y < (guint) scaled_height; |
202 | y++, row += gdk_pixbuf_get_rowstride (pixbuf: scaled)) |
203 | { |
204 | for (x = 0, pixel = row; |
205 | x < (guint) scaled_width; |
206 | x++, pixel += gdk_pixbuf_get_n_channels (pixbuf: scaled)) |
207 | { |
208 | if (!(pixel[0] == expected && pixel[1] == expected && pixel[2] == expected)) |
209 | { |
210 | /* Expected failure: HYPER has a different opinion about the color |
211 | * of the corner pixels: (126,126,126) and (130,130,130) */ |
212 | if (interp_type == GDK_INTERP_HYPER && |
213 | (x == 0 || x == scaled_width - 1) && |
214 | (y == 0 || y == scaled_height - 1)) |
215 | { |
216 | continue; |
217 | } |
218 | g_test_fail (); |
219 | } |
220 | } |
221 | } |
222 | |
223 | g_object_unref (G_OBJECT (scaled)); |
224 | g_object_unref (G_OBJECT (source)); |
225 | } |
226 | |
227 | /* Crop a region out of a source image using subpixbuf() and using the scaler, |
228 | * and check that the results are the same */ |
229 | static void |
230 | crop_n_compare (const GdkPixbuf *source, |
231 | gint offset_x, |
232 | gint offset_y, |
233 | guint width, |
234 | guint height, |
235 | GdkInterpType interp_type) |
236 | { |
237 | GdkPixbuf *cropped, *scaled; |
238 | guchar *crow, *srow; /* Pointer to current row in image data */ |
239 | guchar *cpixel, *spixel; /* Pointer to current pixel in row */ |
240 | guint x, y; |
241 | gint scaled_width, scaled_height; /* Size of scaled image */ |
242 | |
243 | cropped = gdk_pixbuf_new_subpixbuf (src_pixbuf: (GdkPixbuf *)source, src_x: offset_x, src_y: offset_y, width, height); |
244 | g_assert_nonnull (cropped); |
245 | |
246 | scaled = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: 0, bits_per_sample: 8, width, height); |
247 | g_assert_nonnull (scaled); |
248 | gdk_pixbuf_scale (src: source, dest: scaled, |
249 | dest_x: 0, dest_y: 0, /* dest_[xy] */ |
250 | dest_width: width, dest_height: height, /* dest_width/height */ |
251 | offset_x: -1.0 * offset_x, offset_y: -1.0 * offset_y, /* offset_[xy] */ |
252 | scale_x: 1.0, scale_y: 1.0, /* scale_[xy] */ |
253 | interp_type); |
254 | |
255 | scaled_width = gdk_pixbuf_get_width (pixbuf: scaled); |
256 | scaled_height = gdk_pixbuf_get_height (pixbuf: scaled); |
257 | g_assert_cmpint (scaled_width, >, 0); |
258 | g_assert_cmpint (scaled_height, >, 0); |
259 | |
260 | for (y = 0, crow = gdk_pixbuf_get_pixels (pixbuf: cropped), |
261 | srow = gdk_pixbuf_get_pixels (pixbuf: scaled); |
262 | y < scaled_height; |
263 | y++, crow += gdk_pixbuf_get_rowstride (pixbuf: cropped), |
264 | srow += gdk_pixbuf_get_rowstride (pixbuf: scaled)) |
265 | { |
266 | for (x = 0, cpixel = crow, spixel = srow; |
267 | x < scaled_width; |
268 | x++, cpixel += gdk_pixbuf_get_n_channels (pixbuf: cropped), |
269 | spixel += gdk_pixbuf_get_n_channels (pixbuf: scaled)) |
270 | { |
271 | if (!(spixel[0] == cpixel[0] && |
272 | spixel[1] == cpixel[1] && |
273 | spixel[2] == cpixel[2])) |
274 | { |
275 | /* Expected failure: HYPER has a different opinion about the |
276 | * colors of the edge pixels */ |
277 | if (interp_type == GDK_INTERP_HYPER && |
278 | ((x == 0 || x == scaled_width - 1) || |
279 | (y == 0 || y == scaled_height - 1))) |
280 | { |
281 | continue; |
282 | } |
283 | g_test_fail(); |
284 | } |
285 | } |
286 | } |
287 | |
288 | g_object_unref (G_OBJECT (cropped)); |
289 | g_object_unref (G_OBJECT (scaled)); |
290 | } |
291 | |
292 | /* Check that offsets work. |
293 | * We should be able to copy a region of an image using the scaler using |
294 | * negative offsets. */ |
295 | static void |
296 | test_offset (gconstpointer data) |
297 | { |
298 | GdkInterpType interp_type = *(GdkInterpType *) data; |
299 | gint width = 256, height = 256; |
300 | const GdkPixbuf *source; /* Source image */ |
301 | |
302 | source = make_rg (width, height); |
303 | |
304 | /* Check that the scaler correctly crops out an image half the size of the |
305 | * original from each corner and from the middle */ |
306 | crop_n_compare (source, offset_x: 0, offset_y: 0, width: width / 2, height: height / 2, interp_type); |
307 | crop_n_compare (source, offset_x: width / 2, offset_y: 0, width: width / 2, height: height / 2, interp_type); |
308 | crop_n_compare (source, offset_x: 0, offset_y: height / 2, width: width / 2, height: height / 2, interp_type); |
309 | crop_n_compare (source, offset_x: width / 2, offset_y: height / 2, width: width / 2, height: height / 2, interp_type); |
310 | crop_n_compare (source, offset_x: width / 4, offset_y: height / 4, width: width / 2, height: height / 2, interp_type); |
311 | |
312 | g_object_unref (G_OBJECT (source)); |
313 | } |
314 | |
315 | /* Test the dest_x and dest_y fields by making a copy of an image by |
316 | * scaling 1:1 and using dest to copy the four quadrants separately. |
317 | * |
318 | * When scaled, images are: |
319 | * 1) scaled by the scale factors with respect to the top-left corner |
320 | * 2) translated by the offsets (negative to shift the image left/up in its |
321 | * frame) |
322 | * 3) a region of size dest_width x dest-height starting at (dest_x,dest_y) |
323 | * in the scaled-and-offset image is copied to (dest_x,dest_y) in the |
324 | * destination image. See the illustration at |
325 | * https://developer.gnome.org/gdk-pixbuf/2.22/gdk-pixbuf-scaling.html#gdk-pixbuf-composite */ |
326 | static void |
327 | test_dest (gconstpointer data) |
328 | { |
329 | GdkInterpType interp_type = *(GdkInterpType *) data; |
330 | gint width = 256, height = 256; |
331 | const GdkPixbuf *source; /* Source image */ |
332 | GdkPixbuf *copy; /* Destination image */ |
333 | gint x, y; |
334 | guchar *srow, *crow; /* Pointer to current row in source and copy data */ |
335 | guchar *spixel, *cpixel; /* Pointer to current pixel in row */ |
336 | |
337 | source = make_rg (width, height); |
338 | |
339 | copy = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: 0, bits_per_sample: 8, width, height); |
340 | g_assert_nonnull (copy); |
341 | |
342 | /* Copy the four quadrants with a no-op scale */ |
343 | gdk_pixbuf_scale (src: (const GdkPixbuf *)source, dest: copy, |
344 | dest_x: 0, dest_y: 0, /* dest_[xy] */ |
345 | dest_width: width / 2, dest_height: height / 2, /* dest_width/height */ |
346 | offset_x: 0.0, offset_y: 0.0, /* offset_[xy] */ |
347 | scale_x: 1.0, scale_y: 1.0, /* scale_[xy] */ |
348 | interp_type); |
349 | gdk_pixbuf_scale (src: (const GdkPixbuf *)source, dest: copy, |
350 | dest_x: width / 2, dest_y: 0, /* dest_[xy] */ |
351 | dest_width: width / 2, dest_height: height / 2, /* dest_width/height */ |
352 | offset_x: 0.0, offset_y: 0.0, /* offset_[xy] */ |
353 | scale_x: 1.0, scale_y: 1.0, /* scale_[xy] */ |
354 | interp_type); |
355 | gdk_pixbuf_scale (src: (const GdkPixbuf *)source, dest: copy, |
356 | dest_x: 0, dest_y: height / 2, /* dest_[xy] */ |
357 | dest_width: width / 2, dest_height: height / 2, /* dest_width/height */ |
358 | offset_x: 0.0, offset_y: 0.0, /* offset_[xy] */ |
359 | scale_x: 1.0, scale_y: 1.0, /* scale_[xy] */ |
360 | interp_type); |
361 | gdk_pixbuf_scale (src: (const GdkPixbuf *)source, dest: copy, |
362 | dest_x: width / 2, dest_y: height / 2, /* dest_[xy] */ |
363 | dest_width: width / 2, dest_height: height / 2, /* dest_width/height */ |
364 | offset_x: 0.0, offset_y: 0.0, /* offset_[xy] */ |
365 | scale_x: 1.0, scale_y: 1.0, /* scale_[xy] */ |
366 | interp_type); |
367 | |
368 | /* Compare the original and the copy */ |
369 | for (y = 0, srow = gdk_pixbuf_get_pixels (pixbuf: source), |
370 | crow = gdk_pixbuf_get_pixels (pixbuf: copy); |
371 | y < gdk_pixbuf_get_height (pixbuf: source); |
372 | y++, srow += gdk_pixbuf_get_rowstride (pixbuf: source), |
373 | crow += gdk_pixbuf_get_rowstride (pixbuf: copy)) |
374 | { |
375 | for (x = 0, spixel = srow, cpixel = crow; |
376 | x < gdk_pixbuf_get_width (pixbuf: source); |
377 | x++, spixel += gdk_pixbuf_get_n_channels (pixbuf: source), |
378 | cpixel += gdk_pixbuf_get_n_channels (pixbuf: copy)) |
379 | { |
380 | if (!(spixel[0] == cpixel[0] && |
381 | spixel[1] == cpixel[1] && |
382 | spixel[2] == cpixel[2])) |
383 | { |
384 | g_test_fail(); |
385 | } |
386 | } |
387 | } |
388 | |
389 | g_object_unref (G_OBJECT (source)); |
390 | g_object_unref (G_OBJECT (copy)); |
391 | } |
392 | |
393 | int |
394 | main (int argc, char **argv) |
395 | { |
396 | GdkInterpType nearest = GDK_INTERP_NEAREST; |
397 | GdkInterpType tiles = GDK_INTERP_TILES; |
398 | GdkInterpType bilinear = GDK_INTERP_BILINEAR; |
399 | GdkInterpType hyper = GDK_INTERP_HYPER; |
400 | |
401 | g_test_init (argc: &argc, argv: &argv, NULL); |
402 | |
403 | g_test_add_data_func (testpath: "/pixbuf/scale/png" , test_data: "test-images/randomly-modified/valid.1.png" , test_func: test_scale); |
404 | g_test_add_data_func (testpath: "/pixbuf/scale/bmp" , test_data: "test-images/randomly-modified/valid.1.bmp" , test_func: test_scale); |
405 | g_test_add_data_func (testpath: "/pixbuf/scale/gif" , test_data: "test-images/randomly-modified/valid.1.gif" , test_func: test_scale); |
406 | g_test_add_data_func (testpath: "/pixbuf/scale/jpeg" , test_data: "test-images/randomly-modified/valid.1.jpeg" , test_func: test_scale); |
407 | g_test_add_data_func (testpath: "/pixbuf/scale/tga" , test_data: "test-images/randomly-modified/valid.1.tga" , test_func: test_scale); |
408 | g_test_add_data_func (testpath: "/pixbuf/scale/xpm" , test_data: "test-images/randomly-modified/valid.1.xpm" , test_func: test_scale); |
409 | g_test_add_data_func (testpath: "/pixbuf/scale/xbm" , test_data: "test-images/randomly-modified/valid.1.xbm" , test_func: test_scale); |
410 | if (g_test_slow ()) |
411 | { |
412 | g_test_add_data_func (testpath: "/pixbuf/scale/png/large" , test_data: "large.png" , test_func: test_scale_down); |
413 | g_test_add_data_func (testpath: "/pixbuf/scale/jpeg/large" , test_data: "large.jpg" , test_func: test_scale_down); |
414 | g_test_add_data_func (testpath: "/pixbuf/add-alpha/large" , test_data: "large.png" , test_func: test_add_alpha); |
415 | g_test_add_data_func (testpath: "/pixbuf/rotate/large" , test_data: "large.png" , test_func: test_rotate); |
416 | } |
417 | |
418 | g_test_add_data_func (testpath: "/pixbuf/scale/halve-checkerboard/nearest" , test_data: &nearest, test_func: test_halve_checkerboard); |
419 | g_test_add_data_func (testpath: "/pixbuf/scale/halve-checkerboard/tiles" , test_data: &tiles, test_func: test_halve_checkerboard); |
420 | g_test_add_data_func (testpath: "/pixbuf/scale/halve-checkerboard/bilinear" , test_data: &bilinear, test_func: test_halve_checkerboard); |
421 | g_test_add_data_func (testpath: "/pixbuf/scale/halve-checkerboard/hyper" , test_data: &hyper, test_func: test_halve_checkerboard); |
422 | |
423 | g_test_add_data_func (testpath: "/pixbuf/scale/offset/nearest" , test_data: &nearest, test_func: test_offset); |
424 | g_test_add_data_func (testpath: "/pixbuf/scale/offset/tiles" , test_data: &tiles, test_func: test_offset); |
425 | g_test_add_data_func (testpath: "/pixbuf/scale/offset/bilinear" , test_data: &bilinear, test_func: test_offset); |
426 | g_test_add_data_func (testpath: "/pixbuf/scale/offset/hyper" , test_data: &hyper, test_func: test_offset); |
427 | |
428 | g_test_add_data_func (testpath: "/pixbuf/scale/dest/nearest" , test_data: &nearest, test_func: test_dest); |
429 | g_test_add_data_func (testpath: "/pixbuf/scale/dest/tiles" , test_data: &tiles, test_func: test_dest); |
430 | g_test_add_data_func (testpath: "/pixbuf/scale/dest/bilinear" , test_data: &bilinear, test_func: test_dest); |
431 | /* Don't bother with hyper as it changes the edge pixels */ |
432 | |
433 | return g_test_run (); |
434 | } |
435 | |