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
26static void
27test_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
68static void
69test_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
100static void
101test_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
140static void
141test_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). */
176static void
177test_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 */
229static void
230crop_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. */
295static void
296test_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 */
326static void
327test_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
393int
394main (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

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