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 | |
25 | static GdkPixbuf * |
26 | load_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 | |
76 | static void |
77 | size_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 | */ |
94 | GdkPixbuf * |
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 | |
116 | static void |
117 | size_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 | |
148 | GdkPixbuf * |
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 | |
175 | GdkPixbuf * |
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 | */ |
187 | GdkPixbuf * |
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 | |
205 | GdkPixbuf * |
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 | |
212 | GdkPixbuf * |
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 | |
233 | static GdkPixbuf * |
234 | load_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 | |
280 | static void |
281 | (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 | |
316 | GdkPixbuf * |
317 | gtk_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 | |
413 | out: |
414 | g_free (mem: icon_width_str); |
415 | g_free (mem: icon_height_str); |
416 | |
417 | return pixbuf; |
418 | } |
419 | |
420 | GdkPixbuf * |
421 | gtk_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 | |
445 | GdkPixbuf * |
446 | gtk_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 | |
466 | GdkPixbuf * |
467 | gtk_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 | |
487 | GdkTexture * |
488 | gtk_load_symbolic_texture_from_resource (const char *path) |
489 | { |
490 | return gdk_texture_new_from_resource (resource_path: path); |
491 | } |
492 | |
493 | GdkTexture * |
494 | gtk_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 | |
513 | GdkTexture * |
514 | gtk_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 | |
535 | GdkTexture * |
536 | gtk_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 | |
552 | typedef struct { |
553 | int scale_factor; |
554 | } LoaderData; |
555 | |
556 | static void |
557 | on_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 | |
578 | GdkPaintable * |
579 | gdk_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 | |
625 | GdkPaintable * |
626 | gdk_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 | |
646 | GdkPaintable * |
647 | gdk_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 | |
663 | GdkPaintable * |
664 | gdk_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 | |