1/* Copyright (C) 2016 Red Hat, Inc.
2 *
3 * This library is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 2 of the License, or (at your option) any later version.
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "config.h"
18
19#include <gdk/gdk.h>
20#include "gdkpixbufutilsprivate.h"
21#include "gtkscalerprivate.h"
22
23#include "gdk/gdktextureprivate.h"
24
25static GdkPixbuf *
26load_from_stream (GdkPixbufLoader *loader,
27 GInputStream *stream,
28 GCancellable *cancellable,
29 GError **error)
30{
31 GdkPixbuf *pixbuf;
32 gssize n_read;
33 guchar buffer[65536];
34 gboolean res;
35
36 res = TRUE;
37 while (1)
38 {
39 n_read = g_input_stream_read (stream, buffer, count: sizeof (buffer), cancellable, error);
40 if (n_read < 0)
41 {
42 res = FALSE;
43 error = NULL; /* Ignore further errors */
44 break;
45 }
46
47 if (n_read == 0)
48 break;
49
50 if (!gdk_pixbuf_loader_write (loader, buf: buffer, count: n_read, error))
51 {
52 res = FALSE;
53 error = NULL;
54 break;
55 }
56 }
57
58 if (!gdk_pixbuf_loader_close (loader, error))
59 {
60 res = FALSE;
61 error = NULL;
62 }
63
64 pixbuf = NULL;
65
66 if (res)
67 {
68 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
69 if (pixbuf)
70 g_object_ref (pixbuf);
71 }
72
73 return pixbuf;
74}
75
76static void
77size_prepared_cb (GdkPixbufLoader *loader,
78 int width,
79 int height,
80 gpointer data)
81{
82 double *scale = data;
83
84 width = MAX (*scale * width, 1);
85 height = MAX (*scale * height, 1);
86
87 gdk_pixbuf_loader_set_size (loader, width, height);
88}
89
90/* Like gdk_pixbuf_new_from_stream_at_scale, but
91 * load the image at its original size times the
92 * given scale.
93 */
94GdkPixbuf *
95_gdk_pixbuf_new_from_stream_scaled (GInputStream *stream,
96 double scale,
97 GCancellable *cancellable,
98 GError **error)
99{
100 GdkPixbufLoader *loader;
101 GdkPixbuf *pixbuf;
102
103 loader = gdk_pixbuf_loader_new ();
104
105 if (scale != 0)
106 g_signal_connect (loader, "size-prepared",
107 G_CALLBACK (size_prepared_cb), &scale);
108
109 pixbuf = load_from_stream (loader, stream, cancellable, error);
110
111 g_object_unref (object: loader);
112
113 return pixbuf;
114}
115
116static void
117size_prepared_cb2 (GdkPixbufLoader *loader,
118 int width,
119 int height,
120 gpointer data)
121{
122 int *scales = data;
123
124 if (scales[2]) /* keep same aspect ratio as original, while fitting in given size */
125 {
126 double aspect = (double) height / width;
127
128 /* First use given width and calculate size */
129 width = scales[0];
130 height = scales[0] * aspect;
131
132 /* Check if it fits given height, otherwise scale down */
133 if (height > scales[1])
134 {
135 width *= (double) scales[1] / height;
136 height = scales[1];
137 }
138 }
139 else
140 {
141 width = scales[0];
142 height = scales[1];
143 }
144
145 gdk_pixbuf_loader_set_size (loader, width, height);
146}
147
148GdkPixbuf *
149_gdk_pixbuf_new_from_stream_at_scale (GInputStream *stream,
150 int width,
151 int height,
152 gboolean aspect,
153 GCancellable *cancellable,
154 GError **error)
155{
156 GdkPixbufLoader *loader;
157 GdkPixbuf *pixbuf;
158 int scales[3];
159
160 loader = gdk_pixbuf_loader_new ();
161
162 scales[0] = width;
163 scales[1] = height;
164 scales[2] = aspect;
165 g_signal_connect (loader, "size-prepared",
166 G_CALLBACK (size_prepared_cb2), scales);
167
168 pixbuf = load_from_stream (loader, stream, cancellable, error);
169
170 g_object_unref (object: loader);
171
172 return pixbuf;
173}
174
175GdkPixbuf *
176_gdk_pixbuf_new_from_stream (GInputStream *stream,
177 GCancellable *cancellable,
178 GError **error)
179{
180 return _gdk_pixbuf_new_from_stream_scaled (stream, scale: 0, cancellable, error);
181}
182
183/* Like gdk_pixbuf_new_from_resource_at_scale, but
184 * load the image at its original size times the
185 * given scale.
186 */
187GdkPixbuf *
188_gdk_pixbuf_new_from_resource_scaled (const char *resource_path,
189 double scale,
190 GError **error)
191{
192 GInputStream *stream;
193 GdkPixbuf *pixbuf;
194
195 stream = g_resources_open_stream (path: resource_path, lookup_flags: 0, error);
196 if (stream == NULL)
197 return NULL;
198
199 pixbuf = _gdk_pixbuf_new_from_stream_scaled (stream, scale, NULL, error);
200 g_object_unref (object: stream);
201
202 return pixbuf;
203}
204
205GdkPixbuf *
206_gdk_pixbuf_new_from_resource (const char *resource_path,
207 GError **error)
208{
209 return _gdk_pixbuf_new_from_resource_scaled (resource_path, scale: 0, error);
210}
211
212GdkPixbuf *
213_gdk_pixbuf_new_from_resource_at_scale (const char *resource_path,
214 int width,
215 int height,
216 gboolean preserve_aspect,
217 GError **error)
218{
219 GInputStream *stream;
220 GdkPixbuf *pixbuf;
221
222 stream = g_resources_open_stream (path: resource_path, lookup_flags: 0, error);
223 if (stream == NULL)
224 return NULL;
225
226 pixbuf = _gdk_pixbuf_new_from_stream_at_scale (stream, width, height, aspect: preserve_aspect, NULL, error);
227 g_object_unref (object: stream);
228
229 return pixbuf;
230
231}
232
233static GdkPixbuf *
234load_symbolic_svg (const char *escaped_file_data,
235 int width,
236 int height,
237 const char *icon_width_str,
238 const char *icon_height_str,
239 const char *fg_string,
240 const char *success_color_string,
241 const char *warning_color_string,
242 const char *error_color_string,
243 GError **error)
244{
245 GInputStream *stream;
246 GdkPixbuf *pixbuf;
247 char *data;
248
249 data = g_strconcat (string1: "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
250 "<svg version=\"1.1\"\n"
251 " xmlns=\"http://www.w3.org/2000/svg\"\n"
252 " xmlns:xi=\"http://www.w3.org/2001/XInclude\"\n"
253 " width=\"", icon_width_str, "\"\n"
254 " height=\"", icon_height_str, "\">\n"
255 " <style type=\"text/css\">\n"
256 " rect,circle,path {\n"
257 " fill: ", fg_string," !important;\n"
258 " }\n"
259 " .warning {\n"
260 " fill: ", warning_color_string, " !important;\n"
261 " }\n"
262 " .error {\n"
263 " fill: ", error_color_string ," !important;\n"
264 " }\n"
265 " .success {\n"
266 " fill: ", success_color_string, " !important;\n"
267 " }\n"
268 " </style>\n"
269 " <xi:include href=\"data:text/xml;base64,", escaped_file_data, "\"/>\n"
270 "</svg>",
271 NULL);
272
273 stream = g_memory_input_stream_new_from_data (data, len: -1, destroy: g_free);
274 pixbuf = gdk_pixbuf_new_from_stream_at_scale (stream, width, height, TRUE, NULL, error);
275 g_object_unref (object: stream);
276
277 return pixbuf;
278}
279
280static void
281extract_plane (GdkPixbuf *src,
282 GdkPixbuf *dst,
283 int from_plane,
284 int to_plane)
285{
286 guchar *src_data, *dst_data;
287 int width, height, src_stride, dst_stride;
288 guchar *src_row, *dst_row;
289 int x, y;
290
291 width = gdk_pixbuf_get_width (pixbuf: src);
292 height = gdk_pixbuf_get_height (pixbuf: src);
293
294 g_assert (width <= gdk_pixbuf_get_width (dst));
295 g_assert (height <= gdk_pixbuf_get_height (dst));
296
297 src_stride = gdk_pixbuf_get_rowstride (pixbuf: src);
298 src_data = gdk_pixbuf_get_pixels (pixbuf: src);
299
300 dst_data = gdk_pixbuf_get_pixels (pixbuf: dst);
301 dst_stride = gdk_pixbuf_get_rowstride (pixbuf: dst);
302
303 for (y = 0; y < height; y++)
304 {
305 src_row = src_data + src_stride * y;
306 dst_row = dst_data + dst_stride * y;
307 for (x = 0; x < width; x++)
308 {
309 dst_row[to_plane] = src_row[from_plane];
310 src_row += 4;
311 dst_row += 4;
312 }
313 }
314}
315
316GdkPixbuf *
317gtk_make_symbolic_pixbuf_from_data (const char *file_data,
318 gsize file_len,
319 int width,
320 int height,
321 double scale,
322 const char *debug_output_basename,
323 GError **error)
324
325{
326 const char *r_string = "rgb(255,0,0)";
327 const char *g_string = "rgb(0,255,0)";
328 char *icon_width_str;
329 char *icon_height_str;
330 GdkPixbuf *loaded;
331 GdkPixbuf *pixbuf = NULL;
332 int plane;
333 int icon_width, icon_height;
334 char *escaped_file_data;
335
336 /* Fetch size from the original icon */
337 {
338 GInputStream *stream = g_memory_input_stream_new_from_data (data: file_data, len: file_len, NULL);
339 GdkPixbuf *reference = gdk_pixbuf_new_from_stream (stream, NULL, error);
340
341 g_object_unref (object: stream);
342
343 if (!reference)
344 return NULL;
345
346 icon_width = gdk_pixbuf_get_width (pixbuf: reference);
347 icon_height = gdk_pixbuf_get_height (pixbuf: reference);
348 g_object_unref (object: reference);
349 }
350
351 escaped_file_data = g_base64_encode (data: (guchar *) file_data, len: file_len);
352 icon_width_str = g_strdup_printf (format: "%d", icon_width);
353 icon_height_str = g_strdup_printf (format: "%d", icon_height);
354
355 if (width == 0)
356 width = icon_width * scale;
357 if (height == 0)
358 height = icon_height * scale;
359
360 for (plane = 0; plane < 3; plane++)
361 {
362 /* Here we render the svg with all colors solid, this should
363 * always make the alpha channel the same and it should match
364 * the final alpha channel for all possible renderings. We
365 * Just use it as-is for final alpha.
366 *
367 * For the 3 non-fg colors, we render once each with that
368 * color as red, and every other color as green. The resulting
369 * red will describe the amount of that color is in the
370 * opaque part of the color. We store these as the rgb
371 * channels, with the color of the fg being implicitly
372 * the "rest", as all color fractions should add up to 1.
373 */
374 loaded = load_symbolic_svg (escaped_file_data, width, height,
375 icon_width_str,
376 icon_height_str,
377 fg_string: g_string,
378 success_color_string: plane == 0 ? r_string : g_string,
379 warning_color_string: plane == 1 ? r_string : g_string,
380 error_color_string: plane == 2 ? r_string : g_string,
381 error);
382 if (loaded == NULL)
383 goto out;
384
385 if (debug_output_basename)
386 {
387 char *filename;
388
389 filename = g_strdup_printf (format: "%s.debug%d.png", debug_output_basename, plane);
390 g_print (format: "Writing %s\n", filename);
391 gdk_pixbuf_save (pixbuf: loaded, filename, type: "png", NULL, NULL);
392 g_free (mem: filename);
393 }
394
395 if (pixbuf == NULL)
396 {
397 pixbuf = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, TRUE, bits_per_sample: 8,
398 width: gdk_pixbuf_get_width (pixbuf: loaded),
399 height: gdk_pixbuf_get_height (pixbuf: loaded));
400 gdk_pixbuf_fill (pixbuf, pixel: 0);
401 }
402
403 if (plane == 0)
404 extract_plane (src: loaded, dst: pixbuf, from_plane: 3, to_plane: 3);
405
406 extract_plane (src: loaded, dst: pixbuf, from_plane: 0, to_plane: plane);
407
408 g_object_unref (object: loaded);
409 }
410
411 g_free (mem: escaped_file_data);
412
413out:
414 g_free (mem: icon_width_str);
415 g_free (mem: icon_height_str);
416
417 return pixbuf;
418}
419
420GdkPixbuf *
421gtk_make_symbolic_pixbuf_from_resource (const char *path,
422 int width,
423 int height,
424 double scale,
425 GError **error)
426{
427 GBytes *bytes;
428 const char *data;
429 gsize size;
430 GdkPixbuf *pixbuf;
431
432 bytes = g_resources_lookup_data (path, lookup_flags: G_RESOURCE_LOOKUP_FLAGS_NONE, error);
433 if (bytes == NULL)
434 return NULL;
435
436 data = g_bytes_get_data (bytes, size: &size);
437
438 pixbuf = gtk_make_symbolic_pixbuf_from_data (file_data: data, file_len: size, width, height, scale, NULL, error);
439
440 g_bytes_unref (bytes);
441
442 return pixbuf;
443}
444
445GdkPixbuf *
446gtk_make_symbolic_pixbuf_from_path (const char *path,
447 int width,
448 int height,
449 double scale,
450 GError **error)
451{
452 char *data;
453 gsize size;
454 GdkPixbuf *pixbuf;
455
456 if (!g_file_get_contents (filename: path, contents: &data, length: &size, error))
457 return NULL;
458
459 pixbuf = gtk_make_symbolic_pixbuf_from_data (file_data: data, file_len: size, width, height, scale, NULL, error);
460
461 g_free (mem: data);
462
463 return pixbuf;
464}
465
466GdkPixbuf *
467gtk_make_symbolic_pixbuf_from_file (GFile *file,
468 int width,
469 int height,
470 double scale,
471 GError **error)
472{
473 char *data;
474 gsize size;
475 GdkPixbuf *pixbuf;
476
477 if (!g_file_load_contents (file, NULL, contents: &data, length: &size, NULL, error))
478 return NULL;
479
480 pixbuf = gtk_make_symbolic_pixbuf_from_data (file_data: data, file_len: size, width, height, scale, NULL, error);
481
482 g_free (mem: data);
483
484 return pixbuf;
485}
486
487GdkTexture *
488gtk_load_symbolic_texture_from_resource (const char *path)
489{
490 return gdk_texture_new_from_resource (resource_path: path);
491}
492
493GdkTexture *
494gtk_make_symbolic_texture_from_resource (const char *path,
495 int width,
496 int height,
497 double scale,
498 GError **error)
499{
500 GdkPixbuf *pixbuf;
501 GdkTexture *texture = NULL;
502
503 pixbuf = gtk_make_symbolic_pixbuf_from_resource (path, width, height, scale, error);
504 if (pixbuf)
505 {
506 texture = gdk_texture_new_for_pixbuf (pixbuf);
507 g_object_unref (object: pixbuf);
508 }
509
510 return texture;
511}
512
513GdkTexture *
514gtk_load_symbolic_texture_from_file (GFile *file)
515{
516 GdkPixbuf *pixbuf;
517 GdkTexture *texture;
518 GInputStream *stream;
519
520 stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
521 if (stream == NULL)
522 return NULL;
523
524 pixbuf = _gdk_pixbuf_new_from_stream (stream, NULL, NULL);
525 g_object_unref (object: stream);
526 if (pixbuf == NULL)
527 return NULL;
528
529 texture = gdk_texture_new_for_pixbuf (pixbuf);
530 g_object_unref (object: pixbuf);
531
532 return texture;
533}
534
535GdkTexture *
536gtk_make_symbolic_texture_from_file (GFile *file,
537 int width,
538 int height,
539 double scale,
540 GError **error)
541{
542 GdkPixbuf *pixbuf;
543 GdkTexture *texture;
544
545 pixbuf = gtk_make_symbolic_pixbuf_from_file (file, width, height, scale, error);
546 texture = gdk_texture_new_for_pixbuf (pixbuf);
547 g_object_unref (object: pixbuf);
548
549 return texture;
550}
551
552typedef struct {
553 int scale_factor;
554} LoaderData;
555
556static void
557on_loader_size_prepared (GdkPixbufLoader *loader,
558 int width,
559 int height,
560 gpointer user_data)
561{
562 LoaderData *loader_data = user_data;
563 GdkPixbufFormat *format;
564
565 /* Let the regular icon helper code path handle non-scalable images */
566 format = gdk_pixbuf_loader_get_format (loader);
567 if (!gdk_pixbuf_format_is_scalable (format))
568 {
569 loader_data->scale_factor = 1;
570 return;
571 }
572
573 gdk_pixbuf_loader_set_size (loader,
574 width: width * loader_data->scale_factor,
575 height: height * loader_data->scale_factor);
576}
577
578GdkPaintable *
579gdk_paintable_new_from_bytes_scaled (GBytes *bytes,
580 int scale_factor)
581{
582 LoaderData loader_data;
583 GdkTexture *texture;
584 GdkPaintable *paintable;
585
586 loader_data.scale_factor = scale_factor;
587
588 if (gdk_texture_can_load (bytes))
589 {
590 /* We know these formats can't be scaled */
591 texture = gdk_texture_new_from_bytes (bytes, NULL);
592 if (texture == NULL)
593 return NULL;
594 }
595 else
596 {
597 GdkPixbufLoader *loader;
598 gboolean success;
599
600 loader = gdk_pixbuf_loader_new ();
601 g_signal_connect (loader, "size-prepared",
602 G_CALLBACK (on_loader_size_prepared), &loader_data);
603
604 success = gdk_pixbuf_loader_write_bytes (loader, buffer: bytes, NULL);
605 /* close even when writing failed */
606 success &= gdk_pixbuf_loader_close (loader, NULL);
607
608 if (!success)
609 return NULL;
610
611 texture = gdk_texture_new_for_pixbuf (pixbuf: gdk_pixbuf_loader_get_pixbuf (loader));
612 g_object_unref (object: loader);
613 }
614
615 if (loader_data.scale_factor != 1)
616 paintable = gtk_scaler_new (paintable: GDK_PAINTABLE (ptr: texture), scale_factor: loader_data.scale_factor);
617 else
618 paintable = g_object_ref ((GdkPaintable *)texture);
619
620 g_object_unref (object: texture);
621
622 return paintable;
623}
624
625GdkPaintable *
626gdk_paintable_new_from_path_scaled (const char *path,
627 int scale_factor)
628{
629 char *contents;
630 gsize length;
631 GBytes *bytes;
632 GdkPaintable *paintable;
633
634 if (!g_file_get_contents (filename: path, contents: &contents, length: &length, NULL))
635 return NULL;
636
637 bytes = g_bytes_new_take (data: contents, size: length);
638
639 paintable = gdk_paintable_new_from_bytes_scaled (bytes, scale_factor);
640
641 g_bytes_unref (bytes);
642
643 return paintable;
644}
645
646GdkPaintable *
647gdk_paintable_new_from_resource_scaled (const char *path,
648 int scale_factor)
649{
650 GBytes *bytes;
651 GdkPaintable *paintable;
652
653 bytes = g_resources_lookup_data (path, lookup_flags: G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
654 if (!bytes)
655 return NULL;
656
657 paintable = gdk_paintable_new_from_bytes_scaled (bytes, scale_factor);
658 g_bytes_unref (bytes);
659
660 return paintable;
661}
662
663GdkPaintable *
664gdk_paintable_new_from_file_scaled (GFile *file,
665 int scale_factor)
666{
667 GBytes *bytes;
668 GdkPaintable *paintable;
669
670 bytes = g_file_load_bytes (file, NULL, NULL, NULL);
671 if (!bytes)
672 return NULL;
673
674 paintable = gdk_paintable_new_from_bytes_scaled (bytes, scale_factor);
675
676 g_bytes_unref (bytes);
677
678 return paintable;
679}
680

source code of gtk/gtk/gdkpixbufutils.c