Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
nr-filter-morphology.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * feMorphology filter primitive renderer
4 *
5 * Authors:
6 * Felipe Corrêa da Silva Sanches <juca@members.fsf.org>
7 *
8 * Copyright (C) 2007 authors
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <cmath>
14#include <algorithm>
15#include <deque>
16#include <functional>
18#include "display/cairo-utils.h"
22
23namespace Inkscape {
24namespace Filters {
25
27
29
35
36namespace {
37
38/* This performs one "half" of the morphology operation by calculating
39 * the componentwise extreme in the specified axis with the given radius.
40 * Extreme of row extremes is equal to the extreme of components, so this
41 * doesn't change the result.
42 * The algorithm is due to: Petr Dokládal, Eva Dokládalová (2011), "Computationally efficient, one-pass algorithm for morphological filters"
43 * TODO: Currently only the 1D algorithm is implemented, but it should not be too difficult (and at the very least more memory efficient) to implement the full 2D algorithm.
44 * One problem with the 2D algorithm is that it is harder to parallelize.
45 */
46template <typename Comparison, Geom::Dim2 axis, int BPP>
47void morphologicalFilter1D(cairo_surface_t * const input, cairo_surface_t * const out, double radius)
48{
49 Comparison comp;
50
51 int w = cairo_image_surface_get_width(out);
52 int h = cairo_image_surface_get_height(out);
53 if (axis == Geom::Y) std::swap(w,h);
54
55 int stridein = cairo_image_surface_get_stride(input);
56 int strideout = cairo_image_surface_get_stride(out);
57
58 unsigned char *in_data = cairo_image_surface_get_data(input);
59 unsigned char *out_data = cairo_image_surface_get_data(out);
60
61 int ri = round(radius); // TODO: Support fractional radii?
62 int wi = 2*ri+1;
63
64 int const limit = w * h;
65 auto const pool = get_global_dispatch_pool();
66 pool->dispatch_threshold(h, limit > POOL_THRESHOLD, [&](int i, int) {
67 // TODO: Store position and value in one 32 bit integer? 24 bits should be enough for a position, it would be quite strange to have an image with a width/height of more than 16 million(!).
68 std::deque<std::pair<int, unsigned char>> vals[BPP]; // In my tests it was actually slightly faster to allocate it here than allocate it once for all threads and retrieving the correct set based on the thread id.
69
70 // Initialize with transparent black
71 for (int p = 0; p < BPP; ++p) {
72 vals[p].emplace_back(-1, 0); // TODO: Only do this when performing an erosion?
73 }
74
75 // Process "row"
76 unsigned char *in_p = in_data + i * (axis == Geom::X ? stridein : BPP);
77 unsigned char *out_p = out_data + i * (axis == Geom::X ? strideout : BPP);
78 /* This is the "short but slow" version, which might be easier to follow, the longer but faster version follows.
79 for (int j = 0; j < w+ri; ++j) {
80 for(int p = 0; p < BPP; ++p) { // Iterate over channels
81 // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
82 if (!vals[p].empty() && vals[p].front().first+wi <= j) vals[p].pop_front(); // out-of-range
83 if (j < w) {
84 while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
85 vals[p].emplace_back(j, *in_p);
86 ++in_p;
87 } else if (j == w) { // Transparent black beyond the image. TODO: Only do this when performing an erosion?
88 while(!vals[p].empty() && !comp(vals[p].back().second, 0)) vals[p].pop_back();
89 vals[p].emplace_back(j, 0);
90 }
91 // Set output
92 if (j >= ri) {
93 *out_p = vals[p].front().second;
94 ++out_p;
95 }
96 }
97 if (axis == Geom::Y && j < w ) in_p += stridein - BPP;
98 if (axis == Geom::Y && j >= ri) out_p += strideout - BPP;
99 }*/
100 for (int j = 0; j < std::min(ri,w); ++j) {
101 for(int p = 0; p < BPP; ++p) { // Iterate over channels
102 // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
103 if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
104 while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
105 vals[p].emplace_back(j + wi, *in_p);
106 ++in_p;
107 }
108 if (axis == Geom::Y) in_p += stridein - BPP;
109 }
110 // We have now done all preparatory work.
111 // If w<=ri, then the following loop does nothing (which is as it should).
112 for (int j = ri; j < w; ++j) {
113 for(int p = 0; p < BPP; ++p) { // Iterate over channels
114 // Push new value onto FIFO, erasing any previous values that are "useless" (see paper) or out-of-range
115 if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
116 while(!vals[p].empty() && !comp(vals[p].back().second, *in_p)) vals[p].pop_back(); // useless
117 vals[p].emplace_back(j + wi, *in_p);
118 ++in_p;
119 // Set output
120 *out_p = vals[p].front().second;
121 ++out_p;
122 }
123 if (axis == Geom::Y) {
124 in_p += stridein - BPP;
125 out_p += strideout - BPP;
126 }
127 }
128 // We have now done all work which involves both input and output.
129 // The following loop makes sure that the border is handled correctly.
130 for(int p = 0; p < BPP; ++p) { // Iterate over channels
131 while(!vals[p].empty() && !comp(vals[p].back().second, 0)) vals[p].pop_back();
132 vals[p].emplace_back(w + wi, 0);
133 }
134 // Now we just have to finish the output.
135 for (int j = std::max(w,ri); j < w+ri; ++j) {
136 for(int p = 0; p < BPP; ++p) { // Iterate over channels
137 // Remove out-of-range values
138 if (!vals[p].empty() && vals[p].front().first <= j) vals[p].pop_front(); // out-of-range
139 // Set output
140 *out_p = vals[p].front().second;
141 ++out_p;
142 }
143 if (axis == Geom::Y) out_p += strideout - BPP;
144 }
145 });
146
147 cairo_surface_mark_dirty(out);
148}
149
150} // namespace
151
153{
154 cairo_surface_t *input = slot.getcairo(_input);
155
156 if (xradius == 0.0 || yradius == 0.0) {
157 // output is transparent black
159 copy_cairo_surface_ci(input, out);
160 slot.set(_output, out);
161 cairo_surface_destroy(out);
162 return;
163 }
164
165 int device_scale = slot.get_device_scale();
167 double xr = fabs(xradius * p2pb.expansionX()) * device_scale;
168 double yr = fabs(yradius * p2pb.expansionY()) * device_scale;
169 int bpp = cairo_image_surface_get_format(input) == CAIRO_FORMAT_A8 ? 1 : 4;
170
172
174 if (bpp == 1) {
175 morphologicalFilter1D< std::greater<unsigned char>, Geom::X, 1 >(input, interm, xr);
176 } else {
177 morphologicalFilter1D< std::greater<unsigned char>, Geom::X, 4 >(input, interm, xr);
178 }
179 } else {
180 if (bpp == 1) {
181 morphologicalFilter1D< std::less<unsigned char>, Geom::X, 1 >(input, interm, xr);
182 } else {
183 morphologicalFilter1D< std::less<unsigned char>, Geom::X, 4 >(input, interm, xr);
184 }
185 }
186
188
189 // color_interpolation_filters for out same as input. See spec (DisplacementMap).
190 copy_cairo_surface_ci(input, out);
191
193 if (bpp == 1) {
194 morphologicalFilter1D< std::greater<unsigned char>, Geom::Y, 1 >(interm, out, yr);
195 } else {
196 morphologicalFilter1D< std::greater<unsigned char>, Geom::Y, 4 >(interm, out, yr);
197 }
198 } else {
199 if (bpp == 1) {
200 morphologicalFilter1D< std::less<unsigned char>, Geom::Y, 1 >(interm, out, yr);
201 } else {
202 morphologicalFilter1D< std::less<unsigned char>, Geom::Y, 4 >(interm, out, yr);
203 }
204 }
205
206 cairo_surface_destroy(interm);
207
208 slot.set(_output, out);
209 cairo_surface_destroy(out);
210}
211
213{
214 int enlarge_x = std::ceil(xradius * trans.expansionX());
215 int enlarge_y = std::ceil(yradius * trans.expansionY());
216
217 area.expandBy(enlarge_x, enlarge_y);
218}
219
221{
222 int enlarge_x = std::ceil(xradius * trans.expansionX());
223 int enlarge_y = std::ceil(yradius * trans.expansionY());
224 return enlarge_x * enlarge_y;
225}
226
231
233{
234 xradius = x;
235}
236
238{
239 yradius = y;
240}
241
242} /* namespace Filters */
243} /* namespace Inkscape */
244
245/*
246 Local Variables:
247 mode:c++
248 c-file-style:"stroustrup"
249 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
250 indent-tabs-mode:nil
251 fill-column:99
252 End:
253*/
254// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Cairo software blending templates.
static const int POOL_THRESHOLD
void copy_cairo_surface_ci(cairo_surface_t *in, cairo_surface_t *out)
cairo_surface_t * ink_cairo_surface_create_identical(cairo_surface_t *s)
Create a surface that differs only in pixel content.
Cairo integration helpers.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord expansionX() const
Calculates the amount of x-scaling imparted by the Affine.
Definition affine.cpp:64
Coord expansionY() const
Calculates the amount of y-scaling imparted by the Affine.
Definition affine.cpp:71
Axis aligned, non-empty, generic rectangle.
void expandBy(C amount)
Expand the rectangle in both directions by the specified amount.
void set_operator(FilterMorphologyOperator o)
double complexity(Geom::Affine const &ctm) const override
void area_enlarge(Geom::IntRect &area, Geom::Affine const &trans) const override
void render_cairo(FilterSlot &slot) const override
cairo_surface_t * getcairo(int slot)
Returns the pixblock in specified slot.
void set(int slot, cairo_surface_t *s)
Sets or re-sets the pixblock associated with given slot.
int get_device_scale() const
Gets the device scale; for high DPI monitors.
FilterUnits const & get_units() const
Geom::Affine get_matrix_primitiveunits2pb() const
Gets the primitiveUnits to pixblock coordinates transformation matrix.
const double w
Definition conic-4.cpp:19
Operator
Definition cr-term.h:64
struct _cairo_surface cairo_surface_t
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Helper class to stream background task notifications as a series of messages.
std::shared_ptr< dispatch_pool > get_global_dispatch_pool()
Definition threading.cpp:31