1 /* gdktexture.c
2 *
3 * Copyright 2016 Benjamin Otte
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * GdkTexture:
21 *
22 * `GdkTexture` is the basic element used to refer to pixel data.
23 *
24 * It is primarily meant for pixel data that will not change over
25 * multiple frames, and will be used for a long time.
26 *
27 * There are various ways to create `GdkTexture` objects from a
28 * `GdkPixbuf`, or a Cairo surface, or other pixel data.
29 *
30 * The ownership of the pixel data is transferred to the `GdkTexture`
31 * instance; you can only make a copy of it, via
32 * [method@Gdk.Texture.download].
33 *
34 * `GdkTexture` is an immutable object: That means you cannot change
35 * anything about it other than increasing the reference count via
36 * g_object_ref().
37 */
38
39 #include "config.h"
40
41 #include "gdktextureprivate.h"
42
43 #include "gdkinternals.h"
44 #include "gdkmemorytextureprivate.h"
45 #include "gdkpaintable.h"
46 #include "gdksnapshot.h"
47
48 #include <graphene.h>
49
50 /* HACK: So we don't need to include any (not-yet-created) GSK or GTK headers */
51 void
52 gtk_snapshot_append_texture (GdkSnapshot *snapshot,
53 GdkTexture *texture,
54 const graphene_rect_t *bounds);
55
56 enum {
57 PROP_0,
58 PROP_WIDTH,
59 PROP_HEIGHT,
60
61 N_PROPS
62 };
63
64 static GParamSpec *properties[N_PROPS];
65
66 static void
gdk_texture_paintable_snapshot(GdkPaintable * paintable,GdkSnapshot * snapshot,double width,double height)67 gdk_texture_paintable_snapshot (GdkPaintable *paintable,
68 GdkSnapshot *snapshot,
69 double width,
70 double height)
71 {
72 GdkTexture *self = GDK_TEXTURE (paintable);
73
74 gtk_snapshot_append_texture (snapshot,
75 self,
76 &GRAPHENE_RECT_INIT (0, 0, width, height));
77 }
78
79 static GdkPaintableFlags
gdk_texture_paintable_get_flags(GdkPaintable * paintable)80 gdk_texture_paintable_get_flags (GdkPaintable *paintable)
81 {
82 return GDK_PAINTABLE_STATIC_SIZE
83 | GDK_PAINTABLE_STATIC_CONTENTS;
84 }
85
86 static int
gdk_texture_paintable_get_intrinsic_width(GdkPaintable * paintable)87 gdk_texture_paintable_get_intrinsic_width (GdkPaintable *paintable)
88 {
89 GdkTexture *self = GDK_TEXTURE (paintable);
90
91 return self->width;
92 }
93
94 static int
gdk_texture_paintable_get_intrinsic_height(GdkPaintable * paintable)95 gdk_texture_paintable_get_intrinsic_height (GdkPaintable *paintable)
96 {
97 GdkTexture *self = GDK_TEXTURE (paintable);
98
99 return self->height;
100 }
101
102 static void
gdk_texture_paintable_init(GdkPaintableInterface * iface)103 gdk_texture_paintable_init (GdkPaintableInterface *iface)
104 {
105 iface->snapshot = gdk_texture_paintable_snapshot;
106 iface->get_flags = gdk_texture_paintable_get_flags;
107 iface->get_intrinsic_width = gdk_texture_paintable_get_intrinsic_width;
108 iface->get_intrinsic_height = gdk_texture_paintable_get_intrinsic_height;
109 }
110
G_DEFINE_ABSTRACT_TYPE_WITH_CODE(GdkTexture,gdk_texture,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,gdk_texture_paintable_init))111 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GdkTexture, gdk_texture, G_TYPE_OBJECT,
112 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
113 gdk_texture_paintable_init))
114
115 #define GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD(obj,method) \
116 g_critical ("Texture of type '%s' does not implement GdkTexture::" # method, G_OBJECT_TYPE_NAME (obj))
117
118 static void
119 gdk_texture_real_download (GdkTexture *self,
120 const GdkRectangle *area,
121 guchar *data,
122 gsize stride)
123 {
124 GDK_TEXTURE_WARN_NOT_IMPLEMENTED_METHOD (self, download);
125 }
126
127 static void
gdk_texture_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)128 gdk_texture_set_property (GObject *gobject,
129 guint prop_id,
130 const GValue *value,
131 GParamSpec *pspec)
132 {
133 GdkTexture *self = GDK_TEXTURE (gobject);
134
135 switch (prop_id)
136 {
137 case PROP_WIDTH:
138 self->width = g_value_get_int (value);
139 break;
140
141 case PROP_HEIGHT:
142 self->height = g_value_get_int (value);
143 break;
144
145 default:
146 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
147 break;
148 }
149 }
150
151 static void
gdk_texture_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)152 gdk_texture_get_property (GObject *gobject,
153 guint prop_id,
154 GValue *value,
155 GParamSpec *pspec)
156 {
157 GdkTexture *self = GDK_TEXTURE (gobject);
158
159 switch (prop_id)
160 {
161 case PROP_WIDTH:
162 g_value_set_int (value, self->width);
163 break;
164
165 case PROP_HEIGHT:
166 g_value_set_int (value, self->height);
167 break;
168
169 default:
170 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
171 break;
172 }
173 }
174
175 static void
gdk_texture_dispose(GObject * object)176 gdk_texture_dispose (GObject *object)
177 {
178 GdkTexture *self = GDK_TEXTURE (object);
179
180 gdk_texture_clear_render_data (self);
181
182 G_OBJECT_CLASS (gdk_texture_parent_class)->dispose (object);
183 }
184
185 static void
gdk_texture_class_init(GdkTextureClass * klass)186 gdk_texture_class_init (GdkTextureClass *klass)
187 {
188 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
189
190 klass->download = gdk_texture_real_download;
191
192 gobject_class->set_property = gdk_texture_set_property;
193 gobject_class->get_property = gdk_texture_get_property;
194 gobject_class->dispose = gdk_texture_dispose;
195
196 /**
197 * GdkTexture:width: (attributes org.gtk.Property.get=gdk_texture_get_width)
198 *
199 * The width of the texture, in pixels.
200 */
201 properties[PROP_WIDTH] =
202 g_param_spec_int ("width",
203 "Width",
204 "The width of the texture",
205 1,
206 G_MAXINT,
207 1,
208 G_PARAM_READWRITE |
209 G_PARAM_CONSTRUCT_ONLY |
210 G_PARAM_STATIC_STRINGS |
211 G_PARAM_EXPLICIT_NOTIFY);
212
213 /**
214 * GdkTexture:height: (attributes org.gtk.Property.get=gdk_texture_get_height)
215 *
216 * The height of the texture, in pixels.
217 */
218 properties[PROP_HEIGHT] =
219 g_param_spec_int ("height",
220 "Height",
221 "The height of the texture",
222 1,
223 G_MAXINT,
224 1,
225 G_PARAM_READWRITE |
226 G_PARAM_CONSTRUCT_ONLY |
227 G_PARAM_STATIC_STRINGS |
228 G_PARAM_EXPLICIT_NOTIFY);
229
230 g_object_class_install_properties (gobject_class, N_PROPS, properties);
231 }
232
233 static void
gdk_texture_init(GdkTexture * self)234 gdk_texture_init (GdkTexture *self)
235 {
236 }
237
238 /**
239 * gdk_texture_new_for_surface:
240 * @surface: a cairo image surface
241 *
242 * Creates a new texture object representing the surface.
243 *
244 * @surface must be an image surface with format CAIRO_FORMAT_ARGB32.
245 *
246 * Returns: a new `GdkTexture`
247 */
248 GdkTexture *
gdk_texture_new_for_surface(cairo_surface_t * surface)249 gdk_texture_new_for_surface (cairo_surface_t *surface)
250 {
251 GdkTexture *texture;
252 GBytes *bytes;
253
254 g_return_val_if_fail (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE, NULL);
255 g_return_val_if_fail (cairo_image_surface_get_width (surface) > 0, NULL);
256 g_return_val_if_fail (cairo_image_surface_get_height (surface) > 0, NULL);
257
258 bytes = g_bytes_new_with_free_func (cairo_image_surface_get_data (surface),
259 cairo_image_surface_get_height (surface)
260 * cairo_image_surface_get_stride (surface),
261 (GDestroyNotify) cairo_surface_destroy,
262 cairo_surface_reference (surface));
263
264 texture = gdk_memory_texture_new (cairo_image_surface_get_width (surface),
265 cairo_image_surface_get_height (surface),
266 GDK_MEMORY_CAIRO_FORMAT_ARGB32,
267 bytes,
268 cairo_image_surface_get_stride (surface));
269
270 g_bytes_unref (bytes);
271
272 return texture;
273 }
274
275 /**
276 * gdk_texture_new_for_pixbuf:
277 * @pixbuf: a `GdkPixbuf`
278 *
279 * Creates a new texture object representing the `GdkPixbuf`.
280 *
281 * Returns: a new `GdkTexture`
282 */
283 GdkTexture *
gdk_texture_new_for_pixbuf(GdkPixbuf * pixbuf)284 gdk_texture_new_for_pixbuf (GdkPixbuf *pixbuf)
285 {
286 GdkTexture *texture;
287 GBytes *bytes;
288
289 g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
290
291 bytes = g_bytes_new_with_free_func (gdk_pixbuf_get_pixels (pixbuf),
292 gdk_pixbuf_get_height (pixbuf)
293 * gdk_pixbuf_get_rowstride (pixbuf),
294 g_object_unref,
295 g_object_ref (pixbuf));
296 texture = gdk_memory_texture_new (gdk_pixbuf_get_width (pixbuf),
297 gdk_pixbuf_get_height (pixbuf),
298 gdk_pixbuf_get_has_alpha (pixbuf)
299 ? GDK_MEMORY_GDK_PIXBUF_ALPHA
300 : GDK_MEMORY_GDK_PIXBUF_OPAQUE,
301 bytes,
302 gdk_pixbuf_get_rowstride (pixbuf));
303
304 g_bytes_unref (bytes);
305
306 return texture;
307 }
308
309 /**
310 * gdk_texture_new_from_resource:
311 * @resource_path: the path of the resource file
312 *
313 * Creates a new texture by loading an image from a resource.
314 *
315 * The file format is detected automatically. The supported formats
316 * are PNG and JPEG, though more formats might be available.
317 *
318 * It is a fatal error if @resource_path does not specify a valid
319 * image resource and the program will abort if that happens.
320 * If you are unsure about the validity of a resource, use
321 * [ctor@Gdk.Texture.new_from_file] to load it.
322 *
323 * Return value: A newly-created `GdkTexture`
324 */
325 GdkTexture *
gdk_texture_new_from_resource(const char * resource_path)326 gdk_texture_new_from_resource (const char *resource_path)
327 {
328 GError *error = NULL;
329 GdkTexture *texture;
330 GdkPixbuf *pixbuf;
331
332 g_return_val_if_fail (resource_path != NULL, NULL);
333
334 pixbuf = gdk_pixbuf_new_from_resource (resource_path, &error);
335 if (pixbuf == NULL)
336 g_error ("Resource path %s is not a valid image: %s", resource_path, error->message);
337
338 texture = gdk_texture_new_for_pixbuf (pixbuf);
339 g_object_unref (pixbuf);
340
341 return texture;
342 }
343
344 /**
345 * gdk_texture_new_from_file:
346 * @file: `GFile` to load
347 * @error: Return location for an error
348 *
349 * Creates a new texture by loading an image from a file.
350 *
351 * The file format is detected automatically. The supported formats
352 * are PNG and JPEG, though more formats might be available.
353 *
354 * If %NULL is returned, then @error will be set.
355 *
356 * Return value: A newly-created `GdkTexture`
357 */
358 GdkTexture *
gdk_texture_new_from_file(GFile * file,GError ** error)359 gdk_texture_new_from_file (GFile *file,
360 GError **error)
361 {
362 GdkTexture *texture;
363 GdkPixbuf *pixbuf;
364 GInputStream *stream;
365
366 g_return_val_if_fail (G_IS_FILE (file), NULL);
367 g_return_val_if_fail (error == NULL || *error == NULL, NULL);
368
369 stream = G_INPUT_STREAM (g_file_read (file, NULL, error));
370 if (stream == NULL)
371 return NULL;
372
373 pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, error);
374 g_object_unref (stream);
375 if (pixbuf == NULL)
376 return NULL;
377
378 texture = gdk_texture_new_for_pixbuf (pixbuf);
379 g_object_unref (pixbuf);
380
381 return texture;
382 }
383
384 /**
385 * gdk_texture_get_width: (attributes org.gtk.Method.get_property=width)
386 * @texture: a `GdkTexture`
387 *
388 * Returns the width of @texture, in pixels.
389 *
390 * Returns: the width of the `GdkTexture`
391 */
392 int
gdk_texture_get_width(GdkTexture * texture)393 gdk_texture_get_width (GdkTexture *texture)
394 {
395 g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
396
397 return texture->width;
398 }
399
400 /**
401 * gdk_texture_get_height: (attributes org.gtk.Method.get_property=height)
402 * @texture: a `GdkTexture`
403 *
404 * Returns the height of the @texture, in pixels.
405 *
406 * Returns: the height of the `GdkTexture`
407 */
408 int
gdk_texture_get_height(GdkTexture * texture)409 gdk_texture_get_height (GdkTexture *texture)
410 {
411 g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
412
413 return texture->height;
414 }
415
416 cairo_surface_t *
gdk_texture_download_surface(GdkTexture * texture)417 gdk_texture_download_surface (GdkTexture *texture)
418 {
419 cairo_surface_t *surface;
420 cairo_status_t surface_status;
421
422 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
423 texture->width, texture->height);
424
425 surface_status = cairo_surface_status (surface);
426 if (surface_status != CAIRO_STATUS_SUCCESS)
427 g_warning ("%s: surface error: %s", __FUNCTION__,
428 cairo_status_to_string (surface_status));
429
430 gdk_texture_download (texture,
431 cairo_image_surface_get_data (surface),
432 cairo_image_surface_get_stride (surface));
433 cairo_surface_mark_dirty (surface);
434
435 return surface;
436 }
437
438 void
gdk_texture_download_area(GdkTexture * texture,const GdkRectangle * area,guchar * data,gsize stride)439 gdk_texture_download_area (GdkTexture *texture,
440 const GdkRectangle *area,
441 guchar *data,
442 gsize stride)
443 {
444 g_assert (area->x >= 0);
445 g_assert (area->y >= 0);
446 g_assert (area->x + area->width <= texture->width);
447 g_assert (area->y + area->height <= texture->height);
448
449 GDK_TEXTURE_GET_CLASS (texture)->download (texture, area, data, stride);
450 }
451
452 /**
453 * gdk_texture_download:
454 * @texture: a `GdkTexture`
455 * @data: (array): pointer to enough memory to be filled with the
456 * downloaded data of @texture
457 * @stride: rowstride in bytes
458 *
459 * Downloads the @texture into local memory.
460 *
461 * This may be an expensive operation, as the actual texture data
462 * may reside on a GPU or on a remote display server.
463 *
464 * The data format of the downloaded data is equivalent to
465 * %CAIRO_FORMAT_ARGB32, so every downloaded pixel requires
466 * 4 bytes of memory.
467 *
468 * Downloading a texture into a Cairo image surface:
469 * ```c
470 * surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
471 * gdk_texture_get_width (texture),
472 * gdk_texture_get_height (texture));
473 * gdk_texture_download (texture,
474 * cairo_image_surface_get_data (surface),
475 * cairo_image_surface_get_stride (surface));
476 * cairo_surface_mark_dirty (surface);
477 * ```
478 */
479 void
gdk_texture_download(GdkTexture * texture,guchar * data,gsize stride)480 gdk_texture_download (GdkTexture *texture,
481 guchar *data,
482 gsize stride)
483 {
484 g_return_if_fail (GDK_IS_TEXTURE (texture));
485 g_return_if_fail (data != NULL);
486 g_return_if_fail (stride >= gdk_texture_get_width (texture) * 4);
487
488 gdk_texture_download_area (texture,
489 &(GdkRectangle) { 0, 0, texture->width, texture->height },
490 data,
491 stride);
492 }
493
494 gboolean
gdk_texture_set_render_data(GdkTexture * self,gpointer key,gpointer data,GDestroyNotify notify)495 gdk_texture_set_render_data (GdkTexture *self,
496 gpointer key,
497 gpointer data,
498 GDestroyNotify notify)
499 {
500 g_return_val_if_fail (data != NULL, FALSE);
501
502 if (self->render_key != NULL)
503 return FALSE;
504
505 self->render_key = key;
506 self->render_data = data;
507 self->render_notify = notify;
508
509 return TRUE;
510 }
511
512 void
gdk_texture_clear_render_data(GdkTexture * self)513 gdk_texture_clear_render_data (GdkTexture *self)
514 {
515 if (self->render_notify)
516 self->render_notify (self->render_data);
517
518 self->render_key = NULL;
519 self->render_data = NULL;
520 self->render_notify = NULL;
521 }
522
523 gpointer
gdk_texture_get_render_data(GdkTexture * self,gpointer key)524 gdk_texture_get_render_data (GdkTexture *self,
525 gpointer key)
526 {
527 if (self->render_key != key)
528 return NULL;
529
530 return self->render_data;
531 }
532
533 /**
534 * gdk_texture_save_to_png:
535 * @texture: a `GdkTexture`
536 * @filename: (type filename): the filename to store to
537 *
538 * Store the given @texture to the @filename as a PNG file.
539 *
540 * This is a utility function intended for debugging and testing.
541 * If you want more control over formats, proper error handling or
542 * want to store to a `GFile` or other location, you might want to
543 * look into using the gdk-pixbuf library.
544 *
545 * Returns: %TRUE if saving succeeded, %FALSE on failure.
546 */
547 gboolean
gdk_texture_save_to_png(GdkTexture * texture,const char * filename)548 gdk_texture_save_to_png (GdkTexture *texture,
549 const char *filename)
550 {
551 cairo_surface_t *surface;
552 cairo_status_t status;
553 gboolean result;
554
555 g_return_val_if_fail (GDK_IS_TEXTURE (texture), FALSE);
556 g_return_val_if_fail (filename != NULL, FALSE);
557
558 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
559 gdk_texture_get_width (texture),
560 gdk_texture_get_height (texture));
561 gdk_texture_download (texture,
562 cairo_image_surface_get_data (surface),
563 cairo_image_surface_get_stride (surface));
564 cairo_surface_mark_dirty (surface);
565
566 status = cairo_surface_write_to_png (surface, filename);
567
568 if (status != CAIRO_STATUS_SUCCESS ||
569 cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
570 result = FALSE;
571 else
572 result = TRUE;
573
574 cairo_surface_destroy (surface);
575
576 return result;
577 }
578