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