Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
icon.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
7
8#include <assert.h>
9#include <cairo.h>
10#include <gdk-pixbuf/gdk-pixbuf.h>
11#include <stdbool.h>
12#include <string.h>
13#include <math.h>
14
15#include "log.h"
16#include "icon.h"
17#include "settings.h"
18#include "utils.h"
19#include "icon-lookup.h"
20
27 const unsigned char *pixels_p,
28 unsigned char *pixels_c,
29 size_t rowstride_p,
30 size_t rowstride_c,
31 int width,
32 int height,
33 int n_channels)
34{
35#if G_BYTE_ORDER == G_LITTLE_ENDIAN
36 static const size_t CAIRO_B = 0;
37 static const size_t CAIRO_G = 1;
38 static const size_t CAIRO_R = 2;
39 static const size_t CAIRO_A = 3;
40#elif G_BYTE_ORDER == G_BIG_ENDIAN
41 static const size_t CAIRO_A = 0;
42 static const size_t CAIRO_R = 1;
43 static const size_t CAIRO_G = 2;
44 static const size_t CAIRO_B = 3;
45#elif G_BYTE_ORDER == G_PDP_ENDIAN
46 static const size_t CAIRO_R = 0;
47 static const size_t CAIRO_A = 1;
48 static const size_t CAIRO_B = 2;
49 static const size_t CAIRO_G = 3;
50#else
51// GLib doesn't support any other endiannesses
52#error Unsupported Endianness
53#endif
54
55 assert(pixels_p);
56 assert(pixels_c);
57 assert(width > 0);
58 assert(height > 0);
59
60 if (n_channels == 3) {
61 for (int h = 0; h < height; h++) {
62 unsigned char *iter_c = pixels_c + h * rowstride_c;
63 const unsigned char *iter_p = pixels_p + h * rowstride_p;
64 for (int w = 0; w < width; w++) {
65 iter_c[CAIRO_R] = iter_p[0];
66 iter_c[CAIRO_G] = iter_p[1];
67 iter_c[CAIRO_B] = iter_p[2];
68 iter_c[CAIRO_A] = 0xff;
69 iter_c += 4;
70 iter_p += n_channels;
71 }
72 }
73 } else {
74 for (int h = 0; h < height; h++) {
75 unsigned char *iter_c = pixels_c + h * rowstride_c;
76 const unsigned char *iter_p = pixels_p + h * rowstride_p;
77 for (int w = 0; w < width; w++) {
78 double alpha_factor = iter_p[3] / (double)0xff;
79 iter_c[CAIRO_R] = (unsigned char)(iter_p[0] * alpha_factor + .5);
80 iter_c[CAIRO_G] = (unsigned char)(iter_p[1] * alpha_factor + .5);
81 iter_c[CAIRO_B] = (unsigned char)(iter_p[2] * alpha_factor + .5);
82 iter_c[CAIRO_A] = iter_p[3];
83 iter_c += 4;
84 iter_p += n_channels;
85 }
86 }
87
88 }
89}
90
91int get_icon_width(cairo_surface_t *icon, double scale) {
92 return round(cairo_image_surface_get_width(icon) / scale);
93}
94
95int get_icon_height(cairo_surface_t *icon, double scale) {
96 return round(cairo_image_surface_get_height(icon) / scale);
97}
98
99cairo_surface_t *gdk_pixbuf_to_cairo_surface(GdkPixbuf *pixbuf)
100{
101 if (!pixbuf) {
102 return NULL;
103 }
104
105 int width = gdk_pixbuf_get_width(pixbuf);
106 int height = gdk_pixbuf_get_height(pixbuf);
107
108 cairo_format_t fmt = gdk_pixbuf_get_has_alpha(pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24;
109 cairo_surface_t *icon_surface = cairo_image_surface_create(fmt, width, height);
110
111 /* Copy pixel data from pixbuf to surface */
112 cairo_surface_flush(icon_surface);
113 pixbuf_data_to_cairo_data(gdk_pixbuf_read_pixels(pixbuf),
114 cairo_image_surface_get_data(icon_surface),
115 gdk_pixbuf_get_rowstride(pixbuf),
116 cairo_format_stride_for_width(fmt, width),
117 gdk_pixbuf_get_width(pixbuf),
118 gdk_pixbuf_get_height(pixbuf),
119 gdk_pixbuf_get_n_channels(pixbuf));
120 cairo_surface_mark_dirty(icon_surface);
121
122 return icon_surface;
123}
124
134static bool icon_size_clamp(int *w, int *h, int min_size, int max_size) {
135 int _w = *w, _h = *h;
136 int landscape = _w > _h;
137 int orig_larger = landscape ? _w : _h;
138 double larger = orig_larger;
139 double smaller = landscape ? _h : _w;
140 if (min_size && smaller < min_size) {
141 larger = larger / smaller * min_size;
142 smaller = min_size;
143 }
144 if (max_size && larger > max_size) {
145 smaller = smaller / larger * max_size;
146 larger = max_size;
147 }
148 if ((int) larger != orig_larger) {
149 *w = (int) (landscape ? larger : smaller);
150 *h = (int) (landscape ? smaller : larger);
151 return TRUE;
152 }
153 return FALSE;
154}
155
169static GdkPixbuf *icon_pixbuf_scale_to_size(GdkPixbuf *pixbuf, double dpi_scale, int min_size, int max_size)
170{
171 ASSERT_OR_RET(pixbuf, NULL);
172
173 int w = gdk_pixbuf_get_width(pixbuf);
174 int h = gdk_pixbuf_get_height(pixbuf);
175
176 // TODO immediately rescale icon upon scale changes
177 if(icon_size_clamp(&w, &h, min_size, max_size)) {
178 w = round(w * dpi_scale);
179 h = round(h * dpi_scale);
180 }
181 GdkPixbuf *scaled = gdk_pixbuf_scale_simple(
182 pixbuf,
183 w,
184 h,
185 GDK_INTERP_BILINEAR);
186 g_object_unref(pixbuf);
187 pixbuf = scaled;
188 return pixbuf;
189}
190
191static char *get_id_from_data(const uint8_t *data_pb, size_t width, size_t height, size_t pixelstride, size_t rowstride)
192{
193 /* To calculate a checksum of the current image, we have to remove
194 * all excess spacers, so that our checksummed memory only contains
195 * real data. */
196
197 size_t data_chk_len = pixelstride * width * height;
198 unsigned char *data_chk = g_malloc(data_chk_len);
199 size_t rowstride_short = pixelstride * width;
200
201 for (int i = 0; i < height; i++) {
202 memcpy(data_chk + (i*rowstride_short),
203 data_pb + (i*rowstride),
204 rowstride_short);
205 }
206
207 char *id = g_compute_checksum_for_data(G_CHECKSUM_MD5, data_chk, data_chk_len);
208 g_free(data_chk);
209
210 return id;
211}
212
213GdkPixbuf *get_pixbuf_from_file(const char *filename, char **id, int min_size, int max_size, double scale)
214{
215 GError *error = NULL;
216 gint w, h;
217
218 ASSERT_OR_RET(filename, NULL);
219 ASSERT_OR_RET(id, NULL);
220
221 if (!gdk_pixbuf_get_file_info (filename, &w, &h)) {
222 LOG_W("Failed to load image info for %s", STR_NN(filename));
223 return NULL;
224 }
225 GdkPixbuf *pixbuf = NULL;
226 // TODO immediately rescale icon upon scale changes
227 icon_size_clamp(&w, &h, min_size, max_size);
228 pixbuf = gdk_pixbuf_new_from_file_at_scale(filename,
229 round(w * scale),
230 round(h * scale),
231 TRUE,
232 &error);
233
234 if (error) {
235 LOG_W("%s", error->message);
236 g_error_free(error);
237 }
238
239 const uint8_t *data = gdk_pixbuf_get_pixels(pixbuf);
240 size_t rowstride = gdk_pixbuf_get_rowstride(pixbuf);
241 size_t n_channels = gdk_pixbuf_get_n_channels(pixbuf);
242 size_t bits_per_sample = gdk_pixbuf_get_bits_per_sample(pixbuf);
243 size_t pixelstride = (n_channels * bits_per_sample + 7)/8;
244
245 *id = get_id_from_data(data, w, h, pixelstride, rowstride);
246 return pixbuf;
247}
248
249char *get_path_from_icon_name(const char *iconname, int size)
250{
251 if (STR_EMPTY(iconname))
252 return NULL;
253
254 if (g_str_has_prefix(iconname, "file://")) {
255 char *uri_path = g_filename_from_uri(iconname, NULL, NULL);
256 if (STR_EMPTY(uri_path)) {
257 LOG_W("Invalid file uri '%s'", iconname);
258 return NULL;
259 }
260 return uri_path;
261 } else if (is_like_path(iconname)) {
262 return g_strdup(iconname);
263 } else if (settings.enable_recursive_icon_lookup) {
264 char *path = find_icon_path(iconname, size);
265 if (STR_EMPTY(path))
266 LOG_W("Icon '%s' not found in themes", iconname);
267 else
268 LOG_I("Found icon '%s' at %s", iconname, STR_NN(path));
269 return path;
270 }
271
272 // Search icon_path
273 const char *suffixes[] = { ".svg", ".svgz", ".png", ".xpm", NULL };
274 char *start = settings.icon_path, *end, *current_folder, *maybe_icon_path, *path = NULL;
275
276 do {
277 end = strchr(start, ':');
278 if (!end) end = strchr(settings.icon_path, '\0'); /* end = end of string */
279
280 current_folder = string_to_path(g_strndup(start, end - start));
281
282 for (const char **suf = suffixes; *suf; suf++) {
283 gchar *name_with_extension = g_strconcat(iconname, *suf, NULL);
284 maybe_icon_path = g_build_filename(current_folder, name_with_extension, NULL);
285 if (is_readable_file(maybe_icon_path)) {
286 path = g_strdup(maybe_icon_path);
287 }
288 g_free(name_with_extension);
289 g_free(maybe_icon_path);
290
291 if (path) break;
292 }
293 g_free(current_folder);
294
295 if (path) break;
296 start = end + 1;
297 } while (STR_FULL(end));
298
299 if (STR_EMPTY(path))
300 LOG_W("Icon '%s' not found in icon_path", iconname);
301 else
302 LOG_I("Found icon '%s' at %s", iconname, path);
303
304 return path;
305}
306
307static void icon_destroy(guchar *pixels, gpointer data)
308{
309 (void)data;
310 g_free(pixels);
311}
312
313GdkPixbuf *icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size)
314{
315 ASSERT_OR_RET(data, NULL);
316 ASSERT_OR_RET(id, NULL);
317
318 if (!STR_EQ("(iiibiiay)", g_variant_get_type_string(data))) {
319 LOG_W("Invalid data for pixbuf given.");
320 return NULL;
321 }
322
323 /* The raw image is a big array of char data.
324 *
325 * The image is serialised rowwise pixel by pixel. The rows are aligned
326 * by a spacer full of garbage. The overall data length of data + garbage
327 * is called the rowstride.
328 *
329 * Mind the missing spacer at the last row.
330 *
331 * len: |<--------------rowstride---------------->|
332 * len: |<-width*pixelstride->|
333 * row 1: | data for row 1 | spacer of garbage |
334 * row 2: | data for row 2 | spacer of garbage |
335 * | . | spacer of garbage |
336 * | . | spacer of garbage |
337 * | . | spacer of garbage |
338 * row n-1: | data for row n-1 | spacer of garbage |
339 * row n: | data for row n |
340 */
341
342 GdkPixbuf *pixbuf = NULL;
343 GVariant *data_variant = NULL;
344 unsigned char *data_pb;
345
346 gsize len_expected;
347 gsize len_actual;
348 gsize pixelstride;
349
350 int width;
351 int height;
352 int rowstride;
353 int has_alpha;
354 int bits_per_sample;
355 int n_channels;
356
357 g_variant_get(data,
358 "(iiibii@ay)",
359 &width,
360 &height,
361 &rowstride,
362 &has_alpha,
363 &bits_per_sample,
364 &n_channels,
365 &data_variant);
366
367 // note: (A+7)/8 rounds up A to the next byte boundary
368 pixelstride = (n_channels * bits_per_sample + 7)/8;
369 len_expected = (height - 1) * rowstride + width * pixelstride;
370 len_actual = g_variant_get_size(data_variant);
371
372 if (len_actual != len_expected) {
373 LOG_W("Expected image data to be of length %" G_GSIZE_FORMAT
374 " but got a length of %" G_GSIZE_FORMAT,
375 len_expected,
376 len_actual);
377 g_variant_unref(data_variant);
378 return NULL;
379 }
380
381 // g_memdup is deprecated in glib 2.67.4 and higher.
382 // g_memdup2 is a safer alternative
383#if GLIB_CHECK_VERSION(2,67,3)
384 data_pb = (guchar *) g_memdup2(g_variant_get_data(data_variant), len_actual);
385#else
386 data_pb = (guchar *) g_memdup(g_variant_get_data(data_variant), len_actual);
387#endif
388
389 pixbuf = gdk_pixbuf_new_from_data(data_pb,
390 GDK_COLORSPACE_RGB,
391 has_alpha,
392 bits_per_sample,
393 width,
394 height,
395 rowstride,
396 icon_destroy,
397 data_pb);
398 if (!pixbuf) {
399 /* Dear user, I'm sorry, I'd like to give you a more specific
400 * error message. But sadly, I can't */
401 LOG_W("Cannot serialise raw icon data into pixbuf.");
402 return NULL;
403 }
404
405
406 *id = get_id_from_data(data_pb, width, height, pixelstride, rowstride);
407
408 g_variant_unref(data_variant);
409
410 pixbuf = icon_pixbuf_scale_to_size(pixbuf, dpi_scale, min_size, max_size);
411
412 return pixbuf;
413}
char * find_icon_path(const char *name, int size)
Find icon of specified size in the default theme or an inherited theme.
Recursive icon lookup in theme directories.
static GdkPixbuf * icon_pixbuf_scale_to_size(GdkPixbuf *pixbuf, double dpi_scale, int min_size, int max_size)
Scales the given GdkPixbuf to a given size.
Definition icon.c:169
static bool icon_size_clamp(int *w, int *h, int min_size, int max_size)
Scales the given image dimensions if necessary according to the settings.
Definition icon.c:134
static void pixbuf_data_to_cairo_data(const unsigned char *pixels_p, unsigned char *pixels_c, size_t rowstride_p, size_t rowstride_c, int width, int height, int n_channels)
Reassemble the data parts of a GdkPixbuf into a cairo_surface_t's data field.
Definition icon.c:26
int get_icon_width(cairo_surface_t *icon, double scale)
Get the unscaled icon width.
Definition icon.c:91
char * get_path_from_icon_name(const char *iconname, int size)
Retrieve a path from an icon name.
Definition icon.c:249
GdkPixbuf * icon_get_for_data(GVariant *data, char **id, double dpi_scale, int min_size, int max_size)
Convert a GVariant like described in GdkPixbuf, scaled according to settings.
Definition icon.c:313
GdkPixbuf * get_pixbuf_from_file(const char *filename, char **id, int min_size, int max_size, double scale)
Retrieve an icon by its full filepath, scaled according to settings.
Definition icon.c:213
int get_icon_height(cairo_surface_t *icon, double scale)
Get the unscaled icon height, see get_icon_width.
Definition icon.c:95
Notification images loading.
Logging subsystem and helpers.
Type definitions for settings.
bool is_like_path(const char *string)
Check if the strings looks like a path.
Definition utils.c:495
bool is_readable_file(const char *const path)
Check if file is readable.
Definition utils.c:437
char * string_to_path(char *string)
Replace tilde and path-specific values with it's equivalents.
Definition utils.c:178
String, time and other various helpers.
#define STR_EMPTY(s)
Test if a string is NULL or empty.
Definition utils.h:20
#define STR_EQ(a, b)
Test if string a and b contain the same chars.
Definition utils.h:26
#define STR_FULL(s)
Test if a string is non-NULL and not empty.
Definition utils.h:23
#define STR_NN(s)
Get a non null string from a possibly null one.
Definition utils.h:35
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42