1 | /* GdkPixbuf library - Scaling and compositing functions |
2 | * |
3 | * Copyright (C) 1999 The Free Software Foundation |
4 | * |
5 | * Author: Owen Taylor <otaylor@redhat.com> |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | #include <math.h> |
23 | #include <string.h> |
24 | #include "gdk-pixbuf-transform.h" |
25 | #include "gdk-pixbuf-private.h" |
26 | #include "pixops/pixops.h" |
27 | |
28 | /** |
29 | * gdk_pixbuf_scale: |
30 | * @src: a #GdkPixbuf |
31 | * @dest: the #GdkPixbuf into which to render the results |
32 | * @dest_x: the left coordinate for region to render |
33 | * @dest_y: the top coordinate for region to render |
34 | * @dest_width: the width of the region to render |
35 | * @dest_height: the height of the region to render |
36 | * @offset_x: the offset in the X direction (currently rounded to an integer) |
37 | * @offset_y: the offset in the Y direction (currently rounded to an integer) |
38 | * @scale_x: the scale factor in the X direction |
39 | * @scale_y: the scale factor in the Y direction |
40 | * @interp_type: the interpolation type for the transformation. |
41 | * |
42 | * Creates a transformation of the source image @src by scaling by |
43 | * @scale_x and @scale_y then translating by @offset_x and @offset_y, |
44 | * then renders the rectangle (@dest_x, @dest_y, @dest_width, |
45 | * @dest_height) of the resulting image onto the destination image |
46 | * replacing the previous contents. |
47 | * |
48 | * Try to use gdk_pixbuf_scale_simple() first; this function is |
49 | * the industrial-strength power tool you can fall back to, if |
50 | * gdk_pixbuf_scale_simple() isn't powerful enough. |
51 | * |
52 | * If the source rectangle overlaps the destination rectangle on the |
53 | * same pixbuf, it will be overwritten during the scaling which |
54 | * results in rendering artifacts. |
55 | **/ |
56 | void |
57 | gdk_pixbuf_scale (const GdkPixbuf *src, |
58 | GdkPixbuf *dest, |
59 | int dest_x, |
60 | int dest_y, |
61 | int dest_width, |
62 | int dest_height, |
63 | double offset_x, |
64 | double offset_y, |
65 | double scale_x, |
66 | double scale_y, |
67 | GdkInterpType interp_type) |
68 | { |
69 | const guint8 *src_pixels; |
70 | guint8 *dest_pixels; |
71 | |
72 | g_return_if_fail (GDK_IS_PIXBUF (src)); |
73 | g_return_if_fail (GDK_IS_PIXBUF (dest)); |
74 | g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width); |
75 | g_return_if_fail (dest_y >= 0 && dest_y + dest_height <= dest->height); |
76 | |
77 | offset_x = floor (x: offset_x + 0.5); |
78 | offset_y = floor (x: offset_y + 0.5); |
79 | |
80 | /* Force an implicit copy */ |
81 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
82 | src_pixels = gdk_pixbuf_read_pixels (pixbuf: src); |
83 | |
84 | _pixops_scale (dest_buf: dest_pixels, dest_width: dest->width, dest_height: dest->height, dest_rowstride: dest->rowstride, |
85 | dest_channels: dest->n_channels, dest_has_alpha: dest->has_alpha, src_buf: src_pixels, src_width: src->width, |
86 | src_height: src->height, src_rowstride: src->rowstride, src_channels: src->n_channels, src_has_alpha: src->has_alpha, |
87 | dest_x, dest_y, dest_region_width: dest_width, dest_region_height: dest_height, offset_x, offset_y, |
88 | scale_x, scale_y, interp_type: (PixopsInterpType)interp_type); |
89 | } |
90 | |
91 | /** |
92 | * gdk_pixbuf_composite: |
93 | * @src: a #GdkPixbuf |
94 | * @dest: the #GdkPixbuf into which to render the results |
95 | * @dest_x: the left coordinate for region to render |
96 | * @dest_y: the top coordinate for region to render |
97 | * @dest_width: the width of the region to render |
98 | * @dest_height: the height of the region to render |
99 | * @offset_x: the offset in the X direction (currently rounded to an integer) |
100 | * @offset_y: the offset in the Y direction (currently rounded to an integer) |
101 | * @scale_x: the scale factor in the X direction |
102 | * @scale_y: the scale factor in the Y direction |
103 | * @interp_type: the interpolation type for the transformation. |
104 | * @overall_alpha: overall alpha for source image (0..255) |
105 | * |
106 | * Creates a transformation of the source image @src by scaling by |
107 | * @scale_x and @scale_y then translating by @offset_x and @offset_y. |
108 | * |
109 | * This gives an image in the coordinates of the destination pixbuf. |
110 | * The rectangle (@dest_x, @dest_y, @dest_width, @dest_height) |
111 | * is then alpha blended onto the corresponding rectangle of the |
112 | * original destination image. |
113 | * |
114 | * When the destination rectangle contains parts not in the source |
115 | * image, the data at the edges of the source image is replicated |
116 | * to infinity. |
117 | * |
118 | * ![](composite.png) |
119 | */ |
120 | void |
121 | gdk_pixbuf_composite (const GdkPixbuf *src, |
122 | GdkPixbuf *dest, |
123 | int dest_x, |
124 | int dest_y, |
125 | int dest_width, |
126 | int dest_height, |
127 | double offset_x, |
128 | double offset_y, |
129 | double scale_x, |
130 | double scale_y, |
131 | GdkInterpType interp_type, |
132 | int overall_alpha) |
133 | { |
134 | const guint8 *src_pixels; |
135 | guint8 *dest_pixels; |
136 | |
137 | g_return_if_fail (GDK_IS_PIXBUF (src)); |
138 | g_return_if_fail (GDK_IS_PIXBUF (dest)); |
139 | g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width); |
140 | g_return_if_fail (dest_y >= 0 && dest_y + dest_height <= dest->height); |
141 | g_return_if_fail (overall_alpha >= 0 && overall_alpha <= 255); |
142 | |
143 | offset_x = floor (x: offset_x + 0.5); |
144 | offset_y = floor (x: offset_y + 0.5); |
145 | |
146 | /* Force an implicit copy */ |
147 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
148 | src_pixels = gdk_pixbuf_read_pixels (pixbuf: src); |
149 | |
150 | _pixops_composite (dest_buf: dest_pixels, dest_width: dest->width, dest_height: dest->height, dest_rowstride: dest->rowstride, |
151 | dest_channels: dest->n_channels, dest_has_alpha: dest->has_alpha, src_buf: src_pixels, |
152 | src_width: src->width, src_height: src->height, src_rowstride: src->rowstride, src_channels: src->n_channels, |
153 | src_has_alpha: src->has_alpha, dest_x, dest_y, dest_region_width: dest_width, dest_region_height: dest_height, |
154 | offset_x, offset_y, scale_x, scale_y, |
155 | interp_type: (PixopsInterpType)interp_type, overall_alpha); |
156 | } |
157 | |
158 | /** |
159 | * gdk_pixbuf_composite_color: |
160 | * @src: a #GdkPixbuf |
161 | * @dest: the #GdkPixbuf into which to render the results |
162 | * @dest_x: the left coordinate for region to render |
163 | * @dest_y: the top coordinate for region to render |
164 | * @dest_width: the width of the region to render |
165 | * @dest_height: the height of the region to render |
166 | * @offset_x: the offset in the X direction (currently rounded to an integer) |
167 | * @offset_y: the offset in the Y direction (currently rounded to an integer) |
168 | * @scale_x: the scale factor in the X direction |
169 | * @scale_y: the scale factor in the Y direction |
170 | * @interp_type: the interpolation type for the transformation. |
171 | * @overall_alpha: overall alpha for source image (0..255) |
172 | * @check_x: the X offset for the checkboard (origin of checkboard is at -@check_x, -@check_y) |
173 | * @check_y: the Y offset for the checkboard |
174 | * @check_size: the size of checks in the checkboard (must be a power of two) |
175 | * @color1: the color of check at upper left |
176 | * @color2: the color of the other check |
177 | * |
178 | * Creates a transformation of the source image @src by scaling by |
179 | * @scale_x and @scale_y then translating by @offset_x and @offset_y, |
180 | * then alpha blends the rectangle (@dest_x ,@dest_y, @dest_width, |
181 | * @dest_height) of the resulting image with a checkboard of the |
182 | * colors @color1 and @color2 and renders it onto the destination |
183 | * image. |
184 | * |
185 | * If the source image has no alpha channel, and @overall_alpha is 255, a fast |
186 | * path is used which omits the alpha blending and just performs the scaling. |
187 | * |
188 | * See gdk_pixbuf_composite_color_simple() for a simpler variant of this |
189 | * function suitable for many tasks. |
190 | **/ |
191 | void |
192 | gdk_pixbuf_composite_color (const GdkPixbuf *src, |
193 | GdkPixbuf *dest, |
194 | int dest_x, |
195 | int dest_y, |
196 | int dest_width, |
197 | int dest_height, |
198 | double offset_x, |
199 | double offset_y, |
200 | double scale_x, |
201 | double scale_y, |
202 | GdkInterpType interp_type, |
203 | int overall_alpha, |
204 | int check_x, |
205 | int check_y, |
206 | int check_size, |
207 | guint32 color1, |
208 | guint32 color2) |
209 | { |
210 | const guint8 *src_pixels; |
211 | guint8 *dest_pixels; |
212 | |
213 | g_return_if_fail (GDK_IS_PIXBUF (src)); |
214 | g_return_if_fail (GDK_IS_PIXBUF (dest)); |
215 | g_return_if_fail (dest_x >= 0 && dest_x + dest_width <= dest->width); |
216 | g_return_if_fail (dest_y >= 0 && dest_y + dest_height <= dest->height); |
217 | g_return_if_fail (overall_alpha >= 0 && overall_alpha <= 255); |
218 | |
219 | offset_x = floor (x: offset_x + 0.5); |
220 | offset_y = floor (x: offset_y + 0.5); |
221 | |
222 | /* Force an implicit copy */ |
223 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
224 | src_pixels = gdk_pixbuf_read_pixels (pixbuf: src); |
225 | |
226 | _pixops_composite_color (dest_buf: dest_pixels, dest_width, dest_height, |
227 | dest_rowstride: dest->rowstride, dest_channels: dest->n_channels, dest_has_alpha: dest->has_alpha, |
228 | src_buf: src_pixels, src_width: src->width, src_height: src->height, |
229 | src_rowstride: src->rowstride, src_channels: src->n_channels, src_has_alpha: src->has_alpha, |
230 | dest_x, dest_y, dest_region_width: dest_width, dest_region_height: dest_height, offset_x, |
231 | offset_y, scale_x, scale_y, |
232 | interp_type: (PixopsInterpType)interp_type, overall_alpha, |
233 | check_x, check_y, check_size, color1, color2); |
234 | } |
235 | |
236 | /** |
237 | * gdk_pixbuf_scale_simple: |
238 | * @src: a #GdkPixbuf |
239 | * @dest_width: the width of destination image |
240 | * @dest_height: the height of destination image |
241 | * @interp_type: the interpolation type for the transformation. |
242 | * |
243 | * Create a new pixbuf containing a copy of `src` scaled to |
244 | * `dest_width` x `dest_height`. |
245 | * |
246 | * This function leaves `src` unaffected. |
247 | * |
248 | * The `interp_type` should be `GDK_INTERP_NEAREST` if you want maximum |
249 | * speed (but when scaling down `GDK_INTERP_NEAREST` is usually unusably |
250 | * ugly). The default `interp_type` should be `GDK_INTERP_BILINEAR` which |
251 | * offers reasonable quality and speed. |
252 | * |
253 | * You can scale a sub-portion of `src` by creating a sub-pixbuf |
254 | * pointing into `src`; see [method@GdkPixbuf.Pixbuf.new_subpixbuf]. |
255 | * |
256 | * If `dest_width` and `dest_height` are equal to the width and height of |
257 | * `src`, this function will return an unscaled copy of `src`. |
258 | * |
259 | * For more complicated scaling/alpha blending see [method@GdkPixbuf.Pixbuf.scale] |
260 | * and [method@GdkPixbuf.Pixbuf.composite]. |
261 | * |
262 | * Return value: (nullable) (transfer full): the new pixbuf |
263 | **/ |
264 | GdkPixbuf * |
265 | gdk_pixbuf_scale_simple (const GdkPixbuf *src, |
266 | int dest_width, |
267 | int dest_height, |
268 | GdkInterpType interp_type) |
269 | { |
270 | GdkPixbuf *dest; |
271 | |
272 | g_return_val_if_fail (GDK_IS_PIXBUF (src), NULL); |
273 | g_return_val_if_fail (dest_width > 0, NULL); |
274 | g_return_val_if_fail (dest_height > 0, NULL); |
275 | |
276 | /* Fast path. */ |
277 | if (dest_width == src->width && dest_height == src->height) |
278 | return gdk_pixbuf_copy (pixbuf: src); |
279 | |
280 | dest = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: src->has_alpha, bits_per_sample: 8, width: dest_width, height: dest_height); |
281 | if (!dest) |
282 | return NULL; |
283 | |
284 | gdk_pixbuf_scale (src, dest, dest_x: 0, dest_y: 0, dest_width, dest_height, offset_x: 0, offset_y: 0, |
285 | scale_x: (double) dest_width / src->width, |
286 | scale_y: (double) dest_height / src->height, |
287 | interp_type); |
288 | |
289 | return dest; |
290 | } |
291 | |
292 | /** |
293 | * gdk_pixbuf_composite_color_simple: |
294 | * @src: a #GdkPixbuf |
295 | * @dest_width: the width of destination image |
296 | * @dest_height: the height of destination image |
297 | * @interp_type: the interpolation type for the transformation. |
298 | * @overall_alpha: overall alpha for source image (0..255) |
299 | * @check_size: the size of checks in the checkboard (must be a power of two) |
300 | * @color1: the color of check at upper left |
301 | * @color2: the color of the other check |
302 | * |
303 | * Creates a new pixbuf by scaling `src` to `dest_width` x `dest_height` |
304 | * and alpha blending the result with a checkboard of colors `color1` |
305 | * and `color2`. |
306 | * |
307 | * Return value: (nullable) (transfer full): the new pixbuf |
308 | **/ |
309 | GdkPixbuf * |
310 | gdk_pixbuf_composite_color_simple (const GdkPixbuf *src, |
311 | int dest_width, |
312 | int dest_height, |
313 | GdkInterpType interp_type, |
314 | int overall_alpha, |
315 | int check_size, |
316 | guint32 color1, |
317 | guint32 color2) |
318 | { |
319 | GdkPixbuf *dest; |
320 | |
321 | g_return_val_if_fail (GDK_IS_PIXBUF (src), NULL); |
322 | g_return_val_if_fail (dest_width > 0, NULL); |
323 | g_return_val_if_fail (dest_height > 0, NULL); |
324 | g_return_val_if_fail (overall_alpha >= 0 && overall_alpha <= 255, NULL); |
325 | |
326 | dest = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, has_alpha: src->has_alpha, bits_per_sample: 8, width: dest_width, height: dest_height); |
327 | if (!dest) |
328 | return NULL; |
329 | |
330 | gdk_pixbuf_composite_color (src, dest, dest_x: 0, dest_y: 0, dest_width, dest_height, offset_x: 0, offset_y: 0, |
331 | scale_x: (double) dest_width / src->width, |
332 | scale_y: (double) dest_height / src->height, |
333 | interp_type, overall_alpha, check_x: 0, check_y: 0, check_size, color1, color2); |
334 | |
335 | return dest; |
336 | } |
337 | |
338 | #define OFFSET(pb, x, y) ((x) * (pb)->n_channels + (gsize)(y) * (pb)->rowstride) |
339 | |
340 | /** |
341 | * gdk_pixbuf_rotate_simple: |
342 | * @src: a #GdkPixbuf |
343 | * @angle: the angle to rotate by |
344 | * |
345 | * Rotates a pixbuf by a multiple of 90 degrees, and returns the |
346 | * result in a new pixbuf. |
347 | * |
348 | * If `angle` is 0, this function will return a copy of `src`. |
349 | * |
350 | * Returns: (nullable) (transfer full): the new pixbuf |
351 | * |
352 | * Since: 2.6 |
353 | */ |
354 | GdkPixbuf * |
355 | gdk_pixbuf_rotate_simple (const GdkPixbuf *src, |
356 | GdkPixbufRotation angle) |
357 | { |
358 | const guint8 *src_pixels; |
359 | guint8 *dest_pixels; |
360 | GdkPixbuf *dest; |
361 | const guchar *p; |
362 | guchar *q; |
363 | gint x, y; |
364 | |
365 | g_return_val_if_fail (GDK_IS_PIXBUF (src), NULL); |
366 | src_pixels = gdk_pixbuf_read_pixels (pixbuf: src); |
367 | |
368 | switch (angle % 360) |
369 | { |
370 | case 0: |
371 | dest = gdk_pixbuf_copy (pixbuf: src); |
372 | break; |
373 | case 90: |
374 | dest = gdk_pixbuf_new (colorspace: src->colorspace, |
375 | has_alpha: src->has_alpha, |
376 | bits_per_sample: src->bits_per_sample, |
377 | width: src->height, |
378 | height: src->width); |
379 | if (!dest) |
380 | return NULL; |
381 | |
382 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
383 | |
384 | for (y = 0; y < src->height; y++) |
385 | { |
386 | for (x = 0; x < src->width; x++) |
387 | { |
388 | p = src_pixels + OFFSET (src, x, y); |
389 | q = dest_pixels + OFFSET (dest, y, src->width - x - 1); |
390 | memcpy (dest: q, src: p, n: dest->n_channels); |
391 | } |
392 | } |
393 | break; |
394 | case 180: |
395 | dest = gdk_pixbuf_new (colorspace: src->colorspace, |
396 | has_alpha: src->has_alpha, |
397 | bits_per_sample: src->bits_per_sample, |
398 | width: src->width, |
399 | height: src->height); |
400 | if (!dest) |
401 | return NULL; |
402 | |
403 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
404 | |
405 | for (y = 0; y < src->height; y++) |
406 | { |
407 | for (x = 0; x < src->width; x++) |
408 | { |
409 | p = src_pixels + OFFSET (src, x, y); |
410 | q = dest_pixels + OFFSET (dest, src->width - x - 1, src->height - y - 1); |
411 | memcpy (dest: q, src: p, n: dest->n_channels); |
412 | } |
413 | } |
414 | break; |
415 | case 270: |
416 | dest = gdk_pixbuf_new (colorspace: src->colorspace, |
417 | has_alpha: src->has_alpha, |
418 | bits_per_sample: src->bits_per_sample, |
419 | width: src->height, |
420 | height: src->width); |
421 | if (!dest) |
422 | return NULL; |
423 | |
424 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
425 | |
426 | for (y = 0; y < src->height; y++) |
427 | { |
428 | for (x = 0; x < src->width; x++) |
429 | { |
430 | p = src_pixels + OFFSET (src, x, y); |
431 | q = dest_pixels + OFFSET (dest, src->height - y - 1, x); |
432 | memcpy (dest: q, src: p, n: dest->n_channels); |
433 | } |
434 | } |
435 | break; |
436 | default: |
437 | dest = NULL; |
438 | g_warning ("gdk_pixbuf_rotate_simple() can only rotate " |
439 | "by multiples of 90 degrees" ); |
440 | g_assert_not_reached (); |
441 | } |
442 | |
443 | return dest; |
444 | } |
445 | |
446 | /** |
447 | * gdk_pixbuf_flip: |
448 | * @src: a #GdkPixbuf |
449 | * @horizontal: `TRUE` to flip horizontally, `FALSE` to flip vertically |
450 | * |
451 | * Flips a pixbuf horizontally or vertically and returns the |
452 | * result in a new pixbuf. |
453 | * |
454 | * Returns: (nullable) (transfer full): the new pixbuf |
455 | * |
456 | * Since: 2.6 |
457 | */ |
458 | GdkPixbuf * |
459 | gdk_pixbuf_flip (const GdkPixbuf *src, |
460 | gboolean horizontal) |
461 | { |
462 | const guint8 *src_pixels; |
463 | guint8 *dest_pixels; |
464 | GdkPixbuf *dest; |
465 | const guchar *p; |
466 | guchar *q; |
467 | gint x, y; |
468 | |
469 | g_return_val_if_fail (GDK_IS_PIXBUF (src), NULL); |
470 | dest = gdk_pixbuf_new (colorspace: src->colorspace, |
471 | has_alpha: src->has_alpha, |
472 | bits_per_sample: src->bits_per_sample, |
473 | width: src->width, |
474 | height: src->height); |
475 | if (!dest) |
476 | return NULL; |
477 | |
478 | dest_pixels = gdk_pixbuf_get_pixels (pixbuf: dest); |
479 | src_pixels = gdk_pixbuf_read_pixels (pixbuf: src); |
480 | |
481 | if (!horizontal) /* flip vertical */ |
482 | { |
483 | for (y = 0; y < dest->height; y++) |
484 | { |
485 | p = src_pixels + OFFSET (src, 0, y); |
486 | q = dest_pixels + OFFSET (dest, 0, dest->height - y - 1); |
487 | memcpy (dest: q, src: p, n: dest->rowstride); |
488 | } |
489 | } |
490 | else /* flip horizontal */ |
491 | { |
492 | for (y = 0; y < dest->height; y++) |
493 | { |
494 | for (x = 0; x < dest->width; x++) |
495 | { |
496 | p = src_pixels + OFFSET (src, x, y); |
497 | q = dest_pixels + OFFSET (dest, dest->width - x - 1, y); |
498 | memcpy (dest: q, src: p, n: dest->n_channels); |
499 | } |
500 | } |
501 | } |
502 | |
503 | return dest; |
504 | } |
505 | |