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
test_scale(gconstpointer data)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 ("format not supported");
39       return;
40     }
41 
42   path = g_test_get_filename (G_TEST_DIST, filename, NULL);
43   ref = gdk_pixbuf_new_from_file (path, &error);
44   g_assert_no_error (error);
45 
46   width = gdk_pixbuf_get_width (ref);
47   height = gdk_pixbuf_get_height (ref);
48 
49   pixbuf = gdk_pixbuf_new_from_file_at_scale (path, 2 * width, 3 * height, FALSE, &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 (pixbuf);
56 
57   pixbuf = gdk_pixbuf_new_from_file_at_scale (path, 4 * width, 2 * height, TRUE, &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 (pixbuf);
64 
65   g_object_unref (ref);
66 }
67 
68 static void
test_scale_down(gconstpointer data)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 ("format not supported");
81       return;
82     }
83 
84   path = g_test_get_filename (G_TEST_DIST, filename, NULL);
85   ref = gdk_pixbuf_new_from_file (path, &error);
86 
87   if (skip_if_insufficient_memory (&error))
88     return;
89   g_assert_no_error (error);
90 
91   width = gdk_pixbuf_get_width (ref);
92   height = gdk_pixbuf_get_height (ref);
93 
94   pixbuf = gdk_pixbuf_scale_simple (ref, width / 10, height / 10, GDK_INTERP_BILINEAR);
95   g_assert (pixbuf != NULL);
96 
97   g_object_unref (ref);
98 }
99 
100 static void
test_add_alpha(gconstpointer data)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 ("format not supported");
112       return;
113     }
114 
115   path = g_test_get_filename (G_TEST_DIST, filename, NULL);
116   ref = gdk_pixbuf_new_from_file (path, &error);
117 
118   if (skip_if_insufficient_memory (&error))
119     return;
120   g_assert_no_error (error);
121 
122   pixbuf = gdk_pixbuf_add_alpha (ref, FALSE, 0, 0, 0);
123 
124   if (pixbuf == NULL)
125     {
126       g_test_skip ("Couldn't add alpha to the image - your system probably lacks sufficient memory.");
127       g_object_unref (ref);
128       return;
129     }
130 
131   g_object_unref (pixbuf);
132 
133   pixbuf = gdk_pixbuf_add_alpha (ref, TRUE, 0, 0, 255);
134   g_assert (pixbuf != NULL);
135   g_object_unref (pixbuf);
136 
137   g_object_unref (ref);
138 }
139 
140 static void
test_rotate(gconstpointer data)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 ("format not supported");
152       return;
153     }
154 
155   path = g_test_get_filename (G_TEST_DIST, filename, NULL);
156   ref = gdk_pixbuf_new_from_file (path, &error);
157 
158   if (skip_if_insufficient_memory (&error))
159     return;
160   g_assert_no_error (error);
161 
162   pixbuf = gdk_pixbuf_rotate_simple (ref, GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE);
163 
164   if (pixbuf == NULL)
165     g_test_skip ("Couldn't rotate the image - your system probably lacks sufficient memory.");
166   else
167     g_object_unref (pixbuf);
168 
169   g_object_unref (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
test_halve_checkerboard(gconstpointer data)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 (source, width / 2, height / 2, interp_type);
194   scaled_width = gdk_pixbuf_get_width (scaled);
195   scaled_height = gdk_pixbuf_get_height (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 (scaled);
201        y < (guint) scaled_height;
202        y++, row += gdk_pixbuf_get_rowstride (scaled))
203     {
204       for (x = 0, pixel = row;
205            x < (guint) scaled_width;
206            x++, pixel += gdk_pixbuf_get_n_channels (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
crop_n_compare(const GdkPixbuf * source,gint offset_x,gint offset_y,guint width,guint height,GdkInterpType interp_type)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 ((GdkPixbuf *)source, offset_x, offset_y, width, height);
244   g_assert_nonnull (cropped);
245 
246   scaled = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 0, 8, width, height);
247   g_assert_nonnull (scaled);
248   gdk_pixbuf_scale (source, scaled,
249                     0, 0,                             /* dest_[xy] */
250                     width, height,                    /* dest_width/height */
251                     -1.0 * offset_x, -1.0 * offset_y, /* offset_[xy] */
252                     1.0, 1.0,                         /* scale_[xy] */
253                     interp_type);
254 
255   scaled_width = gdk_pixbuf_get_width (scaled);
256   scaled_height = gdk_pixbuf_get_height (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 (cropped),
261               srow = gdk_pixbuf_get_pixels (scaled);
262        y < scaled_height;
263        y++, crow += gdk_pixbuf_get_rowstride (cropped),
264             srow += gdk_pixbuf_get_rowstride (scaled))
265     {
266       for (x = 0, cpixel = crow, spixel = srow;
267            x < scaled_width;
268            x++, cpixel += gdk_pixbuf_get_n_channels (cropped),
269                 spixel += gdk_pixbuf_get_n_channels (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
test_offset(gconstpointer data)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, 0,         0,          width / 2, height / 2, interp_type);
307   crop_n_compare (source, width / 2, 0,          width / 2, height / 2, interp_type);
308   crop_n_compare (source, 0,         height / 2, width / 2, height / 2, interp_type);
309   crop_n_compare (source, width / 2, height / 2, width / 2, height / 2, interp_type);
310   crop_n_compare (source, width / 4, height / 4, width / 2, 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
test_dest(gconstpointer data)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 (GDK_COLORSPACE_RGB, 0, 8, width, height);
340   g_assert_nonnull (copy);
341 
342   /* Copy the four quadrants with a no-op scale */
343   gdk_pixbuf_scale ((const GdkPixbuf *)source, copy,
344                     0, 0,                  /* dest_[xy] */
345                     width / 2, height / 2, /* dest_width/height */
346                     0.0, 0.0,              /* offset_[xy] */
347                     1.0, 1.0,              /* scale_[xy] */
348                     interp_type);
349   gdk_pixbuf_scale ((const GdkPixbuf *)source, copy,
350                     width / 2, 0,          /* dest_[xy] */
351                     width / 2, height / 2, /* dest_width/height */
352                     0.0, 0.0,              /* offset_[xy] */
353                     1.0, 1.0,              /* scale_[xy] */
354                     interp_type);
355   gdk_pixbuf_scale ((const GdkPixbuf *)source, copy,
356                     0, height / 2,         /* dest_[xy] */
357                     width / 2, height / 2, /* dest_width/height */
358                     0.0, 0.0,              /* offset_[xy] */
359                     1.0, 1.0,              /* scale_[xy] */
360                     interp_type);
361   gdk_pixbuf_scale ((const GdkPixbuf *)source, copy,
362                     width / 2, height / 2, /* dest_[xy] */
363                     width / 2, height / 2, /* dest_width/height */
364                     0.0, 0.0,              /* offset_[xy] */
365                     1.0, 1.0,              /* scale_[xy] */
366                     interp_type);
367 
368   /* Compare the original and the copy */
369   for (y = 0, srow = gdk_pixbuf_get_pixels (source),
370               crow = gdk_pixbuf_get_pixels (copy);
371        y < gdk_pixbuf_get_height (source);
372        y++, srow += gdk_pixbuf_get_rowstride (source),
373             crow += gdk_pixbuf_get_rowstride (copy))
374     {
375       for (x = 0, spixel = srow, cpixel = crow;
376            x < gdk_pixbuf_get_width (source);
377            x++, spixel += gdk_pixbuf_get_n_channels (source),
378                 cpixel += gdk_pixbuf_get_n_channels (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
main(int argc,char ** argv)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, &argv, NULL);
402 
403   g_test_add_data_func ("/pixbuf/scale/png", "test-images/randomly-modified/valid.1.png", test_scale);
404   g_test_add_data_func ("/pixbuf/scale/bmp", "test-images/randomly-modified/valid.1.bmp", test_scale);
405   g_test_add_data_func ("/pixbuf/scale/gif", "test-images/randomly-modified/valid.1.gif", test_scale);
406   g_test_add_data_func ("/pixbuf/scale/jpeg", "test-images/randomly-modified/valid.1.jpeg", test_scale);
407   g_test_add_data_func ("/pixbuf/scale/tga", "test-images/randomly-modified/valid.1.tga", test_scale);
408   g_test_add_data_func ("/pixbuf/scale/xpm", "test-images/randomly-modified/valid.1.xpm", test_scale);
409   g_test_add_data_func ("/pixbuf/scale/xbm", "test-images/randomly-modified/valid.1.xbm", test_scale);
410   if (g_test_slow ())
411     {
412       g_test_add_data_func ("/pixbuf/scale/png/large", "large.png", test_scale_down);
413       g_test_add_data_func ("/pixbuf/scale/jpeg/large", "large.jpg", test_scale_down);
414       g_test_add_data_func ("/pixbuf/add-alpha/large", "large.png", test_add_alpha);
415       g_test_add_data_func ("/pixbuf/rotate/large", "large.png", test_rotate);
416     }
417 
418   g_test_add_data_func ("/pixbuf/scale/halve-checkerboard/nearest", &nearest, test_halve_checkerboard);
419   g_test_add_data_func ("/pixbuf/scale/halve-checkerboard/tiles", &tiles, test_halve_checkerboard);
420   g_test_add_data_func ("/pixbuf/scale/halve-checkerboard/bilinear", &bilinear, test_halve_checkerboard);
421   g_test_add_data_func ("/pixbuf/scale/halve-checkerboard/hyper", &hyper, test_halve_checkerboard);
422 
423   g_test_add_data_func ("/pixbuf/scale/offset/nearest", &nearest, test_offset);
424   g_test_add_data_func ("/pixbuf/scale/offset/tiles", &tiles, test_offset);
425   g_test_add_data_func ("/pixbuf/scale/offset/bilinear", &bilinear, test_offset);
426   g_test_add_data_func ("/pixbuf/scale/offset/hyper", &hyper, test_offset);
427 
428   g_test_add_data_func ("/pixbuf/scale/dest/nearest", &nearest, test_dest);
429   g_test_add_data_func ("/pixbuf/scale/dest/tiles", &tiles, test_dest);
430   g_test_add_data_func ("/pixbuf/scale/dest/bilinear", &bilinear, test_dest);
431   /* Don't bother with hyper as it changes the edge pixels */
432 
433   return g_test_run ();
434 }
435