1 /*
2  * Xournal++
3  *
4  * Pixbuf utils
5  *
6  * Copied from F-Spot, part copied from GTK3
7  */
8 
9 /* f-pixbuf-utils.c
10  *
11  * Copyright (C) 2001, 2002, 2003 The Free Software Foundation, Inc.
12  * Copyright (C) 2003 Ettore Perazzoli
13  *
14  * This program is free software; you can redistribute it and/or
15  * modify it under the terms of the GNU General Public License as
16  * published by the Free Software Foundation; either version 2 of the
17  * License, or (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22  * General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public
25  * License along with this program; if not, write to the
26  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27  * Boston, MA 02111-1307, USA.
28  *
29  * Author: Paolo Bacchilega <paolo.bacch@tin.it>
30  *
31  * Adapted by Ettore Perazzoli <ettore@perazzoli.org>
32  */
33 
34 /* Some bits are based upon the GIMP source code, the original copyright
35  * note follows:
36  *
37  * The GIMP -- an image manipulation program
38  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
39  *
40  */
41 
42 #include "pixbuf-utils.h"
43 
44 #include <cerrno>
45 #include <cmath>
46 #include <cstdio>
47 
48 #include <gdk/gdk.h>
49 
50 const cairo_user_data_key_t pixel_key = {0};
51 const cairo_user_data_key_t format_key = {0};
52 
f_image_surface_create(cairo_format_t format,int width,int height)53 auto f_image_surface_create(cairo_format_t format, int width, int height) -> cairo_surface_t* {
54     int size = 0;
55 
56     switch (format) {
57         case CAIRO_FORMAT_ARGB32:
58         case CAIRO_FORMAT_RGB24:
59             size = 4;
60             break;
61         case CAIRO_FORMAT_A8:
62             size = 8;
63             break;
64         case CAIRO_FORMAT_A1:
65             size = 1;
66             break;
67         case CAIRO_FORMAT_INVALID:
68         case CAIRO_FORMAT_RGB16_565:
69         case CAIRO_FORMAT_RGB30:
70         default:
71             g_warning("Unsupported image format: %i\n", format);
72             size = 1;
73             break;
74     }
75 
76     auto* pixels = static_cast<unsigned char*>(g_malloc(width * height * size));
77     cairo_surface_t* surface = cairo_image_surface_create_for_data(pixels, format, width, height, width * size);
78 
79     cairo_surface_set_user_data(surface, &pixel_key, pixels, g_free);
80     cairo_surface_set_user_data(surface, &format_key, GINT_TO_POINTER(format), nullptr);
81 
82     return surface;
83 }
84 
f_image_surface_get_data(cairo_surface_t * surface)85 auto f_image_surface_get_data(cairo_surface_t* surface) -> void* {
86     return cairo_surface_get_user_data(surface, &pixel_key);
87 }
88 
f_image_surface_get_format(cairo_surface_t * surface)89 auto f_image_surface_get_format(cairo_surface_t* surface) -> cairo_format_t {
90     return static_cast<cairo_format_t> GPOINTER_TO_INT(cairo_surface_get_user_data(surface, &format_key));
91 }
92 
f_image_surface_get_width(cairo_surface_t * surface)93 auto f_image_surface_get_width(cairo_surface_t* surface) -> int { return cairo_image_surface_get_width(surface); }
94 
f_image_surface_get_height(cairo_surface_t * surface)95 auto f_image_surface_get_height(cairo_surface_t* surface) -> int { return cairo_image_surface_get_height(surface); }
96 
97 /* Public functions.  */
98 
f_pixbuf_to_cairo_surface(GdkPixbuf * pixbuf)99 auto f_pixbuf_to_cairo_surface(GdkPixbuf* pixbuf) -> cairo_surface_t* {
100     gint width = gdk_pixbuf_get_width(pixbuf);
101     gint height = gdk_pixbuf_get_height(pixbuf);
102     guchar* gdk_pixels = gdk_pixbuf_get_pixels(pixbuf);
103     int gdk_rowstride = gdk_pixbuf_get_rowstride(pixbuf);
104     int n_channels = gdk_pixbuf_get_n_channels(pixbuf);
105 
106     cairo_format_t format{};
107     if (n_channels == 3) {
108         format = CAIRO_FORMAT_RGB24;
109     } else {
110         format = CAIRO_FORMAT_ARGB32;
111     }
112 
113     cairo_surface_t* surface = f_image_surface_create(format, width, height);
114     auto* cairo_pixels = static_cast<guchar*>(f_image_surface_get_data(surface));
115 
116     for (int j = height; j; j--) {
117         guchar* p = gdk_pixels;
118         guchar* q = cairo_pixels;
119 
120         if (n_channels == 3) {
121             guchar* end = p + 3 * width;
122 
123             while (p < end) {
124 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
125                 q[0] = p[2];
126                 q[1] = p[1];
127                 q[2] = p[0];
128 #else
129                 q[1] = p[0];
130                 q[2] = p[1];
131                 q[3] = p[2];
132 #endif
133                 p += 3;
134                 q += 4;
135             }
136         } else {
137             guchar* end = p + 4 * width;
138             guint t1 = 0, t2 = 0, t3 = 0;
139 
140             auto MULT = [](auto& d, auto c, auto a, auto& t) {
141                 t = c * a + 0x7f;
142                 d = ((t >> 8U) + t) >> 8U;
143             };
144 
145             while (p < end) {
146                 if constexpr (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
147                     MULT(q[0], p[2], p[3], t1);
148                     MULT(q[1], p[1], p[3], t2);
149                     MULT(q[2], p[0], p[3], t3);
150                     q[3] = p[3];
151                 } else {
152                     q[0] = p[3];
153                     MULT(q[1], p[0], p[3], t1);
154                     MULT(q[2], p[1], p[3], t2);
155                     MULT(q[3], p[2], p[3], t3);
156                 }
157 
158                 p += 4;
159                 q += 4;
160             }
161         }
162 
163         gdk_pixels += gdk_rowstride;
164         cairo_pixels += 4 * width;
165     }
166 
167     return surface;
168 }
169 
170 /**
171  * Source GTK 3
172  */
173 
gdk_cairo_format_for_content(cairo_content_t content)174 static auto gdk_cairo_format_for_content(cairo_content_t content) -> cairo_format_t {
175     switch (content) {
176         case CAIRO_CONTENT_COLOR:
177             return CAIRO_FORMAT_RGB24;
178         case CAIRO_CONTENT_ALPHA:
179             return CAIRO_FORMAT_A8;
180         case CAIRO_CONTENT_COLOR_ALPHA:
181         default:
182             return CAIRO_FORMAT_ARGB32;
183     }
184 }
185 
gdk_cairo_surface_coerce_to_image(cairo_surface_t * surface,cairo_content_t content,int src_x,int src_y,int width,int height)186 static auto gdk_cairo_surface_coerce_to_image(cairo_surface_t* surface, cairo_content_t content, int src_x, int src_y,
187                                               int width, int height) -> cairo_surface_t* {
188     cairo_surface_t* copy = cairo_image_surface_create(gdk_cairo_format_for_content(content), width, height);
189 
190     cairo_t* cr = cairo_create(copy);
191     cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
192     cairo_set_source_surface(cr, surface, -src_x, -src_y);
193     cairo_paint(cr);
194     cairo_destroy(cr);
195 
196     return copy;
197 }
198 
convert_alpha(guchar * dest_data,int dest_stride,guchar * src_data,int src_stride,int src_x,int src_y,int width,int height)199 static void convert_alpha(guchar* dest_data, int dest_stride, guchar* src_data, int src_stride, int src_x, int src_y,
200                           int width, int height) {
201     src_data += src_stride * src_y + src_x * 4;
202 
203     for (int y = 0; y < height; y++) {
204         auto* src = reinterpret_cast<guint32*>(src_data);
205 
206         for (int x = 0; x < width; x++) {
207             guint alpha = src[x] >> 24;
208 
209             if (alpha == 0) {
210                 dest_data[x * 4 + 0] = 0;
211                 dest_data[x * 4 + 1] = 0;
212                 dest_data[x * 4 + 2] = 0;
213             } else {
214                 dest_data[x * 4 + 0] = (((src[x] & 0xff0000U) >> 16U) * 255 + alpha / 2) / alpha;
215                 dest_data[x * 4 + 1] = (((src[x] & 0x00ff00U) >> 8U) * 255 + alpha / 2) / alpha;
216                 dest_data[x * 4 + 2] = (((src[x] & 0x0000ffU) >> 0U) * 255 + alpha / 2) / alpha;
217             }
218             dest_data[x * 4 + 3] = alpha;
219         }
220 
221         src_data += src_stride;
222         dest_data += dest_stride;
223     }
224 }
225 
convert_no_alpha(guchar * dest_data,int dest_stride,guchar * src_data,int src_stride,int src_x,int src_y,int width,int height)226 static void convert_no_alpha(guchar* dest_data, int dest_stride, guchar* src_data, int src_stride, int src_x, int src_y,
227                              int width, int height) {
228     src_data += src_stride * src_y + src_x * 4;
229 
230     for (int y = 0; y < height; y++) {
231         auto* src = reinterpret_cast<guint32*>(src_data);
232 
233         for (int x = 0; x < width; x++) {
234             dest_data[x * 3 + 0] = src[x] >> 16U;
235             dest_data[x * 3 + 1] = src[x] >> 8U;
236             dest_data[x * 3 + 2] = src[x];
237         }
238 
239         src_data += src_stride;
240         dest_data += dest_stride;
241     }
242 }
243 
244 /**
245  * gdk_pixbuf_get_from_surface:
246  * @surface: surface to copy from
247  * @src_x: Source X coordinate within @surface
248  * @src_y: Source Y coordinate within @surface
249  * @width: Width in pixels of region to get
250  * @height: Height in pixels of region to get
251  *
252  * Transfers image data from a #cairo_surface_t and converts it to an RGB(A)
253  * representation inside a #GdkPixbuf. This allows you to efficiently read
254  * individual pixels from cairo surfaces. For #GdkWindows, use
255  * gdk_pixbuf_get_from_window() instead.
256  *
257  * This function will create an RGB pixbuf with 8 bits per channel.
258  * The pixbuf will contain an alpha channel if the @surface contains one.
259  *
260  * Return value: (transfer full): A newly-created pixbuf with a reference
261  *     count of 1, or %nullptr on error
262  */
xoj_pixbuf_get_from_surface(cairo_surface_t * surface,gint src_x,gint src_y,gint width,gint height)263 auto xoj_pixbuf_get_from_surface(cairo_surface_t* surface, gint src_x, gint src_y, gint width, gint height)
264         -> GdkPixbuf* {
265     /* General sanity checks */
266     g_return_val_if_fail(surface != nullptr, nullptr);
267     g_return_val_if_fail(width > 0 && height > 0, nullptr);
268 
269     auto content = static_cast<cairo_content_t>(cairo_surface_get_content(surface) | CAIRO_CONTENT_COLOR);
270     GdkPixbuf* dest = gdk_pixbuf_new(GDK_COLORSPACE_RGB, !!(content & CAIRO_CONTENT_ALPHA), 8, width, height);
271 
272     surface = gdk_cairo_surface_coerce_to_image(surface, content, src_x, src_y, width, height);
273     cairo_surface_flush(surface);
274     if (cairo_surface_status(surface) || dest == nullptr) {
275         cairo_surface_destroy(surface);
276         return nullptr;
277     }
278 
279     if (gdk_pixbuf_get_has_alpha(dest)) {
280         convert_alpha(gdk_pixbuf_get_pixels(dest), gdk_pixbuf_get_rowstride(dest),
281                       cairo_image_surface_get_data(surface), cairo_image_surface_get_stride(surface), 0, 0, width,
282                       height);
283     } else {
284         convert_no_alpha(gdk_pixbuf_get_pixels(dest), gdk_pixbuf_get_rowstride(dest),
285                          cairo_image_surface_get_data(surface), cairo_image_surface_get_stride(surface), 0, 0, width,
286                          height);
287     }
288 
289     cairo_surface_destroy(surface);
290     return dest;
291 }
292