Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
cairo-templates.h
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
8 *
9 * Copyright (C) 2010 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#ifndef SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
14#define SEEN_INKSCAPE_DISPLAY_CAIRO_TEMPLATES_H
15
16#include <glib.h>
17
18// single-threaded operation if the number of pixels is below this threshold
19static const int POOL_THRESHOLD = 2048;
20
21#include <algorithm>
22#include <cairo.h>
23#include <cmath>
24
25#include "display/cairo-utils.h"
27#include "display/nr-3dutils.h"
28#include "display/threading.h"
29
30template <typename T>
32 int stride;
33 T *data;
34
36 {
37 stride = cairo_image_surface_get_stride(surface) / sizeof(T);
38 data = reinterpret_cast<T *>(cairo_image_surface_get_data(surface));
39 }
40
41 guint32 get(int x, int y) const
42 {
43 if constexpr (sizeof(T) == 1) {
44 return data[y * stride + x] << 24;
45 } else {
46 return data[y * stride + x];
47 }
48 }
49
50 void set(int x, int y, guint32 value)
51 {
52 if constexpr (sizeof(T) == 1) {
53 data[y * stride + x] = value >> 24;
54 } else {
55 data[y * stride + x] = value;
56 }
57 }
58};
59
60template <typename AccOut, typename Acc1, typename Acc2, typename Blend>
62{
63 surface_accessor<AccOut> acc_out(out);
64 surface_accessor<Acc1> acc_in1(in1);
65 surface_accessor<Acc2> acc_in2(in2);
66
67 // NOTE
68 // This probably doesn't help much here.
69 // It would be better to render more than 1 tile at a time.
70 auto const pool = get_global_dispatch_pool();
71 pool->dispatch_threshold(h, (w * h) > POOL_THRESHOLD, [&](int i, int) {
72 for (int j = 0; j < w; ++j) {
73 acc_out.set(j, i, blend(acc_in1.get(j, i), acc_in2.get(j, i)));
74 }
75 });
76}
77
78template <typename AccOut, typename AccIn, typename Filter>
79void ink_cairo_surface_filter_internal(cairo_surface_t *out, cairo_surface_t *in, int w, int h, Filter &filter)
80{
81 surface_accessor<AccOut> acc_out(out);
82 surface_accessor<AccIn> acc_in(in);
83
84 // NOTE
85 // This probably doesn't help much here.
86 // It would be better to render more than 1 tile at a time.
87 auto const pool = get_global_dispatch_pool();
88 pool->dispatch_threshold(h, (w * h) > POOL_THRESHOLD, [&](int i, int) {
89 for (int j = 0; j < w; ++j) {
90 acc_out.set(j, i, filter(acc_in.get(j, i)));
91 }
92 });
93}
94
95template <typename AccOut, typename Synth>
96void ink_cairo_surface_synthesize_internal(cairo_surface_t *out, int x0, int y0, int x1, int y1, Synth &synth)
97{
98 surface_accessor<AccOut> acc_out(out);
99
100 // NOTE
101 // This probably doesn't help much here.
102 // It would be better to render more than 1 tile at a time.
103 int const limit = (x1 - x0) * (y1 - y0);
104 auto const pool = get_global_dispatch_pool();
105 pool->dispatch_threshold(y1 - y0, limit > POOL_THRESHOLD, [&](int y, int) {
106 int const i = y0 + y;
107
108 for (int j = x0; j < x1; ++j) {
109 acc_out.set(j, i, synth(j, i));
110 }
111 });
112}
113
121template <typename Blend>
123{
124 cairo_surface_flush(in1);
125 cairo_surface_flush(in2);
126
127 // ASSUMPTIONS
128 // 1. Cairo ARGB32 surface strides are always divisible by 4
129 // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
130 // 3. Both surfaces are of the same size
131 // 4. Output surface is ARGB32 if at least one input is ARGB32
132
133 int w = cairo_image_surface_get_width(in2);
134 int h = cairo_image_surface_get_height(in2);
135 int bpp1 = cairo_image_surface_get_format(in1) == CAIRO_FORMAT_A8 ? 1 : 4;
136 int bpp2 = cairo_image_surface_get_format(in2) == CAIRO_FORMAT_A8 ? 1 : 4;
137
138 if (bpp1 == 4 && bpp2 == 4) {
139 ink_cairo_surface_blend_internal<guint32, guint32, guint32>(out, in1, in2, w, h, blend);
140 } else if (bpp1 == 4 && bpp2 == 1) {
141 ink_cairo_surface_blend_internal<guint32, guint32, guint8>(out, in1, in2, w, h, blend);
142 } else if (bpp1 == 1 && bpp2 == 4) {
143 ink_cairo_surface_blend_internal<guint32, guint8, guint32>(out, in1, in2, w, h, blend);
144 } else /* if (bpp1 == 1 && bpp2 == 1) */ {
145 ink_cairo_surface_blend_internal<guint8, guint8, guint8>(out, in1, in2, w, h, blend);
146 }
147
148 cairo_surface_mark_dirty(out);
149}
150
151template <typename Filter>
153{
154 cairo_surface_flush(in);
155
156 // ASSUMPTIONS
157 // 1. Cairo ARGB32 surface strides are always divisible by 4
158 // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
159 // 3. Surfaces have the same dimensions
160
161 int w = cairo_image_surface_get_width(in);
162 int h = cairo_image_surface_get_height(in);
163 int bppin = cairo_image_surface_get_format(in) == CAIRO_FORMAT_A8 ? 1 : 4;
164 int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
165
166 if (bppin == 4 && bppout == 4) {
167 ink_cairo_surface_filter_internal<guint32, guint32>(out, in, w, h, filter);
168 } else if (bppin == 4 && bppout == 1) {
169 // we use this path with COLORMATRIX_LUMINANCETOALPHA
170 ink_cairo_surface_filter_internal<guint8, guint32>(out, in, w, h, filter);
171 } else if (bppin == 1 && bppout == 4) {
172 // used in COLORMATRIX_MATRIX when in is NR_FILTER_SOURCEALPHA
173 ink_cairo_surface_filter_internal<guint32, guint8>(out, in, w, h, filter);
174 } else /* if (bppin == 1 && bppout == 1) */ {
175 ink_cairo_surface_filter_internal<guint8, guint8>(out, in, w, h, filter);
176 }
177
178 cairo_surface_mark_dirty(out);
179}
180
189template <typename Synth>
190void ink_cairo_surface_synthesize(cairo_surface_t *out, cairo_rectangle_t const &out_area, Synth &&synth)
191{
192 // ASSUMPTIONS
193 // 1. Cairo ARGB32 surface strides are always divisible by 4
194 // 2. We can only receive CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_A8 surfaces
195
196 int x0 = out_area.x, x1 = out_area.x + out_area.width;
197 int y0 = out_area.y, y1 = out_area.y + out_area.height;
198 int bppout = cairo_image_surface_get_format(out) == CAIRO_FORMAT_A8 ? 1 : 4;
199
200 if (bppout == 4) {
201 ink_cairo_surface_synthesize_internal<guint32>(out, x0, y0, x1, y1, synth);
202 } else /* if (bppout == 1) */ {
203 ink_cairo_surface_synthesize_internal<guint8>(out, x0, y0, x1, y1, synth);
204 }
205
206 cairo_surface_mark_dirty(out);
207}
208
209template <typename Synth>
211{
212 int w = cairo_image_surface_get_width(out);
213 int h = cairo_image_surface_get_height(out);
214
215 cairo_rectangle_t area;
216 area.x = 0;
217 area.y = 0;
218 area.width = w;
219 area.height = h;
220
221 ink_cairo_surface_synthesize(out, area, synth);
222}
223
226 : _px(cairo_image_surface_get_data(surface))
227 , _w(cairo_image_surface_get_width(surface))
228 , _h(cairo_image_surface_get_height(surface))
229 , _stride(cairo_image_surface_get_stride(surface))
230 , _alpha(cairo_surface_get_content(surface) == CAIRO_CONTENT_ALPHA)
231 {
232 cairo_surface_flush(surface);
233 }
234
235 guint32 pixelAt(int x, int y) const {
236 if (_alpha) {
237 unsigned char *px = _px + y*_stride + x;
238 return *px << 24;
239 } else {
240 unsigned char *px = _px + y*_stride + x*4;
241 return *reinterpret_cast<guint32*>(px);
242 }
243 }
244 guint32 alphaAt(int x, int y) const {
245 if (_alpha) {
246 unsigned char *px = _px + y*_stride + x;
247 return *px;
248 } else {
249 unsigned char *px = _px + y*_stride + x*4;
250 guint32 p = *reinterpret_cast<guint32*>(px);
251 return (p & 0xff000000) >> 24;
252 }
253 }
254
255 // retrieve a pixel value with bilinear interpolation
256 guint32 pixelAt(double x, double y) const {
257 if (_alpha) {
258 return alphaAt(x, y) << 24;
259 }
260
261 double xf = floor(x), yf = floor(y);
262 int xi = xf, yi = yf;
263 guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
264 guint32 p00, p01, p10, p11;
265
266 unsigned char *pxi = _px + yi*_stride + xi*4;
267 guint32 *pxu = reinterpret_cast<guint32*>(pxi);
268 guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
269 p00 = *pxu; p10 = *(pxu + 1);
270 p01 = *pxl; p11 = *(pxl + 1);
271
272 guint32 comp[4];
273
274 for (unsigned i = 0; i < 4; ++i) {
275 guint32 shift = i*8;
276 guint32 mask = 0xff << shift;
277 guint32 c00 = (p00 & mask) >> shift;
278 guint32 c10 = (p10 & mask) >> shift;
279 guint32 c01 = (p01 & mask) >> shift;
280 guint32 c11 = (p11 & mask) >> shift;
281
282 guint32 iu = (255-xif) * c00 + xif * c10;
283 guint32 il = (255-xif) * c01 + xif * c11;
284 comp[i] = (255-yif) * iu + yif * il;
285 comp[i] = (comp[i] + (255*255/2)) / (255*255);
286 }
287
288 guint32 result = comp[0] | (comp[1] << 8) | (comp[2] << 16) | (comp[3] << 24);
289 return result;
290 }
291
292 // retrieve an alpha value with bilinear interpolation
293 guint32 alphaAt(double x, double y) const {
294 double xf = floor(x), yf = floor(y);
295 int xi = xf, yi = yf;
296 guint32 xif = round((x - xf) * 255), yif = round((y - yf) * 255);
297 guint32 p00, p01, p10, p11;
298 if (_alpha) {
299 unsigned char *pxu = _px + yi*_stride + xi;
300 unsigned char *pxl = pxu + _stride;
301 p00 = *pxu; p10 = *(pxu + 1);
302 p01 = *pxl; p11 = *(pxl + 1);
303 } else {
304 unsigned char *pxi = _px + yi*_stride + xi*4;
305 guint32 *pxu = reinterpret_cast<guint32*>(pxi);
306 guint32 *pxl = reinterpret_cast<guint32*>(pxi + _stride);
307 p00 = (*pxu & 0xff000000) >> 24; p10 = (*(pxu + 1) & 0xff000000) >> 24;
308 p01 = (*pxl & 0xff000000) >> 24; p11 = (*(pxl + 1) & 0xff000000) >> 24;
309 }
310 guint32 iu = (255-xif) * p00 + xif * p10;
311 guint32 il = (255-xif) * p01 + xif * p11;
312 guint32 result = (255-yif) * iu + yif * il;
313 result = (result + (255*255/2)) / (255*255);
314 return result;
315 }
316
317 // compute surface normal at given coordinates using 3x3 Sobel gradient filter
318 NR::Fvector surfaceNormalAt(int x, int y, double scale) const {
319 // Below there are some multiplies by zero. They will be optimized out.
320 // Do not remove them, because they improve readability.
321 // NOTE: fetching using alphaAt is slightly lazy.
322 NR::Fvector normal;
323 double fx = -scale/255.0, fy = -scale/255.0;
324 normal[Z_3D] = 1.0;
325 if (G_UNLIKELY(x == 0)) {
326 // leftmost column
327 if (G_UNLIKELY(y == 0)) {
328 // upper left corner
329 fx *= (2.0/3.0);
330 fy *= (2.0/3.0);
331 double p00 = alphaAt(x,y), p10 = alphaAt(x+1, y),
332 p01 = alphaAt(x,y+1), p11 = alphaAt(x+1, y+1);
333 normal[X_3D] =
334 -2.0 * p00 +2.0 * p10
335 -1.0 * p01 +1.0 * p11;
336 normal[Y_3D] =
337 -2.0 * p00 -1.0 * p10
338 +2.0 * p01 +1.0 * p11;
339 } else if (G_UNLIKELY(y == (_h - 1))) {
340 // lower left corner
341 fx *= (2.0/3.0);
342 fy *= (2.0/3.0);
343 double p00 = alphaAt(x,y-1), p10 = alphaAt(x+1, y-1),
344 p01 = alphaAt(x,y ), p11 = alphaAt(x+1, y);
345 normal[X_3D] =
346 -1.0 * p00 +1.0 * p10
347 -2.0 * p01 +2.0 * p11;
348 normal[Y_3D] =
349 -2.0 * p00 -1.0 * p10
350 +2.0 * p01 +1.0 * p11;
351 } else {
352 // leftmost column
353 fx *= (1.0/2.0);
354 fy *= (1.0/3.0);
355 double p00 = alphaAt(x, y-1), p10 = alphaAt(x+1, y-1),
356 p01 = alphaAt(x, y ), p11 = alphaAt(x+1, y ),
357 p02 = alphaAt(x, y+1), p12 = alphaAt(x+1, y+1);
358 normal[X_3D] =
359 -1.0 * p00 +1.0 * p10
360 -2.0 * p01 +2.0 * p11
361 -1.0 * p02 +1.0 * p12;
362 normal[Y_3D] =
363 -2.0 * p00 -1.0 * p10
364 +0.0 * p01 +0.0 * p11 // this will be optimized out
365 +2.0 * p02 +1.0 * p12;
366 }
367 } else if (G_UNLIKELY(x == (_w - 1))) {
368 // rightmost column
369 if (G_UNLIKELY(y == 0)) {
370 // top right corner
371 fx *= (2.0/3.0);
372 fy *= (2.0/3.0);
373 double p00 = alphaAt(x-1,y), p10 = alphaAt(x, y),
374 p01 = alphaAt(x-1,y+1), p11 = alphaAt(x, y+1);
375 normal[X_3D] =
376 -2.0 * p00 +2.0 * p10
377 -1.0 * p01 +1.0 * p11;
378 normal[Y_3D] =
379 -1.0 * p00 -2.0 * p10
380 +1.0 * p01 +2.0 * p11;
381 } else if (G_UNLIKELY(y == (_h - 1))) {
382 // bottom right corner
383 fx *= (2.0/3.0);
384 fy *= (2.0/3.0);
385 double p00 = alphaAt(x-1,y-1), p10 = alphaAt(x, y-1),
386 p01 = alphaAt(x-1,y ), p11 = alphaAt(x, y);
387 normal[X_3D] =
388 -1.0 * p00 +1.0 * p10
389 -2.0 * p01 +2.0 * p11;
390 normal[Y_3D] =
391 -1.0 * p00 -2.0 * p10
392 +1.0 * p01 +2.0 * p11;
393 } else {
394 // rightmost column
395 fx *= (1.0/2.0);
396 fy *= (1.0/3.0);
397 double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1),
398 p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ),
399 p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1);
400 normal[X_3D] =
401 -1.0 * p00 +1.0 * p10
402 -2.0 * p01 +2.0 * p11
403 -1.0 * p02 +1.0 * p12;
404 normal[Y_3D] =
405 -1.0 * p00 -2.0 * p10
406 +0.0 * p01 +0.0 * p11
407 +1.0 * p02 +2.0 * p12;
408 }
409 } else {
410 // interior
411 if (G_UNLIKELY(y == 0)) {
412 // top row
413 fx *= (1.0/3.0);
414 fy *= (1.0/2.0);
415 double p00 = alphaAt(x-1, y ), p10 = alphaAt(x, y ), p20 = alphaAt(x+1, y ),
416 p01 = alphaAt(x-1, y+1), p11 = alphaAt(x, y+1), p21 = alphaAt(x+1, y+1);
417 normal[X_3D] =
418 -2.0 * p00 +0.0 * p10 +2.0 * p20
419 -1.0 * p01 +0.0 * p11 +1.0 * p21;
420 normal[Y_3D] =
421 -1.0 * p00 -2.0 * p10 -1.0 * p20
422 +1.0 * p01 +2.0 * p11 +1.0 * p21;
423 } else if (G_UNLIKELY(y == (_h - 1))) {
424 // bottom row
425 fx *= (1.0/3.0);
426 fy *= (1.0/2.0);
427 double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
428 p01 = alphaAt(x-1, y ), p11 = alphaAt(x, y ), p21 = alphaAt(x+1, y );
429 normal[X_3D] =
430 -1.0 * p00 +0.0 * p10 +1.0 * p20
431 -2.0 * p01 +0.0 * p11 +2.0 * p21;
432 normal[Y_3D] =
433 -1.0 * p00 -2.0 * p10 -1.0 * p20
434 +1.0 * p01 +2.0 * p11 +1.0 * p21;
435 } else {
436 // interior pixels
437 // note: p11 is actually unused, so we don't fetch its value
438 fx *= (1.0/4.0);
439 fy *= (1.0/4.0);
440 double p00 = alphaAt(x-1, y-1), p10 = alphaAt(x, y-1), p20 = alphaAt(x+1, y-1),
441 p01 = alphaAt(x-1, y ), p11 = 0.0, p21 = alphaAt(x+1, y ),
442 p02 = alphaAt(x-1, y+1), p12 = alphaAt(x, y+1), p22 = alphaAt(x+1, y+1);
443 normal[X_3D] =
444 -1.0 * p00 +0.0 * p10 +1.0 * p20
445 -2.0 * p01 +0.0 * p11 +2.0 * p21
446 -1.0 * p02 +0.0 * p12 +1.0 * p22;
447 normal[Y_3D] =
448 -1.0 * p00 -2.0 * p10 -1.0 * p20
449 +0.0 * p01 +0.0 * p11 +0.0 * p21
450 +1.0 * p02 +2.0 * p12 +1.0 * p22;
451 }
452 }
453 normal[X_3D] *= fx;
454 normal[Y_3D] *= fy;
455 NR::normalize_vector(normal);
456 return normal;
457 }
458
459 unsigned char *_px;
460 int _w, _h, _stride;
461 bool _alpha;
462};
463
464// Some helpers for pixel manipulation
465G_GNUC_CONST inline gint32
467 // NOTE: it is possible to write a "branchless" clamping operation.
468 // However, it will be slower than this function, because the code below
469 // is compiled to conditional moves.
470 if (v < low) return low;
471 if (v > high) return high;
472 return v;
473}
474
475#endif
476/*
477 Local Variables:
478 mode:c++
479 c-file-style:"stroustrup"
480 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
481 indent-tabs-mode:nil
482 fill-column:99
483 End:
484*/
485// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
G_GNUC_CONST gint32 pxclamp(gint32 v, gint32 low, gint32 high)
void ink_cairo_surface_synthesize_internal(cairo_surface_t *out, int x0, int y0, int x1, int y1, Synth &synth)
void ink_cairo_surface_filter(cairo_surface_t *in, cairo_surface_t *out, Filter &&filter)
void ink_cairo_surface_blend(cairo_surface_t *in1, cairo_surface_t *in2, cairo_surface_t *out, Blend &&blend)
Blend two surfaces using the supplied functor.
void ink_cairo_surface_synthesize(cairo_surface_t *out, cairo_rectangle_t const &out_area, Synth &&synth)
Synthesize surface pixels based on their position.
static const int POOL_THRESHOLD
void ink_cairo_surface_filter_internal(cairo_surface_t *out, cairo_surface_t *in, int w, int h, Filter &filter)
void ink_cairo_surface_blend_internal(cairo_surface_t *out, cairo_surface_t *in1, cairo_surface_t *in2, int w, int h, Blend &blend)
Cairo integration helpers.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
const double w
Definition conic-4.cpp:19
Css & result
unsigned int guint32
struct _cairo_surface cairo_surface_t
auto floor(Geom::Rect const &rect)
Definition geom.h:131
void shift(T &a, T &b, T const &c)
void normalize_vector(Fvector &v)
Normalizes a vector.
signed int gint32
a type of 3 gdouble components vectors
Definition nr-3dutils.h:29
guint32 alphaAt(int x, int y) const
guint32 alphaAt(double x, double y) const
NR::Fvector surfaceNormalAt(int x, int y, double scale) const
unsigned char * _px
guint32 pixelAt(int x, int y) const
SurfaceSynth(cairo_surface_t *surface)
guint32 pixelAt(double x, double y) const
surface_accessor(cairo_surface_t *surface)
void set(int x, int y, guint32 value)
guint32 get(int x, int y) const