1 | /* GdkPixbuf library - Utilities and miscellaneous convenience functions |
2 | * |
3 | * Copyright (C) 1999 The Free Software Foundation |
4 | * |
5 | * Authors: Federico Mena-Quintero <federico@gimp.org> |
6 | * Cody Russell <bratsche@gnome.org> |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Lesser General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Lesser General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Lesser General Public |
19 | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
20 | */ |
21 | |
22 | #include "config.h" |
23 | #include <string.h> |
24 | #include <libintl.h> |
25 | |
26 | #include "gdk-pixbuf-transform.h" |
27 | #include "gdk-pixbuf-private.h" |
28 | |
29 | /** |
30 | * gdk_pixbuf_add_alpha: |
31 | * @pixbuf: A #GdkPixbuf. |
32 | * @substitute_color: Whether to set a color to zero opacity. |
33 | * @r: Red value to substitute. |
34 | * @g: Green value to substitute. |
35 | * @b: Blue value to substitute. |
36 | * |
37 | * Takes an existing pixbuf and adds an alpha channel to it. |
38 | * |
39 | * If the existing pixbuf already had an alpha channel, the channel |
40 | * values are copied from the original; otherwise, the alpha channel |
41 | * is initialized to 255 (full opacity). |
42 | * |
43 | * If `substitute_color` is `TRUE`, then the color specified by the |
44 | * (`r`, `g`, `b`) arguments will be assigned zero opacity. That is, |
45 | * if you pass `(255, 255, 255)` for the substitute color, all white |
46 | * pixels will become fully transparent. |
47 | * |
48 | * If `substitute_color` is `FALSE`, then the (`r`, `g`, `b`) arguments |
49 | * will be ignored. |
50 | * |
51 | * Return value: (transfer full): A newly-created pixbuf |
52 | **/ |
53 | GdkPixbuf * |
54 | gdk_pixbuf_add_alpha (const GdkPixbuf *pixbuf, |
55 | gboolean substitute_color, |
56 | guchar r, |
57 | guchar g, |
58 | guchar b) |
59 | { |
60 | GdkPixbuf *new_pixbuf; |
61 | int x, y; |
62 | const guint8 *src_pixels; |
63 | guint8 *ret_pixels; |
64 | const guchar *src; |
65 | guchar *dest; |
66 | |
67 | g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL); |
68 | g_return_val_if_fail (pixbuf->colorspace == GDK_COLORSPACE_RGB, NULL); |
69 | g_return_val_if_fail (pixbuf->n_channels == 3 || pixbuf->n_channels == 4, NULL); |
70 | g_return_val_if_fail (pixbuf->bits_per_sample == 8, NULL); |
71 | |
72 | src_pixels = gdk_pixbuf_read_pixels (pixbuf); |
73 | |
74 | if (pixbuf->has_alpha) { |
75 | new_pixbuf = gdk_pixbuf_copy (pixbuf); |
76 | if (!new_pixbuf) |
77 | return NULL; |
78 | |
79 | if (!substitute_color) |
80 | return new_pixbuf; |
81 | } else { |
82 | new_pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8, width: pixbuf->width, height: pixbuf->height); |
83 | } |
84 | |
85 | if (!new_pixbuf) |
86 | return NULL; |
87 | |
88 | ret_pixels = gdk_pixbuf_get_pixels (pixbuf: new_pixbuf); |
89 | |
90 | for (y = 0; y < pixbuf->height; y++, src_pixels += pixbuf->rowstride, ret_pixels += new_pixbuf->rowstride) { |
91 | guchar tr, tg, tb; |
92 | |
93 | src = src_pixels; |
94 | dest = ret_pixels; |
95 | |
96 | if (pixbuf->has_alpha) { |
97 | /* Just subst color, we already copied everything else */ |
98 | for (x = 0; x < pixbuf->width; x++) { |
99 | if (src[0] == r && src[1] == g && src[2] == b) |
100 | dest[3] = 0; |
101 | src += 4; |
102 | dest += 4; |
103 | } |
104 | } else { |
105 | for (x = 0; x < pixbuf->width; x++) { |
106 | tr = *dest++ = *src++; |
107 | tg = *dest++ = *src++; |
108 | tb = *dest++ = *src++; |
109 | |
110 | if (substitute_color && tr == r && tg == g && tb == b) |
111 | *dest++ = 0; |
112 | else |
113 | *dest++ = 255; |
114 | } |
115 | } |
116 | } |
117 | |
118 | return new_pixbuf; |
119 | } |
120 | |
121 | /** |
122 | * gdk_pixbuf_copy_area: |
123 | * @src_pixbuf: Source pixbuf. |
124 | * @src_x: Source X coordinate within @src_pixbuf. |
125 | * @src_y: Source Y coordinate within @src_pixbuf. |
126 | * @width: Width of the area to copy. |
127 | * @height: Height of the area to copy. |
128 | * @dest_pixbuf: Destination pixbuf. |
129 | * @dest_x: X coordinate within @dest_pixbuf. |
130 | * @dest_y: Y coordinate within @dest_pixbuf. |
131 | * |
132 | * Copies a rectangular area from `src_pixbuf` to `dest_pixbuf`. |
133 | * |
134 | * Conversion of pixbuf formats is done automatically. |
135 | * |
136 | * If the source rectangle overlaps the destination rectangle on the |
137 | * same pixbuf, it will be overwritten during the copy operation. |
138 | * Therefore, you can not use this function to scroll a pixbuf. |
139 | **/ |
140 | void |
141 | gdk_pixbuf_copy_area (const GdkPixbuf *src_pixbuf, |
142 | int src_x, int src_y, |
143 | int width, int height, |
144 | GdkPixbuf *dest_pixbuf, |
145 | int dest_x, int dest_y) |
146 | { |
147 | g_return_if_fail (src_pixbuf != NULL); |
148 | g_return_if_fail (dest_pixbuf != NULL); |
149 | |
150 | g_return_if_fail (src_x >= 0 && src_x + width <= src_pixbuf->width); |
151 | g_return_if_fail (src_y >= 0 && src_y + height <= src_pixbuf->height); |
152 | |
153 | g_return_if_fail (dest_x >= 0 && dest_x + width <= dest_pixbuf->width); |
154 | g_return_if_fail (dest_y >= 0 && dest_y + height <= dest_pixbuf->height); |
155 | |
156 | g_return_if_fail (!(gdk_pixbuf_get_has_alpha (src_pixbuf) && !gdk_pixbuf_get_has_alpha (dest_pixbuf))); |
157 | |
158 | /* This will perform format conversions automatically */ |
159 | |
160 | gdk_pixbuf_scale (src: src_pixbuf, |
161 | dest: dest_pixbuf, |
162 | dest_x, dest_y, |
163 | dest_width: width, dest_height: height, |
164 | offset_x: (double) (dest_x - src_x), |
165 | offset_y: (double) (dest_y - src_y), |
166 | scale_x: 1.0, scale_y: 1.0, |
167 | interp_type: GDK_INTERP_NEAREST); |
168 | } |
169 | |
170 | |
171 | |
172 | /** |
173 | * gdk_pixbuf_saturate_and_pixelate: |
174 | * @src: source image |
175 | * @dest: place to write modified version of @src |
176 | * @saturation: saturation factor |
177 | * @pixelate: whether to pixelate |
178 | * |
179 | * Modifies saturation and optionally pixelates `src`, placing the result in |
180 | * `dest`. |
181 | * |
182 | * The `src` and `dest` pixbufs must have the same image format, size, and |
183 | * rowstride. |
184 | * |
185 | * The `src` and `dest` arguments may be the same pixbuf with no ill effects. |
186 | * |
187 | * If `saturation` is 1.0 then saturation is not changed. If it's less than 1.0, |
188 | * saturation is reduced (the image turns toward grayscale); if greater than |
189 | * 1.0, saturation is increased (the image gets more vivid colors). |
190 | * |
191 | * If `pixelate` is `TRUE`, then pixels are faded in a checkerboard pattern to |
192 | * create a pixelated image. |
193 | * |
194 | **/ |
195 | void |
196 | gdk_pixbuf_saturate_and_pixelate (const GdkPixbuf *src, |
197 | GdkPixbuf *dest, |
198 | gfloat saturation, |
199 | gboolean pixelate) |
200 | { |
201 | /* NOTE that src and dest MAY be the same pixbuf! */ |
202 | |
203 | g_return_if_fail (GDK_IS_PIXBUF (src)); |
204 | g_return_if_fail (GDK_IS_PIXBUF (dest)); |
205 | g_return_if_fail (gdk_pixbuf_get_height (src) == gdk_pixbuf_get_height (dest)); |
206 | g_return_if_fail (gdk_pixbuf_get_width (src) == gdk_pixbuf_get_width (dest)); |
207 | g_return_if_fail (gdk_pixbuf_get_has_alpha (src) == gdk_pixbuf_get_has_alpha (dest)); |
208 | g_return_if_fail (gdk_pixbuf_get_colorspace (src) == gdk_pixbuf_get_colorspace (dest)); |
209 | |
210 | if (saturation == 1.0 && !pixelate) { |
211 | if (dest != src) |
212 | gdk_pixbuf_copy_area (src_pixbuf: src, src_x: 0, src_y: 0, |
213 | width: gdk_pixbuf_get_width (pixbuf: src), |
214 | height: gdk_pixbuf_get_height (pixbuf: src), |
215 | dest_pixbuf: dest, dest_x: 0, dest_y: 0); |
216 | } else { |
217 | int i, j, t; |
218 | int width, height, has_alpha, src_rowstride, dest_rowstride, bytes_per_pixel; |
219 | const guchar *src_line; |
220 | guchar *dest_line; |
221 | const guchar *src_pixel; |
222 | guchar *dest_pixel; |
223 | guchar intensity; |
224 | |
225 | has_alpha = gdk_pixbuf_get_has_alpha (pixbuf: src); |
226 | bytes_per_pixel = has_alpha ? 4 : 3; |
227 | width = gdk_pixbuf_get_width (pixbuf: src); |
228 | height = gdk_pixbuf_get_height (pixbuf: src); |
229 | src_rowstride = gdk_pixbuf_get_rowstride (pixbuf: src); |
230 | dest_rowstride = gdk_pixbuf_get_rowstride (pixbuf: dest); |
231 | |
232 | dest_line = gdk_pixbuf_get_pixels (pixbuf: dest); |
233 | src_line = gdk_pixbuf_read_pixels (pixbuf: src); |
234 | |
235 | #define DARK_FACTOR 0.7 |
236 | #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11) |
237 | #define CLAMP_UCHAR(v) (t = (v), CLAMP (t, 0, 255)) |
238 | #define SATURATE(v) ((1.0 - saturation) * intensity + saturation * (v)) |
239 | |
240 | for (i = 0 ; i < height ; i++) { |
241 | src_pixel = src_line; |
242 | src_line += src_rowstride; |
243 | dest_pixel = dest_line; |
244 | dest_line += dest_rowstride; |
245 | |
246 | for (j = 0 ; j < width ; j++) { |
247 | intensity = INTENSITY (src_pixel[0], src_pixel[1], src_pixel[2]); |
248 | if (pixelate && (i + j) % 2 == 0) { |
249 | dest_pixel[0] = intensity / 2 + 127; |
250 | dest_pixel[1] = intensity / 2 + 127; |
251 | dest_pixel[2] = intensity / 2 + 127; |
252 | } else if (pixelate) { |
253 | dest_pixel[0] = CLAMP_UCHAR ((SATURATE (src_pixel[0])) * DARK_FACTOR); |
254 | dest_pixel[1] = CLAMP_UCHAR ((SATURATE (src_pixel[1])) * DARK_FACTOR); |
255 | dest_pixel[2] = CLAMP_UCHAR ((SATURATE (src_pixel[2])) * DARK_FACTOR); |
256 | } else { |
257 | dest_pixel[0] = CLAMP_UCHAR (SATURATE (src_pixel[0])); |
258 | dest_pixel[1] = CLAMP_UCHAR (SATURATE (src_pixel[1])); |
259 | dest_pixel[2] = CLAMP_UCHAR (SATURATE (src_pixel[2])); |
260 | } |
261 | |
262 | if (has_alpha) |
263 | dest_pixel[3] = src_pixel[3]; |
264 | |
265 | src_pixel += bytes_per_pixel; |
266 | dest_pixel += bytes_per_pixel; |
267 | } |
268 | } |
269 | } |
270 | } |
271 | |
272 | |
273 | /** |
274 | * gdk_pixbuf_apply_embedded_orientation: |
275 | * @src: a pixbuf with an orientation option |
276 | * |
277 | * Takes an existing pixbuf and checks for the presence of an |
278 | * associated "orientation" option. |
279 | * |
280 | * The orientation option may be provided by the JPEG loader (which |
281 | * reads the exif orientation tag) or the TIFF loader (which reads |
282 | * the TIFF orientation tag, and compensates it for the partial |
283 | * transforms performed by libtiff). |
284 | * |
285 | * If an orientation option/tag is present, the appropriate transform |
286 | * will be performed so that the pixbuf is oriented correctly. |
287 | * |
288 | * Return value: (transfer full) (nullable): A newly-created pixbuf |
289 | * |
290 | * Since: 2.12 |
291 | **/ |
292 | GdkPixbuf * |
293 | gdk_pixbuf_apply_embedded_orientation (GdkPixbuf *src) |
294 | { |
295 | const gchar *orientation_string; |
296 | int transform = 0; |
297 | GdkPixbuf *temp; |
298 | GdkPixbuf *dest; |
299 | |
300 | g_return_val_if_fail (GDK_IS_PIXBUF (src), NULL); |
301 | |
302 | /* Read the orientation option associated with the pixbuf */ |
303 | orientation_string = gdk_pixbuf_get_option (pixbuf: src, key: "orientation" ); |
304 | |
305 | if (orientation_string) { |
306 | /* If an orientation option was found, convert the |
307 | orientation string into an integer. */ |
308 | transform = (int) g_ascii_strtoll (nptr: orientation_string, NULL, base: 10); |
309 | } |
310 | |
311 | /* Apply the actual transforms, which involve rotations and flips. |
312 | The meaning of orientation values 1-8 and the required transforms |
313 | are defined by the TIFF and EXIF (for JPEGs) standards. */ |
314 | switch (transform) { |
315 | case 1: |
316 | dest = src; |
317 | g_object_ref (dest); |
318 | break; |
319 | case 2: |
320 | dest = gdk_pixbuf_flip (src, TRUE); |
321 | break; |
322 | case 3: |
323 | dest = gdk_pixbuf_rotate_simple (src, angle: GDK_PIXBUF_ROTATE_UPSIDEDOWN); |
324 | break; |
325 | case 4: |
326 | dest = gdk_pixbuf_flip (src, FALSE); |
327 | break; |
328 | case 5: |
329 | temp = gdk_pixbuf_rotate_simple (src, angle: GDK_PIXBUF_ROTATE_CLOCKWISE); |
330 | dest = gdk_pixbuf_flip (src: temp, TRUE); |
331 | g_object_unref (object: temp); |
332 | break; |
333 | case 6: |
334 | dest = gdk_pixbuf_rotate_simple (src, angle: GDK_PIXBUF_ROTATE_CLOCKWISE); |
335 | break; |
336 | case 7: |
337 | temp = gdk_pixbuf_rotate_simple (src, angle: GDK_PIXBUF_ROTATE_CLOCKWISE); |
338 | dest = gdk_pixbuf_flip (src: temp, FALSE); |
339 | g_object_unref (object: temp); |
340 | break; |
341 | case 8: |
342 | dest = gdk_pixbuf_rotate_simple (src, angle: GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE); |
343 | break; |
344 | default: |
345 | /* if no orientation tag was present */ |
346 | dest = src; |
347 | g_object_ref (dest); |
348 | break; |
349 | } |
350 | |
351 | return dest; |
352 | } |
353 | |
354 | #ifdef GDK_PIXBUF_RELOCATABLE |
355 | |
356 | static const gchar * |
357 | get_localedir (void) |
358 | { |
359 | gchar *temp; |
360 | |
361 | temp = g_build_filename (gdk_pixbuf_get_toplevel (), "share/locale" , NULL); |
362 | |
363 | #ifdef G_OS_WIN32 |
364 | { |
365 | gchar *retval; |
366 | /* The localedir is passed to bindtextdomain() which isn't |
367 | * UTF-8-aware. |
368 | */ |
369 | retval = g_win32_locale_filename_from_utf8 (temp); |
370 | g_free (temp); |
371 | return retval; |
372 | } |
373 | #else |
374 | return temp; |
375 | #endif |
376 | } |
377 | |
378 | #undef GDK_PIXBUF_LOCALEDIR |
379 | #define GDK_PIXBUF_LOCALEDIR get_localedir () |
380 | |
381 | #endif |
382 | |
383 | void |
384 | _gdk_pixbuf_init_gettext (void) |
385 | { |
386 | static gsize gettext_initialized = FALSE; |
387 | |
388 | if (G_UNLIKELY (g_once_init_enter (&gettext_initialized))) { |
389 | bindtextdomain (GETTEXT_PACKAGE, GDK_PIXBUF_LOCALEDIR); |
390 | #ifdef HAVE_BIND_TEXTDOMAIN_CODESET |
391 | bind_textdomain_codeset (GETTEXT_PACKAGE, codeset: "UTF-8" ); |
392 | #endif |
393 | g_once_init_leave (&gettext_initialized, TRUE); |
394 | } |
395 | } |
396 | |
397 | const gchar * |
398 | gdk_pixbuf_gettext (const gchar *msgid) |
399 | { |
400 | return g_dgettext (GETTEXT_PACKAGE, msgid); |
401 | } |
402 | |