Simplify vflist_get_formatted()
[geeqie.git] / src / pixbuf-util.cc
1 /*
2  * Copyright (C) 2004 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "pixbuf-util.h"
23
24 #include <algorithm>
25 #include <cmath>
26 #include <cstdlib>
27 #include <cstring>
28 #include <utility>
29
30 #include <cairo.h>
31 #include <gio/gio.h>
32 #include <glib-object.h>
33 #include <pango/pangocairo.h>
34
35 #include <config.h>
36
37 #include "debug.h"
38 #include "exif.h"
39 #include "filedata.h"
40 #include "main-defines.h"
41 #include "typedefs.h"
42 #include "ui-fileops.h"
43
44
45 /*
46  *-----------------------------------------------------------------------------
47  * png save
48  *-----------------------------------------------------------------------------
49  */
50
51 gboolean pixbuf_to_file_as_png(GdkPixbuf *pixbuf, const gchar *filename)
52 {
53         GError *error = nullptr;
54         gboolean ret;
55
56         if (!pixbuf || !filename) return FALSE;
57
58         ret = gdk_pixbuf_save(pixbuf, filename, "png", &error,
59                               "tEXt::Software", GQ_APPNAME " " VERSION, NULL);
60
61         if (error)
62                 {
63                 log_printf("Error saving png file: %s\n", error->message);
64                 g_error_free(error);
65                 }
66
67         return ret;
68 }
69
70 /*
71  *-----------------------------------------------------------------------------
72  * jpeg save
73  *-----------------------------------------------------------------------------
74  */
75
76 #pragma GCC diagnostic push
77 #pragma GCC diagnostic ignored "-Wunused-function"
78 gboolean pixbuf_to_file_as_jpg_unused(GdkPixbuf *pixbuf, const gchar *filename, gint quality)
79 {
80         GError *error = nullptr;
81         gchar *qbuf;
82         gboolean ret;
83
84         if (!pixbuf || !filename) return FALSE;
85
86         if (quality == -1) quality = 75;
87         if (quality < 1 || quality > 100)
88                 {
89                 log_printf("Jpeg not saved, invalid quality %d\n", quality);
90                 return FALSE;
91                 }
92
93         qbuf = g_strdup_printf("%d", quality);
94         ret = gdk_pixbuf_save(pixbuf, filename, "jpeg", &error, "quality", qbuf, NULL);
95         g_free(qbuf);
96
97         if (error)
98                 {
99                 log_printf("Error saving jpeg to %s\n%s\n", filename, error->message);
100                 g_error_free(error);
101                 }
102
103         return ret;
104 }
105 #pragma GCC diagnostic pop
106
107 /*
108  *-----------------------------------------------------------------------------
109  * pixbuf from inline
110  *-----------------------------------------------------------------------------
111  */
112
113 struct PixbufInline
114 {
115         const gchar *key;
116         const gchar *data;
117 };
118
119 static PixbufInline inline_pixbuf_data[] = {
120         {  PIXBUF_INLINE_ARCHIVE,                "gq-icon-archive-file" },
121         {  PIXBUF_INLINE_BROKEN,                 "gq-icon-broken" },
122         {  PIXBUF_INLINE_COLLECTION,             "gq-icon-collection" },
123         {  PIXBUF_INLINE_ICON_180,               "gq-icon-rotate-180" },
124         {  PIXBUF_INLINE_ICON_BOOK,              "gq-icon-book" },
125         {  PIXBUF_INLINE_ICON_CONFIG,            "gq-icon-config" },
126         {  PIXBUF_INLINE_ICON_DRAW_RECTANGLE,    "gq-icon-draw-rectangle" },
127         {  PIXBUF_INLINE_ICON_EXIF,              "gq-icon-exif" },
128         {  PIXBUF_INLINE_ICON_EXPOSURE,          "gq-icon-exposure" },
129         {  PIXBUF_INLINE_ICON_FLOAT,             "gq-icon-float" },
130         {  PIXBUF_INLINE_ICON,                   "gqview-icon" },
131         {  PIXBUF_INLINE_ICON_GRAYSCALE,         "gq-icon-grayscale" },
132         {  PIXBUF_INLINE_ICON_HEIF,              "gq-icon-heic" },
133         {  PIXBUF_INLINE_ICON_HIDETOOLS,         "gq-icon-hidetools" },
134         {  PIXBUF_INLINE_ICON_MAINTENANCE,       "gq-icon-maintenance" },
135         {  PIXBUF_INLINE_ICON_MARKS,             "gq-icon-marks" },
136         {  PIXBUF_INLINE_ICON_MOVE,              "gq-icon-move" },
137         {  PIXBUF_INLINE_ICON_ORIGINAL,          "gq-icon-original" },
138         {  PIXBUF_INLINE_ICON_PANORAMA,          "gq-icon-panorama" },
139         {  PIXBUF_INLINE_ICON_PDF,               "gq-icon-pdf" },
140         {  PIXBUF_INLINE_ICON_PROPERTIES,        "gq-icon-properties" },
141         {  PIXBUF_INLINE_ICON_RENAME,            "gq-icon-rename" },
142         {  PIXBUF_INLINE_ICON_SELECT_ALL,        "gq-icon-select-all" },
143         {  PIXBUF_INLINE_ICON_SELECT_INVERT,     "gq-icon-select-invert" },
144         {  PIXBUF_INLINE_ICON_SELECT_NONE,       "gq-icon-select-none" },
145         {  PIXBUF_INLINE_ICON_SELECT_RECTANGLE,  "gq-icon-select-rectangle" },
146         {  PIXBUF_INLINE_ICON_SORT,              "gq-icon-sort" },
147         {  PIXBUF_INLINE_ICON_THUMB,             "gq-icon-thumb" },
148         {  PIXBUF_INLINE_ICON_TOOLS,             "gq-icon-tools" },
149         {  PIXBUF_INLINE_ICON_VIEW,              "gq-icon-view" },
150         {  PIXBUF_INLINE_ICON_ZOOMFILLHOR,       "gq-icon-zoomfillhor" },
151         {  PIXBUF_INLINE_ICON_ZOOMFILLVERT,      "gq-icon-zoomfillvert" },
152         {  PIXBUF_INLINE_LOGO,                   "geeqie-logo" },
153         {  PIXBUF_INLINE_METADATA,               "gq-icon-metadata" },
154         {  PIXBUF_INLINE_SCROLLER,               "gq-scroller" },
155         {  PIXBUF_INLINE_SPLIT_PANE_SYNC,        "gq-icon-split-pane-sync" },
156         {  PIXBUF_INLINE_UNKNOWN,                "gq-icon-unknown" },
157         {  PIXBUF_INLINE_VIDEO,                  "gq-icon-video" },
158         {  nullptr,                              nullptr }
159 };
160
161 GdkPixbuf *pixbuf_inline(const gchar *key)
162 {
163         gboolean dark = FALSE;
164         gchar *file_name = nullptr;
165         gchar *path;
166         gchar *theme_name;
167         GdkPixbuf *icon_pixbuf;
168         GError *error = nullptr;
169         GInputStream *in_stream;
170         gint i;
171         GtkSettings *settings;
172
173         if (!key) return nullptr;
174
175         settings = gtk_settings_get_default();
176         g_object_get(settings, "gtk-theme-name", &theme_name, nullptr);
177         dark = g_str_has_suffix(theme_name, "dark");
178         g_free(theme_name);
179
180         i = 0;
181         while (inline_pixbuf_data[i].key)
182                 {
183                 if (strcmp(inline_pixbuf_data[i].key, key) == 0)
184                         {
185                         file_name = g_strconcat(inline_pixbuf_data[i].data, dark ? "-dark" : "", ".png", nullptr);
186                         path = g_build_filename(GQ_RESOURCE_PATH_ICONS, file_name, nullptr);
187                         g_free(file_name);
188
189                         in_stream = g_resources_open_stream(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
190                         g_free(path);
191
192                         if (error)
193                                 {
194                                 g_error_free(error);
195                                 error = nullptr;
196
197                                 file_name = g_strconcat(inline_pixbuf_data[i].data, ".png", nullptr);
198                                 path = g_build_filename(GQ_RESOURCE_PATH_ICONS, file_name, nullptr);
199                                 g_free(file_name);
200
201                                 in_stream = g_resources_open_stream(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
202                                 g_free(path);
203                                 }
204
205                         if (error)
206                                 {
207                                 log_printf("warning: inline pixbuf error: %s", error->message);
208                                 g_error_free(error);
209                                 g_object_unref(in_stream);
210                                 return nullptr;
211                                 }
212
213                         icon_pixbuf = gdk_pixbuf_new_from_stream(in_stream, nullptr, &error);
214                         g_object_unref(in_stream);
215
216                         if (error)
217                                 {
218                                 log_printf("warning: inline pixbuf error: %s", error->message);
219                                 g_error_free(error);
220                                 return nullptr;
221                                 }
222
223                         return icon_pixbuf;
224                         }
225                 i++;
226                 }
227
228         log_printf("warning: inline pixbuf key \"%s\" not found.\n", key);
229
230         return nullptr;
231 }
232
233 static void register_stock_icon(const gchar *key, GdkPixbuf *pixbuf)
234 {
235         static GtkIconFactory *icon_factory = nullptr;
236         GtkIconSet *icon_set;
237
238         if (!icon_factory)
239                 {
240                 icon_factory = gtk_icon_factory_new();
241                 gtk_icon_factory_add_default(icon_factory);
242                 }
243
244         icon_set = gtk_icon_set_new_from_pixbuf(pixbuf);
245         gtk_icon_factory_add(icon_factory, key, icon_set);
246 }
247
248
249 void pixbuf_inline_register_stock_icons()
250 {
251         gint i;
252
253         i = 0;
254         while (inline_pixbuf_data[i].key)
255                 {
256                 register_stock_icon(inline_pixbuf_data[i].key, pixbuf_inline(inline_pixbuf_data[i].key));
257                 i++;
258                 }
259 }
260
261 gboolean register_theme_icon_as_stock(const gchar *key, const gchar *icon)
262 {
263         GtkIconTheme *icon_theme;
264         GdkPixbuf *pixbuf;
265         GError *error = nullptr;
266
267         icon_theme = gtk_icon_theme_get_default();
268
269         if (gtk_icon_theme_has_icon(icon_theme, key)) return FALSE;
270
271         pixbuf = gtk_icon_theme_load_icon(icon_theme,
272                            icon, /* icon name */
273                            64, /* size */
274                            static_cast<GtkIconLookupFlags>(0),  /* flags */
275                            &error);
276         if (!pixbuf)
277                 {
278                 if (error)
279                         {
280                         DEBUG_1("Couldn't load icon %s: %s", icon, error->message);
281                         g_error_free(error);
282                         error = nullptr;
283                         }
284
285                 if (strchr(icon, '.'))
286                         {
287                         /* try again without extension */
288                         gchar *icon2 = remove_extension_from_path(icon);
289                         pixbuf = gtk_icon_theme_load_icon(icon_theme,
290                                            icon2, /* icon name */
291                                            64, /* size */
292                                            static_cast<GtkIconLookupFlags>(0),  /* flags */
293                                            &error);
294                         if (error)
295                                 {
296                                 DEBUG_1("Couldn't load icon %s: %s", icon2, error->message);
297                                 g_error_free(error);
298                                 error = nullptr;
299
300                                 /* try as an absolute path */
301                                 pixbuf = gdk_pixbuf_new_from_file(icon, &error);
302                                 if (error)
303                                         {
304                                         DEBUG_1("Couldn't load icon as absolute path %s: %s", icon, error->message);
305                                         g_error_free(error);
306                                         }
307                                 }
308                         g_free(icon2);
309                         }
310                 }
311
312         if (!pixbuf) return FALSE;
313
314         register_stock_icon(key, pixbuf);
315         return TRUE;
316 }
317
318 gboolean pixbuf_scale_aspect(gint req_w, gint req_h,
319                              gint old_w, gint old_h,
320                              gint *new_w, gint *new_h)
321 {
322         if ((static_cast<gdouble>(req_w) / old_w) < (static_cast<gdouble>(req_h) / old_h))
323                 {
324                 *new_w = req_w;
325                 *new_h = static_cast<gdouble>(*new_w) / old_w * old_h;
326                 if (*new_h < 1) *new_h = 1;
327                 }
328         else
329                 {
330                 *new_h = req_h;
331                 *new_w = static_cast<gdouble>(*new_h) / old_h * old_w;
332                 if (*new_w < 1) *new_w = 1;
333                 }
334
335         return (*new_w != old_w || *new_h != old_h);
336 }
337
338 GdkPixbuf *pixbuf_fallback(FileData *fd, gint requested_width, gint requested_height)
339 {
340         GdkPixbuf *pixbuf;
341
342         switch (fd->format_class)
343                 {
344                 case FORMAT_CLASS_UNKNOWN:
345                         pixbuf = pixbuf_inline(PIXBUF_INLINE_UNKNOWN);
346                         break;
347                 case FORMAT_CLASS_META:
348                         pixbuf = pixbuf_inline(PIXBUF_INLINE_METADATA);
349                         break;
350                 case FORMAT_CLASS_VIDEO:
351                         pixbuf = pixbuf_inline(PIXBUF_INLINE_VIDEO);
352                         break;
353                 case FORMAT_CLASS_COLLECTION:
354                         pixbuf = pixbuf_inline(PIXBUF_INLINE_COLLECTION);
355                         break;
356                 case FORMAT_CLASS_DOCUMENT:
357                         pixbuf = pixbuf_inline(PIXBUF_INLINE_ICON_PDF);
358                         break;
359                 case FORMAT_CLASS_ARCHIVE:
360                         pixbuf = pixbuf_inline(PIXBUF_INLINE_ARCHIVE);
361                         break;
362                 default:
363                         pixbuf = pixbuf_inline(PIXBUF_INLINE_BROKEN);
364                 }
365
366         if (requested_width && requested_height)
367                 {
368                 gint w = gdk_pixbuf_get_width(pixbuf);
369                 gint h = gdk_pixbuf_get_height(pixbuf);
370
371                 if (w > requested_width || h > requested_height)
372                         {
373                         gint nw;
374                         gint nh;
375
376                         if (pixbuf_scale_aspect(requested_width, requested_height,
377                                                           w, h, &nw, &nh))
378                                 {
379                                 GdkPixbuf *tmp;
380
381                                 tmp = pixbuf;
382                                 pixbuf = gdk_pixbuf_scale_simple(tmp, nw, nh, GDK_INTERP_TILES);
383                                 g_object_unref(G_OBJECT(tmp));
384                                 }
385                         }
386                 }
387         return pixbuf;
388 }
389
390
391 /*
392  *-----------------------------------------------------------------------------
393  * misc utils
394  *-----------------------------------------------------------------------------
395  */
396
397 gboolean util_clip_region(gint x, gint y, gint w, gint h,
398                           gint clip_x, gint clip_y, gint clip_w, gint clip_h,
399                           gint *rx, gint *ry, gint *rw, gint *rh)
400 {
401         // Ensures that clip region and main region have some overlap (they aren't
402         // completely disjoint).
403         if (clip_x + clip_w <= x ||  /* assert(x < clip_right) && */
404             clip_x >= x + w ||       /* assert(clip_x < right) && */
405             clip_y + clip_h <= y ||  /* assert(y < clip_bottom && */
406             clip_y >= y + h)         /* assert(bottom < clip_y) */
407                 {
408                 return FALSE;
409                 }
410
411         // We choose the right-most x coordinate.
412         *rx = MAX(x, clip_x);
413         // And the narrowest width.
414         *rw = MIN((x + w), (clip_x + clip_w)) - *rx;
415
416         // We choose the bottom-most y coordinate.
417         *ry = MAX(y, clip_y);
418         // And the shortest height.
419         *rh = MIN((y + h), (clip_y + clip_h)) - *ry;
420
421         return TRUE;
422 }
423
424 /*
425  *-----------------------------------------------------------------------------
426  * pixbuf rotation
427  *-----------------------------------------------------------------------------
428  */
429
430 static void pixbuf_copy_block_rotate(guchar *src, gint src_row_stride, gint x, gint y,
431                                      guchar *dest, gint dest_row_stride, gint w, gint h,
432                                      gint bytes_per_pixel, gboolean counter_clockwise)
433 {
434         gint i;
435         gint j;
436         guchar *sp;
437         guchar *dp;
438
439         for (i = 0; i < h; i++)
440                 {
441                 sp = src + ((i + y) * src_row_stride) + (x * bytes_per_pixel);
442                 for (j = 0; j < w; j++)
443                         {
444                         if (counter_clockwise)
445                                 {
446                                 dp = dest + ((w - j - 1) * dest_row_stride) + (i * bytes_per_pixel);
447                                 }
448                         else
449                                 {
450                                 dp = dest + (j * dest_row_stride) + ((h - i - 1) * bytes_per_pixel);
451                                 }
452                         *(dp++) = *(sp++);      /* r */
453                         *(dp++) = *(sp++);      /* g */
454                         *(dp++) = *(sp++);      /* b */
455                         if (bytes_per_pixel == 4) *(dp) = *(sp++);      /* a */
456                         }
457                 }
458
459 }
460
461 static void pixbuf_copy_block(guchar *src, gint src_row_stride, gint w, gint h,
462                               guchar *dest, gint dest_row_stride, gint x, gint y, gint bytes_per_pixel)
463 {
464         gint i;
465         guchar *sp;
466         guchar *dp;
467
468         for (i = 0; i < h; i++)
469                 {
470                 sp = src + (i * src_row_stride);
471                 dp = dest + ((y + i) * dest_row_stride) + (x * bytes_per_pixel);
472                 memcpy(dp, sp, w * bytes_per_pixel);
473                 }
474 }
475
476 enum {
477         ROTATE_BUFFER_WIDTH = 48,
478         ROTATE_BUFFER_HEIGHT = 48
479 };
480
481 /*
482  * Returns a copy of pixbuf src rotated 90 degrees clockwise or 90 counterclockwise
483  *
484  */
485 GdkPixbuf *pixbuf_copy_rotate_90(GdkPixbuf *src, gboolean counter_clockwise)
486 {
487         GdkPixbuf *dest;
488         gboolean has_alpha;
489         gint sw;
490         gint sh;
491         gint srs;
492         gint dw;
493         gint dh;
494         gint drs;
495         guchar *s_pix;
496         guchar *d_pix;
497         gint i;
498         gint j;
499         gint a;
500         GdkPixbuf *buffer;
501         guchar *b_pix;
502         gint brs;
503         gint w;
504         gint h;
505
506         if (!src) return nullptr;
507
508         sw = gdk_pixbuf_get_width(src);
509         sh = gdk_pixbuf_get_height(src);
510         has_alpha = gdk_pixbuf_get_has_alpha(src);
511         srs = gdk_pixbuf_get_rowstride(src);
512         s_pix = gdk_pixbuf_get_pixels(src);
513
514         dw = sh;
515         dh = sw;
516         dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, dw, dh);
517         drs = gdk_pixbuf_get_rowstride(dest);
518         d_pix = gdk_pixbuf_get_pixels(dest);
519
520         a = (has_alpha ? 4 : 3);
521
522         buffer = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8,
523                                 ROTATE_BUFFER_WIDTH, ROTATE_BUFFER_HEIGHT);
524         b_pix = gdk_pixbuf_get_pixels(buffer);
525         brs = gdk_pixbuf_get_rowstride(buffer);
526
527         for (i = 0; i < sh; i+= ROTATE_BUFFER_WIDTH)
528                 {
529                 w = MIN(ROTATE_BUFFER_WIDTH, (sh - i));
530                 for (j = 0; j < sw; j += ROTATE_BUFFER_HEIGHT)
531                         {
532                         gint x;
533                         gint y;
534
535                         h = MIN(ROTATE_BUFFER_HEIGHT, (sw - j));
536                         pixbuf_copy_block_rotate(s_pix, srs, j, i,
537                                                  b_pix, brs, h, w,
538                                                  a, counter_clockwise);
539
540                         if (counter_clockwise)
541                                 {
542                                 x = i;
543                                 y = sw - h - j;
544                                 }
545                         else
546                                 {
547                                 x = sh - w - i;
548                                 y = j;
549                                 }
550                         pixbuf_copy_block(b_pix, brs, w, h,
551                                           d_pix, drs, x, y, a);
552                         }
553                 }
554
555         g_object_unref(buffer);
556
557 #if 0
558         /* this is the simple version of rotation (roughly 2-4x slower) */
559
560         for (i = 0; i < sh; i++)
561                 {
562                 sp = s_pix + (i * srs);
563                 for (j = 0; j < sw; j++)
564                         {
565                         if (counter_clockwise)
566                                 {
567                                 dp = d_pix + ((dh - j - 1) * drs) + (i * a);
568                                 }
569                         else
570                                 {
571                                 dp = d_pix + (j * drs) + ((dw - i - 1) * a);
572                                 }
573
574                         *(dp++) = *(sp++);      /* r */
575                         *(dp++) = *(sp++);      /* g */
576                         *(dp++) = *(sp++);      /* b */
577                         if (has_alpha) *(dp) = *(sp++); /* a */
578                         }
579                 }
580 #endif
581
582         return dest;
583 }
584
585 /*
586  * Returns a copy of pixbuf mirrored and or flipped.
587  * TO do a 180 degree rotations set both mirror and flipped TRUE
588  * if mirror and flip are FALSE, result is a simple copy.
589  */
590 GdkPixbuf *pixbuf_copy_mirror(GdkPixbuf *src, gboolean mirror, gboolean flip)
591 {
592         GdkPixbuf *dest;
593         gboolean has_alpha;
594         gint w;
595         gint h;
596         gint srs;
597         gint drs;
598         guchar *s_pix;
599         guchar *d_pix;
600         guchar *sp;
601         guchar *dp;
602         gint i;
603         gint j;
604         gint a;
605
606         if (!src) return nullptr;
607
608         w = gdk_pixbuf_get_width(src);
609         h = gdk_pixbuf_get_height(src);
610         has_alpha = gdk_pixbuf_get_has_alpha(src);
611         srs = gdk_pixbuf_get_rowstride(src);
612         s_pix = gdk_pixbuf_get_pixels(src);
613
614         dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, has_alpha, 8, w, h);
615         drs = gdk_pixbuf_get_rowstride(dest);
616         d_pix = gdk_pixbuf_get_pixels(dest);
617
618         a = has_alpha ? 4 : 3;
619
620         for (i = 0; i < h; i++)
621                 {
622                 sp = s_pix + (i * srs);
623                 if (flip)
624                         {
625                         dp = d_pix + ((h - i - 1) * drs);
626                         }
627                 else
628                         {
629                         dp = d_pix + (i * drs);
630                         }
631                 if (mirror)
632                         {
633                         dp += (w - 1) * a;
634                         for (j = 0; j < w; j++)
635                                 {
636                                 *(dp++) = *(sp++);      /* r */
637                                 *(dp++) = *(sp++);      /* g */
638                                 *(dp++) = *(sp++);      /* b */
639                                 if (has_alpha) *(dp) = *(sp++); /* a */
640                                 dp -= (a + 3);
641                                 }
642                         }
643                 else
644                         {
645                         for (j = 0; j < w; j++)
646                                 {
647                                 *(dp++) = *(sp++);      /* r */
648                                 *(dp++) = *(sp++);      /* g */
649                                 *(dp++) = *(sp++);      /* b */
650                                 if (has_alpha) *(dp++) = *(sp++);       /* a */
651                                 }
652                         }
653                 }
654
655         return dest;
656 }
657
658 GdkPixbuf *pixbuf_apply_orientation(GdkPixbuf *pixbuf, gint orientation)
659 {
660         GdkPixbuf *dest;
661         GdkPixbuf *tmp = nullptr;
662
663         switch (orientation)
664                 {
665                 case EXIF_ORIENTATION_TOP_LEFT:
666                         dest = gdk_pixbuf_copy(pixbuf);
667                         break;
668                 case EXIF_ORIENTATION_TOP_RIGHT:
669                         /* mirrored */
670                         dest = pixbuf_copy_mirror(pixbuf, TRUE, FALSE);
671                         break;
672                 case EXIF_ORIENTATION_BOTTOM_RIGHT:
673                         /* upside down */
674                         dest = pixbuf_copy_mirror(pixbuf, TRUE, TRUE);
675                         break;
676                 case EXIF_ORIENTATION_BOTTOM_LEFT:
677                         /* flipped */
678                         dest = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
679                         break;
680                 case EXIF_ORIENTATION_LEFT_TOP:
681                         tmp = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
682                         dest = pixbuf_copy_rotate_90(tmp, FALSE);
683                         break;
684                 case EXIF_ORIENTATION_RIGHT_TOP:
685                         /* rotated -90 (270) */
686                         dest = pixbuf_copy_rotate_90(pixbuf, FALSE);
687                         break;
688                 case EXIF_ORIENTATION_RIGHT_BOTTOM:
689                         tmp = pixbuf_copy_mirror(pixbuf, FALSE, TRUE);
690                         dest = pixbuf_copy_rotate_90(tmp, TRUE);
691                         break;
692                 case EXIF_ORIENTATION_LEFT_BOTTOM:
693                         /* rotated 90 */
694                         dest = pixbuf_copy_rotate_90(pixbuf, TRUE);
695                         break;
696                 default:
697                         dest = gdk_pixbuf_copy(pixbuf);
698                         break;
699                 }
700         if (tmp) g_object_unref(tmp);
701         return dest;
702
703 }
704
705
706 /*
707  *-----------------------------------------------------------------------------
708  * pixbuf drawing (rectangles)
709  *-----------------------------------------------------------------------------
710  */
711
712 /*
713  * Fills region of pixbuf at x,y over w,h
714  * with colors red (r), green (g), blue (b)
715  * applying alpha (a), use a=255 for solid.
716  */
717 void pixbuf_draw_rect_fill(GdkPixbuf *pb,
718                            gint x, gint y, gint w, gint h,
719                            gint r, gint g, gint b, gint a)
720 {
721         gboolean has_alpha;
722         gint pw;
723         gint ph;
724         gint prs;
725         guchar *p_pix;
726         guchar *pp;
727         gint i;
728         gint j;
729
730         if (!pb) return;
731
732         pw = gdk_pixbuf_get_width(pb);
733         ph = gdk_pixbuf_get_height(pb);
734
735         if (x < 0 || x + w > pw) return;
736         if (y < 0 || y + h > ph) return;
737
738         has_alpha = gdk_pixbuf_get_has_alpha(pb);
739         prs = gdk_pixbuf_get_rowstride(pb);
740         p_pix = gdk_pixbuf_get_pixels(pb);
741
742         const gint p_step = has_alpha ? 4 : 3;
743
744         for (i = 0; i < h; i++)
745                 {
746                 pp = p_pix + (y + i) * prs + (x * p_step);
747                 for (j = 0; j < w; j++)
748                         {
749                         pp[0] = (r * a + pp[0] * (256-a)) >> 8;
750                         pp[1] = (g * a + pp[1] * (256-a)) >> 8;
751                         pp[2] = (b * a + pp[2] * (256-a)) >> 8;
752                         // TODO(xsdg): Should we do anything about a potential
753                         // existing alpha value here?
754                         pp += p_step;
755                         }
756                 }
757 }
758
759 #pragma GCC diagnostic push
760 #pragma GCC diagnostic ignored "-Wunused-function"
761 void pixbuf_draw_rect_unused(GdkPixbuf *pb,
762                       gint x, gint y, gint w, gint h,
763                       gint r, gint g, gint b, gint a,
764                       gint left, gint right, gint top, gint bottom)
765 {
766         pixbuf_draw_rect_fill(pb, x + left, y, w - left - right, top,
767                               r, g, b ,a);
768         pixbuf_draw_rect_fill(pb, x + w - right, y, right, h,
769                               r, g, b ,a);
770         pixbuf_draw_rect_fill(pb, x + left, y + h - bottom, w - left - right, bottom,
771                               r, g, b ,a);
772         pixbuf_draw_rect_fill(pb, x, y, left, h,
773                               r, g, b ,a);
774 }
775 #pragma GCC diagnostic pop
776
777 void pixbuf_set_rect_fill(GdkPixbuf *pb,
778                           gint x, gint y, gint w, gint h,
779                           gint r, gint g, gint b, gint a)
780 {
781         gboolean has_alpha;
782         gint pw;
783         gint ph;
784         gint prs;
785         guchar *p_pix;
786         guchar *pp;
787         gint i;
788         gint j;
789
790         if (!pb) return;
791
792         pw = gdk_pixbuf_get_width(pb);
793         ph = gdk_pixbuf_get_height(pb);
794
795         if (x < 0 || x + w > pw) return;
796         if (y < 0 || y + h > ph) return;
797
798         has_alpha = gdk_pixbuf_get_has_alpha(pb);
799         prs = gdk_pixbuf_get_rowstride(pb);
800         p_pix = gdk_pixbuf_get_pixels(pb);
801
802         const gint p_step = has_alpha ? 4 : 3;
803
804         for (i = 0; i < h; i++)
805                 {
806                 pp = p_pix + (y + i) * prs + (x * p_step);
807                 for (j = 0; j < w; j++)
808                         {
809                         *pp = r; pp++;
810                         *pp = g; pp++;
811                         *pp = b; pp++;
812                         if (has_alpha) { *pp = a; pp++; }
813                         }
814                 }
815 }
816
817 void pixbuf_set_rect(GdkPixbuf *pb,
818                      gint x, gint y, gint w, gint h,
819                      gint r, gint g, gint b, gint a,
820                      gint left_width, gint right_width, gint top_width, gint bottom_width)
821 {
822         // TODO(xsdg): This function has multiple off-by-one errors.  Would be
823         // much easier to read (and implement correctly) with temporaries to
824         // translate from (x, y, w, h) coordinates to (x1, y1, x2, y2).
825         pixbuf_set_rect_fill(pb,
826                              x + left_width, y, w - left_width - right_width, top_width,
827                              r, g, b ,a);
828         pixbuf_set_rect_fill(pb,
829                              x + w - right_width, y, right_width, h,
830                              r, g, b ,a);
831         pixbuf_set_rect_fill(pb,
832                              x + left_width, y + h - bottom_width, w - left_width - right_width, bottom_width,
833                              r, g, b ,a);
834         pixbuf_set_rect_fill(pb,
835                              x, y, left_width, h,
836                              r, g, b ,a);
837 }
838
839 void pixbuf_pixel_set(GdkPixbuf *pb, gint x, gint y, gint r, gint g, gint b, gint a)
840 {
841         guchar *buf;
842         gboolean has_alpha;
843         gint rowstride;
844         guchar *p;
845
846         if (x < 0 || x >= gdk_pixbuf_get_width(pb) ||
847             y < 0 || y >= gdk_pixbuf_get_height(pb)) return;
848
849         buf = gdk_pixbuf_get_pixels(pb);
850         has_alpha = gdk_pixbuf_get_has_alpha(pb);
851         rowstride = gdk_pixbuf_get_rowstride(pb);
852
853         p = buf + (y * rowstride) + (x * (has_alpha ? 4 : 3));
854         *p = r; p++;
855         *p = g; p++;
856         *p = b; p++;
857         if (has_alpha) *p = a;
858 }
859
860
861 /*
862  *-----------------------------------------------------------------------------
863  * pixbuf text rendering
864  *-----------------------------------------------------------------------------
865  */
866
867 static void pixbuf_copy_font(GdkPixbuf *src, gint sx, gint sy,
868                              GdkPixbuf *dest, gint dx, gint dy,
869                              gint w, gint h,
870                              guint8 r, guint8 g, guint8 b, guint8 a)
871 {
872         gint sw;
873         gint sh;
874         gint srs;
875         gboolean s_alpha;
876         gint s_step;
877         guchar *s_pix;
878         gint dw;
879         gint dh;
880         gint drs;
881         gboolean d_alpha;
882         gint d_step;
883         guchar *d_pix;
884
885         guchar *sp;
886         guchar *dp;
887         gint i;
888         gint j;
889
890         if (!src || !dest) return;
891
892         sw = gdk_pixbuf_get_width(src);
893         sh = gdk_pixbuf_get_height(src);
894
895         if (sx < 0 || sx + w > sw) return;
896         if (sy < 0 || sy + h > sh) return;
897
898         dw = gdk_pixbuf_get_width(dest);
899         dh = gdk_pixbuf_get_height(dest);
900
901         if (dx < 0 || dx + w > dw) return;
902         if (dy < 0 || dy + h > dh) return;
903
904         s_alpha = gdk_pixbuf_get_has_alpha(src);
905         d_alpha = gdk_pixbuf_get_has_alpha(dest);
906         srs = gdk_pixbuf_get_rowstride(src);
907         drs = gdk_pixbuf_get_rowstride(dest);
908         s_pix = gdk_pixbuf_get_pixels(src);
909         d_pix = gdk_pixbuf_get_pixels(dest);
910
911         s_step = (s_alpha) ? 4 : 3;
912         d_step = (d_alpha) ? 4 : 3;
913
914         for (i = 0; i < h; i++)
915                 {
916                 sp = s_pix + (sy + i) * srs + sx * s_step;
917                 dp = d_pix + (dy + i) * drs + dx * d_step;
918                 for (j = 0; j < w; j++)
919                         {
920                         if (*sp)
921                                 {
922                                 guint8 asub;
923
924                                 asub = a * sp[0] / 255;
925                                 *dp = (r * asub + *dp * (256-asub)) >> 8;
926                                 dp++;
927                                 asub = a * sp[1] / 255;
928                                 *dp = (g * asub + *dp * (256-asub)) >> 8;
929                                 dp++;
930                                 asub = a * sp[2] / 255;
931                                 *dp = (b * asub + *dp * (256-asub)) >> 8;
932                                 dp++;
933
934                                 if (d_alpha)
935                                         {
936                                         *dp = MAX(*dp, a * ((sp[0] + sp[1] + sp[2]) / 3) / 255);
937                                         dp++;
938                                         }
939                                 }
940                         else
941                                 {
942                                 dp += d_step;
943                                 }
944
945                         sp += s_step;
946                         }
947                 }
948 }
949
950 void pixbuf_draw_layout(GdkPixbuf *pixbuf, PangoLayout *layout, GtkWidget *,
951                         gint x, gint y,
952                         guint8 r, guint8 g, guint8 b, guint8 a)
953 {
954         GdkPixbuf *buffer;
955         gint w;
956         gint h;
957         gint sx;
958         gint sy;
959         gint dw;
960         gint dh;
961         cairo_surface_t *source;
962         cairo_t *cr;
963
964         pango_layout_get_pixel_size(layout, &w, &h);
965         if (w < 1 || h < 1) return;
966
967         source = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h);
968
969         cr = cairo_create (source);
970         cairo_set_source_rgb(cr, 0, 0, 0);
971         cairo_rectangle (cr, 0, 0, w, h);
972         cairo_fill (cr);
973         cairo_set_source_rgb(cr, 1, 1, 1);
974         pango_cairo_show_layout (cr, layout);
975         cairo_destroy (cr);
976
977         buffer = gdk_pixbuf_new_from_data (cairo_image_surface_get_data (source),
978                                            GDK_COLORSPACE_RGB,
979                                            cairo_image_surface_get_format (source) == CAIRO_FORMAT_ARGB32,
980                                            8,
981                                            cairo_image_surface_get_width (source),
982                                            cairo_image_surface_get_height (source),
983                                            cairo_image_surface_get_stride (source),
984                                            nullptr,
985                                            nullptr);
986
987         sx = 0;
988         sy = 0;
989         dw = gdk_pixbuf_get_width(pixbuf);
990         dh = gdk_pixbuf_get_height(pixbuf);
991
992         if (x < 0)
993                 {
994                 w += x;
995                 sx = -x;
996                 x = 0;
997                 }
998
999         if (y < 0)
1000                 {
1001                 h += y;
1002                 sy = -y;
1003                 y = 0;
1004                 }
1005
1006         if (x + w > dw) w = dw - x;
1007         if (y + h > dh) h = dh - y;
1008
1009         pixbuf_copy_font(buffer, sx, sy,
1010                          pixbuf, x, y, w, h,
1011                          r, g, b, a);
1012
1013         g_object_unref(buffer);
1014         cairo_surface_destroy(source);
1015 }
1016
1017 /*
1018  *-----------------------------------------------------------------------------
1019  * pixbuf drawing (triangle)
1020  *-----------------------------------------------------------------------------
1021  */
1022
1023 void util_clip_triangle(gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
1024                         gint &rx, gint &ry, gint &rw, gint &rh)
1025 {
1026         rx = std::min({x1, x2, x3});
1027         ry = std::min({y1, y2, y3});
1028         rw = std::max({abs(x1 - x2), abs(x2 - x3), abs(x3 - x1)});
1029         rh = std::max({abs(y1 - y2), abs(y2 - y3), abs(y3 - y1)});
1030 }
1031
1032 void pixbuf_draw_triangle(GdkPixbuf *pb,
1033                           gint clip_x, gint clip_y, gint clip_w, gint clip_h,
1034                           gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
1035                           guint8 r, guint8 g, guint8 b, guint8 a)
1036 {
1037         gboolean has_alpha;
1038         gint pw;
1039         gint ph;
1040         gint prs;
1041         gint rx;
1042         gint ry;
1043         gint rw;
1044         gint rh;
1045         gint tx;
1046         gint ty;
1047         gint tw;
1048         gint th;
1049         gint fx1;
1050         gint fy1;
1051         gint fx2;
1052         gint fy2;
1053         gint fw;
1054         gint fh;
1055         guchar *p_pix;
1056         guchar *pp;
1057         gint p_step;
1058         gdouble slope1;
1059         gdouble slope2;
1060         gint slope1_x;
1061         gint slope1_y;
1062         gint y;
1063         gboolean middle = FALSE;
1064
1065         if (!pb) return;
1066
1067         pw = gdk_pixbuf_get_width(pb);
1068         ph = gdk_pixbuf_get_height(pb);
1069
1070         // Intersects the clip region with the pixbuf. r{x,y,w,h} is that
1071         // intersecting region.
1072         if (!util_clip_region(0, 0, pw, ph,
1073                               clip_x, clip_y, clip_w, clip_h,
1074                               &rx, &ry, &rw, &rh)) return;
1075
1076         // Determine the bounding box for the triangle.
1077         util_clip_triangle(x1, y1, x2, y2, x3, y3,
1078                            tx, ty, tw, th);
1079
1080         // And now clip the triangle bounding box to the pixbuf clipping region.
1081         if (!util_clip_region(rx, ry, rw, rh,
1082                               tx, ty, tw, th,
1083                               &fx1, &fy1, &fw, &fh)) return;
1084         fx2 = fx1 + fw;
1085         fy2 = fy1 + fh;
1086
1087         has_alpha = gdk_pixbuf_get_has_alpha(pb);
1088         prs = gdk_pixbuf_get_rowstride(pb);
1089         p_pix = gdk_pixbuf_get_pixels(pb);
1090
1091         p_step = (has_alpha) ? 4 : 3;
1092
1093         // Ensure that points are ordered by increasing y coordinate.
1094         if (y1 > y2)
1095                 {
1096                 std::swap(x1, x2);
1097                 std::swap(y1, y2);
1098                 }
1099         if (y2 > y3)
1100                 {
1101                 std::swap(x2, x3);
1102                 std::swap(y2, y3);
1103                 }
1104         if (y1 > y2)
1105                 {
1106                 std::swap(x1, x2);
1107                 std::swap(y1, y2);
1108                 }
1109
1110         // TODO(xsdg): Drop these explicit casts.  Int will always promote to
1111         // double without issue.
1112         slope1 = static_cast<gdouble>(y2 - y1);
1113         if (slope1) slope1 = static_cast<gdouble>(x2 - x1) / slope1;
1114         slope1_x = x1;
1115         slope1_y = y1;
1116         slope2 = static_cast<gdouble>(y3 - y1);
1117         if (slope2) slope2 = static_cast<gdouble>(x3 - x1) / slope2;
1118
1119         for (y = fy1; y < fy2; y++)
1120                 {
1121                 gint xa;
1122                 gint xb;
1123
1124                 if (!middle && y > y2)
1125                         {
1126                         slope1 = static_cast<gdouble>(y3 - y2);
1127                         if (slope1) slope1 = static_cast<gdouble>(x3 - x2) / slope1;
1128                         slope1_x = x2;
1129                         slope1_y = y2;
1130
1131                         middle = TRUE;
1132                         }
1133
1134                 xa = slope1_x + (slope1 * (y - slope1_y) + 0.5);
1135                 xb = x1 + (slope2 * (y - y1) + 0.5);
1136
1137                 if (xa > xb)
1138                         {
1139                         std::swap(xa, xb);
1140                         }
1141
1142                 xa = CLAMP(xa, fx1, fx2);
1143                 xb = CLAMP(xb, fx1, fx2);
1144
1145                 pp = p_pix + y * prs + xa * p_step;
1146
1147                 while (xa < xb)
1148                         {
1149                         pp[0] = (r * a + pp[0] * (256-a)) >> 8;
1150                         pp[1] = (g * a + pp[1] * (256-a)) >> 8;
1151                         pp[2] = (b * a + pp[2] * (256-a)) >> 8;
1152                         pp += p_step;
1153
1154                         xa++;
1155                         }
1156                 }
1157 }
1158
1159 /*
1160  *-----------------------------------------------------------------------------
1161  * pixbuf drawing (line)
1162  *-----------------------------------------------------------------------------
1163  */
1164
1165 /**
1166  * @brief Clips the specified line segment to the specified clipping region.
1167  * @param[in] clip_x,clip_y Coordinates of the top-left corner of the clipping region.
1168  * @param[in] clip_w,clip_h Extent of the clipping region.
1169  * @param[in] x1,y1 Coordinates of the first point of the line segment.
1170  * @param[in] x2,y2 Coordinates of the second point of the line segment.
1171  * @param[out] rx1,ry1 Computed coordinates of the first point of the clipped line segment.
1172  * @param[out] rx2,ry2 Computed coordinates of the second point of the clipped line segment.
1173  * @retval FALSE The line segment lies outside of the clipping region.
1174  * @retval TRUE The clip operation was performed, and the output params were set.
1175  */
1176 static gboolean util_clip_line(gdouble clip_x, gdouble clip_y, gdouble clip_w, gdouble clip_h,
1177                                gdouble x1, gdouble y1, gdouble x2, gdouble y2,
1178                                gdouble *rx1, gdouble *ry1, gdouble *rx2, gdouble *ry2)
1179 {
1180         gboolean flip = FALSE;
1181         gdouble d;
1182
1183         // Normalize: Line endpoint 1 must be farther left.
1184         if (x1 > x2)
1185                 {
1186                 std::swap(x1, x2);
1187                 std::swap(y1, y2);
1188                 flip = TRUE;
1189                 }
1190
1191         // Ensure the line horizontally overlaps with the clip region.
1192         if (x2 < clip_x || x1 > clip_x + clip_w) return FALSE;
1193
1194         // Ensure the line vertically overlaps with the clip region.
1195         // Note that a line can both horizontally and vertically overlap with
1196         // clipping region, while still being outside of the clipping region.  That
1197         // case is detected further below.
1198         if (y1 < y2)
1199                 {
1200                 if (y2 < clip_y || y1 > clip_y + clip_h) return FALSE;
1201                 }
1202         else
1203                 {
1204                 if (y1 < clip_y || y2 > clip_y + clip_h) return FALSE;
1205                 }
1206
1207         d = x2 - x1;
1208         // TODO(xsdg): Either use ints here, or define a reasonable epsilon to do the
1209         // right thing if -epsilon < d < 0.  We already guaranteed above that x2 >= x1.
1210         if (d > 0.0)
1211                 {
1212                 gdouble slope;
1213
1214                 slope = (y2 - y1) / d;
1215                 // If needed, project (x1, y1) to be horizontally within the clip
1216                 // region, while maintaining the line's slope and y-offset.
1217                 if (x1 < clip_x)
1218                         {
1219                         y1 = y1 + slope * (clip_x - x1);
1220                         x1 = clip_x;
1221                         }
1222                 // Likewise with (x2, y2).
1223                 if (x2 > clip_x + clip_w)
1224                         {
1225                         y2 = y2 + slope * (clip_x + clip_w - x2);
1226                         x2 = clip_x + clip_w;
1227                         }
1228                 }
1229
1230         // Check that any horizontal projections didn't cause the line segment to
1231         // no longer vertically overlap with the clip region.
1232         if (y1 < y2)
1233                 {
1234                 if (y2 < clip_y || y1 > clip_y + clip_h) return FALSE;
1235                 }
1236         else
1237                 {
1238                 if (y1 < clip_y || y2 > clip_y + clip_h) return FALSE;
1239
1240                 // Re-normalize: line endpoint 1 must be farther up.
1241                 std::swap(x1, x2);
1242                 std::swap(y1, y2);
1243                 flip = !flip;
1244                 }
1245
1246         d = y2 - y1;
1247         if (d > 0.0)
1248                 {
1249                 gdouble slope;
1250
1251                 slope = (x2 - x1) / d;
1252                 // If needed, project (x1, y1) to be vertically within the clip
1253                 // region, while maintaining the line's slope and x-offset.
1254                 if (y1 < clip_y)
1255                         {
1256                         x1 = x1 + slope * (clip_y - y1);
1257                         y1 = clip_y;
1258                         }
1259                 // Likewise with (x2, y2).
1260                 if (y2 > clip_y + clip_h)
1261                         {
1262                         x2 = x2 + slope * (clip_y + clip_h - y2);
1263                         y2 = clip_y + clip_h;
1264                         }
1265                 }
1266
1267         // Set the output params, accounting for any flips that might have
1268         // happened during normalization.
1269         if (flip)
1270                 {
1271                 *rx1 = x2;
1272                 *ry1 = y2;
1273                 *rx2 = x1;
1274                 *ry2 = y1;
1275                 }
1276         else
1277                 {
1278                 *rx1 = x1;
1279                 *ry1 = y1;
1280                 *rx2 = x2;
1281                 *ry2 = y2;
1282                 }
1283
1284         return TRUE;
1285 }
1286
1287 void pixbuf_draw_line(GdkPixbuf *pb,
1288                       gint clip_x, gint clip_y, gint clip_w, gint clip_h,
1289                       gint x1, gint y1, gint x2, gint y2,
1290                       guint8 r, guint8 g, guint8 b, guint8 a)
1291 {
1292         gboolean has_alpha;
1293         gint pw;
1294         gint ph;
1295         gint prs;
1296         gint rx;
1297         gint ry;
1298         gint rw;
1299         gint rh;
1300         gdouble rx1;
1301         gdouble ry1;
1302         gdouble rx2;
1303         gdouble ry2;
1304         guchar *p_pix;
1305         guchar *pp;
1306         gint p_step;
1307         gdouble slope;
1308         gdouble x;
1309         gdouble y;
1310         gint px;
1311         gint py;
1312         gint cx1;
1313         gint cy1;
1314         gint cx2;
1315         gint cy2;
1316
1317         if (!pb) return;
1318
1319         pw = gdk_pixbuf_get_width(pb);
1320         ph = gdk_pixbuf_get_height(pb);
1321
1322         // Intersects the clip region with the pixbuf. r{x,y,w,h} is that
1323         // intersecting region.
1324         if (!util_clip_region(0, 0, pw, ph,
1325                               clip_x, clip_y, clip_w, clip_h,
1326                               &rx, &ry, &rw, &rh)) return;
1327         // TODO(xsdg): These explicit casts are unnecessary and harm readability.
1328         // Clips the specified line segment to the intersecting region from above.
1329         if (!util_clip_line(static_cast<gdouble>(rx), static_cast<gdouble>(ry), static_cast<gdouble>(rw), static_cast<gdouble>(rh),
1330                             static_cast<gdouble>(x1), static_cast<gdouble>(y1), static_cast<gdouble>(x2), static_cast<gdouble>(y2),
1331                             &rx1, &ry1, &rx2, &ry2)) return;
1332
1333         cx1 = rx;
1334         cy1 = ry;
1335         cx2 = rx + rw;
1336         cy2 = ry + rh;
1337
1338         has_alpha = gdk_pixbuf_get_has_alpha(pb);
1339         prs = gdk_pixbuf_get_rowstride(pb);
1340         p_pix = gdk_pixbuf_get_pixels(pb);
1341
1342         p_step = (has_alpha) ? 4 : 3;
1343
1344         // We draw the clipped line segment along the longer axis first, and
1345         // allow the shorter axis to follow.  This is because our raster line segment
1346         // will contain max(rx2-rx1, ry2-ry1) pixels, and the pixels along the
1347         // shorter axis may not advance for each cycle (the line is not anti-aliased).
1348         if (fabs(rx2 - rx1) > fabs(ry2 - ry1))
1349                 {
1350                 if (rx1 > rx2)
1351                         {
1352                         std::swap(rx1, rx2);
1353                         std::swap(ry1, ry2);
1354                         }
1355
1356                 slope = rx2 - rx1;
1357                 if (slope != 0.0) slope = (ry2 - ry1) / slope;
1358                 for (x = rx1; x < rx2; x += 1.0)
1359                         {
1360                         px = static_cast<gint>(x + 0.5);
1361                         py = static_cast<gint>(ry1 + (x - rx1) * slope + 0.5);
1362
1363                         if (px >=  cx1 && px < cx2 && py >= cy1 && py < cy2)
1364                                 {
1365                                 pp = p_pix + py * prs + px * p_step;
1366                                 *pp = (r * a + *pp * (256-a)) >> 8;
1367                                 pp++;
1368                                 *pp = (g * a + *pp * (256-a)) >> 8;
1369                                 pp++;
1370                                 *pp = (b * a + *pp * (256-a)) >> 8;
1371                                 }
1372                         }
1373                 }
1374         else
1375                 {
1376                 if (ry1 > ry2)
1377                         {
1378                         std::swap(rx1, rx2);
1379                         std::swap(ry1, ry2);
1380                         }
1381
1382                 slope = ry2 - ry1;
1383                 if (slope != 0.0) slope = (rx2 - rx1) / slope;
1384                 for (y = ry1; y < ry2; y += 1.0)
1385                         {
1386                         px = static_cast<gint>(rx1 + (y - ry1) * slope + 0.5);
1387                         py = static_cast<gint>(y + 0.5);
1388
1389                         if (px >=  cx1 && px < cx2 && py >= cy1 && py < cy2)
1390                                 {
1391                                 pp = p_pix + py * prs + px * p_step;
1392                                 *pp = (r * a + *pp * (256-a)) >> 8;
1393                                 pp++;
1394                                 *pp = (g * a + *pp * (256-a)) >> 8;
1395                                 pp++;
1396                                 *pp = (b * a + *pp * (256-a)) >> 8;
1397                                 }
1398                         }
1399                 }
1400 }
1401
1402 /*
1403  *-----------------------------------------------------------------------------
1404  * pixbuf drawing (fades and shadows)
1405  *-----------------------------------------------------------------------------
1406  */
1407
1408 /**
1409  * @brief Composites a horizontal or vertical linear gradient into the rectangular
1410  *        region defined by corners `(x1, y1)` and `(x2, y2)`.  Note that the
1411  *        current implementation breaks if the max distance between `s` and
1412  *        `x1/x2/y1/y2` is greater than `border`.
1413  * @param p_pix The pixel buffer to paint into.
1414  * @param prs The pixel row stride (how many pixels per row of the buffer).
1415  * @param has_alpha TRUE if the p_pix representation is rgba.  FALSE if just rgb.
1416  * @param s The "center" of the gradient, along the axis defined by `vertical`.
1417  *          Note that if the center is not along an edge, the gradient will be
1418  *          symmetric about the center.
1419  * @param vertical When `TRUE`, the gradient color will vary vertically.  When `FALSE`,
1420  *                 horizontally.
1421  * @param border The maximum extent of the gradient, in pixels.
1422  * @param x1,y1 Coordinates of the first corner of the region.
1423  * @param x2,y2 Coordinates of the second corner of the region.
1424  * @param r,g,b Base color of the gradient.
1425  * @param a The peak alpha value when compositing the gradient.  The alpha varies
1426  *          from this value down to 0 (fully transparent).  Note that any alpha
1427  *          value associated with the original pixel is unmodified.
1428  */
1429 static void pixbuf_draw_fade_linear(guchar *p_pix, gint prs, gboolean has_alpha,
1430                                     gint s, gboolean vertical, gint border,
1431                                     gint x1, gint y1, gint x2, gint y2,
1432                                     guint8 r, guint8 g, guint8 b, guint8 a)
1433 {
1434         guchar *pp;
1435         gint p_step;
1436         guint8 n = a;
1437         gint i;
1438         gint j;
1439
1440         p_step = (has_alpha) ? 4 : 3;
1441         for (j = y1; j < y2; j++)
1442                 {
1443                 pp = p_pix + j * prs + x1 * p_step;
1444                 if (!vertical) n = a - a * abs(j - s) / border;
1445                 for (i = x1; i < x2; i++)
1446                         {
1447                         if (vertical) n = a - a * abs(i - s) / border;
1448                         pp[0] = (r * n + pp[0] * (256-n)) >> 8;
1449                         pp[1] = (g * n + pp[1] * (256-n)) >> 8;
1450                         pp[2] = (b * n + pp[2] * (256-n)) >> 8;
1451                         pp += p_step;
1452                         }
1453                 }
1454 }
1455
1456 /**
1457  * @brief Composites a radial gradient into the rectangular region defined by
1458  *        corners `(x1, y1)` and `(x2, y2)`.
1459  * @param p_pix The pixel buffer to paint into.
1460  * @param prs The pixel row stride (how many pixels per row of the buffer).
1461  * @param has_alpha TRUE if the p_pix representation is rgba.  FALSE if just rgb.
1462  * @param sx,sy The coordinates of the center of the gradient.
1463  * @param border The max radius, in pixels, of the gradient.  Pixels farther away
1464  *               from the center than this will be unaffected.
1465  * @param x1,y1 Coordinates of the first corner of the region.
1466  * @param x2,y2 Coordinates of the second corner of the region.
1467  * @param r,g,b Base color of the gradient.
1468  * @param a The peak alpha value when compositing the gradient.  The alpha varies
1469  *          from this value down to 0 (fully transparent).  Note that any alpha
1470  *          value associated with the original pixel is unmodified.
1471  */
1472 static void pixbuf_draw_fade_radius(guchar *p_pix, gint prs, gboolean has_alpha,
1473                                     gint sx, gint sy, gint border,
1474                                     gint x1, gint y1, gint x2, gint y2,
1475                                     guint8, guint8 g, guint8 b, guint8 a)
1476 {
1477         // TODO(xsdg): r (red) was shadowed by r (radius), and was removed from
1478         // the params list by an automated cleanup.  Fix this and distinguish the
1479         // red param from the radius temporary variable.
1480         guchar *pp;
1481         gint p_step;
1482         gint i;
1483         gint j;
1484
1485         p_step = (has_alpha) ? 4 : 3;
1486         for (j = y1; j < y2; j++)
1487                 {
1488                 pp = p_pix + j * prs + x1 * p_step;
1489                 for (i = x1; i < x2; i++)
1490                         {
1491                         guint8 n;
1492                         gint r;
1493
1494                         r = MIN(border, (gint)hypot(i - sx, j - sy));
1495                         n = a - a * r / border;
1496                         pp[0] = (r * n + pp[0] * (256-n)) >> 8;
1497                         pp[1] = (g * n + pp[1] * (256-n)) >> 8;
1498                         pp[2] = (b * n + pp[2] * (256-n)) >> 8;
1499                         pp += p_step;
1500                         }
1501                 }
1502 }
1503
1504 void pixbuf_draw_shadow(GdkPixbuf *pb,
1505                         gint clip_x, gint clip_y, gint clip_w, gint clip_h,
1506                         gint x, gint y, gint w, gint h, gint border,
1507                         guint8 r, guint8 g, guint8 b, guint8 a)
1508 {
1509         gint has_alpha;
1510         gint pw;
1511         gint ph;
1512         gint prs;
1513         gint rx;
1514         gint ry;
1515         gint rw;
1516         gint rh;
1517         gint fx;
1518         gint fy;
1519         gint fw;
1520         gint fh;
1521         guchar *p_pix;
1522
1523         if (!pb) return;
1524
1525         pw = gdk_pixbuf_get_width(pb);
1526         ph = gdk_pixbuf_get_height(pb);
1527
1528         // Intersects the clip region with the pixbuf. r{x,y,w,h} is that
1529         // intersecting region.
1530         if (!util_clip_region(0, 0, pw, ph,
1531                               clip_x, clip_y, clip_w, clip_h,
1532                               &rx, &ry, &rw, &rh)) return;
1533
1534         has_alpha = gdk_pixbuf_get_has_alpha(pb);
1535         prs = gdk_pixbuf_get_rowstride(pb);
1536         p_pix = gdk_pixbuf_get_pixels(pb);
1537
1538         // Composites the specified color into the rectangle specified by x, y, w, h,
1539         // as contracted by `border` pixels, with a composition fraction that's defined
1540         // by the supplied `a` parameter.
1541         if (util_clip_region(x + border, y + border, w - border * 2, h - border * 2,
1542                              rx, ry, rw, rh,
1543                              &fx, &fy, &fw, &fh))
1544                 {
1545                 pixbuf_draw_rect_fill(pb, fx, fy, fw, fh, r, g, b, a);
1546                 }
1547
1548         if (border < 1) return;
1549
1550         // Draws linear gradients along each of the 4 edges.
1551         if (util_clip_region(x, y + border, border, h - border * 2,
1552                              rx, ry, rw, rh,
1553                              &fx, &fy, &fw, &fh))
1554                 {
1555                 pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
1556                                         x + border, TRUE, border,
1557                                         fx, fy, fx + fw, fy + fh,
1558                                         r, g, b, a);
1559                 }
1560         if (util_clip_region(x + w - border, y + border, border, h - border * 2,
1561                              rx, ry, rw, rh,
1562                              &fx, &fy, &fw, &fh))
1563                 {
1564                 pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
1565                                         x + w - border, TRUE, border,
1566                                         fx, fy, fx + fw, fy + fh,
1567                                         r, g, b, a);
1568                 }
1569         if (util_clip_region(x + border, y, w - border * 2, border,
1570                              rx, ry, rw, rh,
1571                              &fx, &fy, &fw, &fh))
1572                 {
1573                 pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
1574                                         y + border, FALSE, border,
1575                                         fx, fy, fx + fw, fy + fh,
1576                                         r, g, b, a);
1577                 }
1578         if (util_clip_region(x + border, y + h - border, w - border * 2, border,
1579                              rx, ry, rw, rh,
1580                              &fx, &fy, &fw, &fh))
1581                 {
1582                 pixbuf_draw_fade_linear(p_pix, prs, has_alpha,
1583                                         y + h - border, FALSE, border,
1584                                         fx, fy, fx + fw, fy + fh,
1585                                         r, g, b, a);
1586                 }
1587         // Draws radial gradients at each of the 4 corners.
1588         if (util_clip_region(x, y, border, border,
1589                              rx, ry, rw, rh,
1590                              &fx, &fy, &fw, &fh))
1591                 {
1592                 pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
1593                                         x + border, y + border, border,
1594                                         fx, fy, fx + fw, fy + fh,
1595                                         r, g, b, a);
1596                 }
1597         if (util_clip_region(x + w - border, y, border, border,
1598                              rx, ry, rw, rh,
1599                              &fx, &fy, &fw, &fh))
1600                 {
1601                 pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
1602                                         x + w - border, y + border, border,
1603                                         fx, fy, fx + fw, fy + fh,
1604                                         r, g, b, a);
1605                 }
1606         if (util_clip_region(x, y + h - border, border, border,
1607                              rx, ry, rw, rh,
1608                              &fx, &fy, &fw, &fh))
1609                 {
1610                 pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
1611                                         x + border, y + h - border, border,
1612                                         fx, fy, fx + fw, fy + fh,
1613                                         r, g, b, a);
1614                 }
1615         if (util_clip_region(x + w - border, y + h - border, border, border,
1616                              rx, ry, rw, rh,
1617                              &fx, &fy, &fw, &fh))
1618                 {
1619                 pixbuf_draw_fade_radius(p_pix, prs, has_alpha,
1620                                         x + w - border, y + h - border, border,
1621                                         fx, fy, fx + fw, fy + fh,
1622                                         r, g, b, a);
1623                 }
1624 }
1625
1626
1627 /*
1628  *-----------------------------------------------------------------------------
1629  * pixbuf color alterations
1630  *-----------------------------------------------------------------------------
1631  */
1632
1633 void pixbuf_desaturate_rect(GdkPixbuf *pb,
1634                             gint x, gint y, gint w, gint h)
1635 {
1636         gboolean has_alpha;
1637         gint pw;
1638         gint ph;
1639         gint prs;
1640         guchar *p_pix;
1641         guchar *pp;
1642         gint i;
1643         gint j;
1644
1645         if (!pb) return;
1646
1647         pw = gdk_pixbuf_get_width(pb);
1648         ph = gdk_pixbuf_get_height(pb);
1649
1650         if (x < 0 || x + w > pw) return;
1651         if (y < 0 || y + h > ph) return;
1652
1653         has_alpha = gdk_pixbuf_get_has_alpha(pb);
1654         prs = gdk_pixbuf_get_rowstride(pb);
1655         p_pix = gdk_pixbuf_get_pixels(pb);
1656
1657         const gint p_step = has_alpha ? 4 : 3;
1658
1659         for (i = 0; i < h; i++)
1660                 {
1661                 pp = p_pix + (y + i) * prs + (x * p_step);
1662                 for (j = 0; j < w; j++)
1663                         {
1664                         guint8 grey;
1665
1666                         grey = (pp[0] + pp[1] + pp[2]) / 3;
1667                         pp[0] = grey;
1668                         pp[1] = grey;
1669                         pp[2] = grey;
1670                         pp += p_step;
1671                         }
1672                 }
1673 }
1674
1675 /*
1676  *-----------------------------------------------------------------------------
1677  * pixbuf highlight under/over exposure
1678  *-----------------------------------------------------------------------------
1679  */
1680 void pixbuf_highlight_overunderexposed(GdkPixbuf *pb, gint x, gint y, gint w, gint h)
1681 {
1682         gboolean has_alpha;
1683         gint pw;
1684         gint ph;
1685         gint prs;
1686         guchar *p_pix;
1687         guchar *pp;
1688         gint i;
1689         gint j;
1690
1691         if (!pb) return;
1692
1693         pw = gdk_pixbuf_get_width(pb);
1694         ph = gdk_pixbuf_get_height(pb);
1695
1696         if (x < 0 || x + w > pw) return;
1697         if (y < 0 || y + h > ph) return;
1698
1699         has_alpha = gdk_pixbuf_get_has_alpha(pb);
1700         prs = gdk_pixbuf_get_rowstride(pb);
1701         p_pix = gdk_pixbuf_get_pixels(pb);
1702
1703         const gint p_step = has_alpha ? 4 : 3;
1704
1705         for (i = 0; i < h; i++)
1706                 {
1707                 pp = p_pix + (y + i) * prs + (x * p_step);
1708                 for (j = 0; j < w; j++)
1709                         {
1710                         if (pp[0] == 255 || pp[1] == 255 || pp[2] == 255 || pp[0] == 0 || pp[1] == 0 || pp[2] == 0)
1711                                 {
1712                                 pp[0] = 255;
1713                                 pp[1] = 0;
1714                                 pp[2] = 0;
1715                                 }
1716                         pp += p_step;
1717                         }
1718                 }
1719 }
1720
1721 /*
1722  *-----------------------------------------------------------------------------
1723  * pixbuf ignore alpha
1724  *-----------------------------------------------------------------------------
1725 */
1726 void pixbuf_ignore_alpha_rect(GdkPixbuf *pb,
1727                               gint x, gint y, gint w, gint h)
1728 {
1729    gboolean has_alpha;
1730    gint pw;
1731    gint ph;
1732    gint prs;
1733    guchar *p_pix;
1734    guchar *pp;
1735    gint i;
1736    gint j;
1737
1738    if (!pb) return;
1739
1740    pw = gdk_pixbuf_get_width(pb);
1741    ph = gdk_pixbuf_get_height(pb);
1742
1743    if (x < 0 || x + w > pw) return;
1744    if (y < 0 || y + h > ph) return;
1745
1746    has_alpha = gdk_pixbuf_get_has_alpha(pb);
1747    if (!has_alpha) return;
1748
1749    prs = gdk_pixbuf_get_rowstride(pb);
1750    p_pix = gdk_pixbuf_get_pixels(pb);
1751
1752    for (i = 0; i < h; i++)
1753        {
1754        pp = p_pix + (y + i) * prs + (x * 4 );
1755        for (j = 0; j < w; j++)
1756            {
1757            pp[3] = 0xff;
1758            pp+=4;
1759            }
1760        }
1761 }
1762 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */