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