1/* GdkPixbuf library - convert X drawable information to RGB
2 *
3 * Copyright (C) 1999 Michael Zucchi
4 *
5 * Authors: Michael Zucchi <zucchi@zedzone.mmc.com.au>
6 * Cody Russell <bratsche@dfw.net>
7 * Federico Mena-Quintero <federico@gimp.org>
8 *
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
13 *
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
18 *
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23#include "config.h"
24
25#include "gdkpixbuf.h"
26
27#include "gdkwindow.h"
28#include "gdkinternals.h"
29
30#include <gdk-pixbuf/gdk-pixbuf.h>
31
32/**
33 * SECTION:pixbufs
34 * @Short_description: Functions for obtaining pixbufs
35 * @Title: Pixbufs
36 *
37 * Pixbufs are client-side images. For details on how to create
38 * and manipulate pixbufs, see the #GdkPixbuf API documentation.
39 *
40 * The functions described here allow to obtain pixbufs from
41 * #GdkWindows and cairo surfaces.
42 */
43
44
45/**
46 * gdk_pixbuf_get_from_window:
47 * @window: Source window
48 * @src_x: Source X coordinate within @window
49 * @src_y: Source Y coordinate within @window
50 * @width: Width in pixels of region to get
51 * @height: Height in pixels of region to get
52 *
53 * Transfers image data from a #GdkWindow and converts it to an RGB(A)
54 * representation inside a #GdkPixbuf. In other words, copies
55 * image data from a server-side drawable to a client-side RGB(A) buffer.
56 * This allows you to efficiently read individual pixels on the client side.
57 *
58 * This function will create an RGB pixbuf with 8 bits per channel with
59 * the size specified by the @width and @height arguments scaled by the
60 * scale factor of @window. The pixbuf will contain an alpha channel if
61 * the @window contains one.
62 *
63 * If the window is off the screen, then there is no image data in the
64 * obscured/offscreen regions to be placed in the pixbuf. The contents of
65 * portions of the pixbuf corresponding to the offscreen region are undefined.
66 *
67 * If the window you’re obtaining data from is partially obscured by
68 * other windows, then the contents of the pixbuf areas corresponding
69 * to the obscured regions are undefined.
70 *
71 * If the window is not mapped (typically because it’s iconified/minimized
72 * or not on the current workspace), then %NULL will be returned.
73 *
74 * If memory can’t be allocated for the return value, %NULL will be returned
75 * instead.
76 *
77 * (In short, there are several ways this function can fail, and if it fails
78 * it returns %NULL; so check the return value.)
79 *
80 * Returns: (nullable) (transfer full): A newly-created pixbuf with a
81 * reference count of 1, or %NULL on error
82 */
83GdkPixbuf *
84gdk_pixbuf_get_from_window (GdkWindow *src,
85 gint src_x,
86 gint src_y,
87 gint width,
88 gint height)
89{
90 cairo_surface_t *surface;
91 cairo_surface_t *copy;
92 cairo_t *cr;
93 GdkPixbuf *dest;
94 int scale;
95
96 g_return_val_if_fail (GDK_IS_WINDOW (src), NULL);
97 g_return_val_if_fail (gdk_window_is_viewable (src), NULL);
98
99 surface = _gdk_window_ref_cairo_surface (src);
100 scale = gdk_window_get_scale_factor (src);
101
102 /* We do not know what happened to this surface outside of GDK.
103 * Especially for foreign windows, they will have been modified
104 * by external applications.
105 * So be on the safe side and:
106 */
107 cairo_surface_mark_dirty (surface);
108
109 if (cairo_surface_get_content (surface) & CAIRO_CONTENT_ALPHA)
110 copy = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width * scale, height * scale);
111 else
112 copy = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width * scale, height * scale);
113
114 cairo_surface_set_device_scale (copy, scale, scale);
115
116 cr = cairo_create (copy);
117 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
118 cairo_set_source_surface (cr, surface, -src_x, -src_y);
119 cairo_paint (cr);
120 cairo_destroy (cr);
121
122 dest = gdk_pixbuf_get_from_surface (copy, 0, 0, width * scale, height * scale);
123
124 cairo_surface_destroy (copy);
125 cairo_surface_destroy (surface);
126
127 return dest;
128}
129
130static cairo_format_t
131gdk_cairo_format_for_content (cairo_content_t content)
132{
133 switch (content)
134 {
135 case CAIRO_CONTENT_COLOR:
136 return CAIRO_FORMAT_RGB24;
137 case CAIRO_CONTENT_ALPHA:
138 return CAIRO_FORMAT_A8;
139 case CAIRO_CONTENT_COLOR_ALPHA:
140 default:
141 return CAIRO_FORMAT_ARGB32;
142 }
143}
144
145static cairo_surface_t *
146gdk_cairo_surface_coerce_to_image (cairo_surface_t *surface,
147 cairo_content_t content,
148 int src_x,
149 int src_y,
150 int width,
151 int height)
152{
153 cairo_surface_t *copy;
154 cairo_t *cr;
155
156 copy = cairo_image_surface_create (gdk_cairo_format_for_content (content),
157 width,
158 height);
159
160 cr = cairo_create (copy);
161 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
162 cairo_set_source_surface (cr, surface, -src_x, -src_y);
163 cairo_paint (cr);
164 cairo_destroy (cr);
165
166 return copy;
167}
168
169static void
170convert_alpha (guchar *dest_data,
171 int dest_stride,
172 guchar *src_data,
173 int src_stride,
174 int src_x,
175 int src_y,
176 int width,
177 int height)
178{
179 int x, y;
180
181 src_data += src_stride * src_y + src_x * 4;
182
183 for (y = 0; y < height; y++) {
184 guint32 *src = (guint32 *) src_data;
185
186 for (x = 0; x < width; x++) {
187 guint alpha = src[x] >> 24;
188
189 if (alpha == 0)
190 {
191 dest_data[x * 4 + 0] = 0;
192 dest_data[x * 4 + 1] = 0;
193 dest_data[x * 4 + 2] = 0;
194 }
195 else
196 {
197 dest_data[x * 4 + 0] = (((src[x] & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
198 dest_data[x * 4 + 1] = (((src[x] & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
199 dest_data[x * 4 + 2] = (((src[x] & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
200 }
201 dest_data[x * 4 + 3] = alpha;
202 }
203
204 src_data += src_stride;
205 dest_data += dest_stride;
206 }
207}
208
209static void
210convert_no_alpha (guchar *dest_data,
211 int dest_stride,
212 guchar *src_data,
213 int src_stride,
214 int src_x,
215 int src_y,
216 int width,
217 int height)
218{
219 int x, y;
220
221 src_data += src_stride * src_y + src_x * 4;
222
223 for (y = 0; y < height; y++) {
224 guint32 *src = (guint32 *) src_data;
225
226 for (x = 0; x < width; x++) {
227 dest_data[x * 3 + 0] = src[x] >> 16;
228 dest_data[x * 3 + 1] = src[x] >> 8;
229 dest_data[x * 3 + 2] = src[x];
230 }
231
232 src_data += src_stride;
233 dest_data += dest_stride;
234 }
235}
236
237/**
238 * gdk_pixbuf_get_from_surface:
239 * @surface: surface to copy from
240 * @src_x: Source X coordinate within @surface
241 * @src_y: Source Y coordinate within @surface
242 * @width: Width in pixels of region to get
243 * @height: Height in pixels of region to get
244 *
245 * Transfers image data from a #cairo_surface_t and converts it to an RGB(A)
246 * representation inside a #GdkPixbuf. This allows you to efficiently read
247 * individual pixels from cairo surfaces. For #GdkWindows, use
248 * gdk_pixbuf_get_from_window() instead.
249 *
250 * This function will create an RGB pixbuf with 8 bits per channel.
251 * The pixbuf will contain an alpha channel if the @surface contains one.
252 *
253 * Returns: (nullable) (transfer full): A newly-created pixbuf with a
254 * reference count of 1, or %NULL on error
255 */
256GdkPixbuf *
257gdk_pixbuf_get_from_surface (cairo_surface_t *surface,
258 gint src_x,
259 gint src_y,
260 gint width,
261 gint height)
262{
263 cairo_content_t content;
264 GdkPixbuf *dest;
265
266 /* General sanity checks */
267 g_return_val_if_fail (surface != NULL, NULL);
268 g_return_val_if_fail (width > 0 && height > 0, NULL);
269
270 content = cairo_surface_get_content (surface) | CAIRO_CONTENT_COLOR;
271 dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
272 !!(content & CAIRO_CONTENT_ALPHA),
273 8,
274 width, height);
275
276 if (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE &&
277 cairo_image_surface_get_format (surface) == gdk_cairo_format_for_content (content))
278 surface = cairo_surface_reference (surface);
279 else
280 {
281 surface = gdk_cairo_surface_coerce_to_image (surface, content,
282 src_x, src_y,
283 width, height);
284 src_x = 0;
285 src_y = 0;
286 }
287 cairo_surface_flush (surface);
288 if (cairo_surface_status (surface) || dest == NULL)
289 {
290 cairo_surface_destroy (surface);
291 return NULL;
292 }
293
294 if (gdk_pixbuf_get_has_alpha (dest))
295 convert_alpha (gdk_pixbuf_get_pixels (dest),
296 gdk_pixbuf_get_rowstride (dest),
297 cairo_image_surface_get_data (surface),
298 cairo_image_surface_get_stride (surface),
299 src_x, src_y,
300 width, height);
301 else
302 convert_no_alpha (gdk_pixbuf_get_pixels (dest),
303 gdk_pixbuf_get_rowstride (dest),
304 cairo_image_surface_get_data (surface),
305 cairo_image_surface_get_stride (surface),
306 src_x, src_y,
307 width, height);
308
309 cairo_surface_destroy (surface);
310 return dest;
311}
312