Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
drawing-pattern.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
6 * Authors:
7 * Tomasz Boczkowski <penginsbacon@gmail.com>
8 *
9 * Copyright (C) 2014 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <cairomm/region.h>
14#include <cairo.h>
15#include "cairo-utils.h"
17#include "drawing-context.h"
18#include "drawing-pattern.h"
19#include "drawing-surface.h"
20#include "drawing.h"
21#include "helper/geom.h"
22#include "ui/util.h"
23
24namespace Inkscape {
25
26auto constexpr PATTERN_MATRIX_EPSILON = 1e-18;
27
28DrawingPattern::Surface::Surface(Geom::IntRect const &rect, int device_scale)
29 : rect(rect)
30 , surface(Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, rect.width() * device_scale, rect.height() * device_scale))
31{
32 cairo_surface_set_device_scale(surface->cobj(), device_scale, device_scale);
33}
34
40
42{
43 defer([=, this] {
46 return;
47 }
49 _pattern_to_user = transform.isIdentity(PATTERN_MATRIX_EPSILON) ? nullptr : std::make_unique<Geom::Affine>(transform);
51 });
52}
53
55{
56 defer([=, this] {
57 _tile_rect = tile_rect;
59 });
60}
61
62void DrawingPattern::setOverflow(Geom::Affine const &initial_transform, int steps, Geom::Affine const &step_transform)
63{
64 defer([=, this] {
65 _overflow_initial_transform = initial_transform;
66 _overflow_steps = steps;
67 _overflow_step_transform = step_transform;
68 });
69}
70
71cairo_pattern_t *DrawingPattern::renderPattern(RenderContext &rc, Geom::IntRect const &area, float opacity, int device_scale) const
72{
73 if (opacity < 1e-3) {
74 // Invisible.
75 return nullptr;
76 }
77
79 // Empty.
80 return nullptr;
81 }
82
83 // Calculate various transforms.
84 auto const dt = Geom::Translate(-_tile_rect->min()) * Geom::Scale(_pattern_resolution / _tile_rect->dimensions()); // AKA user_to_tile.
85 if (dt.isSingular(PATTERN_MATRIX_EPSILON)) {
86 return nullptr;
87 }
88 auto const idt = dt.inverse();
89 auto const pattern_to_tile = _pattern_to_user ? _pattern_to_user->inverse() * dt : dt;
90 auto const screen_to_tile = _ctm.inverse() * pattern_to_tile;
91
92 // Return a canonical choice of rectangle with the same periodic tiling as rect.
93 auto canonicalised = [&, this] (Geom::IntRect rect) {
94 for (int i = 0; i < 2; i++) {
95 if (rect.dimensions()[i] >= _pattern_resolution[i]) {
96 rect[i] = {0, _pattern_resolution[i]};
97 } else {
98 rect[i] -= Util::round_down(rect[i].min(), _pattern_resolution[i]);
99 }
100 }
101 return rect;
102 };
103
104 // Return whether the periodic tiling of a contains the periodic tiling of b.
105 auto wrapped_contains = [&] (Geom::IntRect const &a, Geom::IntRect const &b) {
106 auto check = [&] (int i) {
107 int const period = _pattern_resolution[i];
108 if (a[i].extent() >= period) return true;
109 if (b[i].extent() > a[i].extent()) return false;
110 return Util::round_down(b[i].min() - a[i].min(), period) >= b[i].max() - a[i].max();
111 };
112 return check(0) && check(1);
113 };
114
115 // Return whether the periodic tiling of a intersects with or touches the periodic tiling of b.
116 auto wrapped_touches = [&] (Geom::IntRect const &a, Geom::IntRect const &b) {
117 auto check = [&] (int i) {
118 int const period = _pattern_resolution[i];
119 if (a[i].extent() >= period) return true;
120 if (b[i].extent() >= period) return true;
121 return Util::round_down(b[i].max() - a[i].min(), period) >= b[i].min() - a[i].max();
122 };
123 return check(0) && check(1);
124 };
125
126 // Calculate the minimum and maximum translates of a that overlap with b.
127 auto overlapping_translates = [&, this] (Geom::IntRect const &a, Geom::IntRect const &b) {
128 Geom::IntPoint min, max;
129 for (int i = 0; i < 2; i++) {
130 min[i] = Util::round_up (b[i].min() - a[i].max() + 1, _pattern_resolution[i]);
131 max[i] = Util::round_down(b[i].max() - a[i].min() - 1, _pattern_resolution[i]);
132 }
133 return std::make_pair(min, max);
134 };
135
136 // Paint the periodic tiling of a into b, and remove the painted region from dirty.
137 auto wrapped_paint = [&, this] (Surface const &a, Geom::IntRect &b, Cairo::RefPtr<Cairo::Context> const &cr, Cairo::RefPtr<Cairo::Region> const &dirty) {
138 auto const [min, max] = overlapping_translates(a.rect, b);
139 for (int x = min.x(); x <= max.x(); x += _pattern_resolution.x()) {
140 for (int y = min.y(); y <= max.y(); y += _pattern_resolution.y()) {
141 auto const rect = a.rect + Geom::IntPoint(x, y);
142 dirty->subtract(geom_to_cairo(rect));
143 cr->set_source(a.surface, rect.left(), rect.top());
144 cr->paint();
145 }
146 }
147 };
148
149 // Calculate the requested area to draw within tile rasterisation space.
150 auto const area_orig = (Geom::Rect(area) * screen_to_tile).roundOutwards();
151 auto const area_tile = canonicalised(area_orig);
152
153 // Simplest solution for now to protecting pattern cache is a mutex. This makes all
154 // pattern rendering single-threaded, however patterns are typically not the bottleneck.
155 auto lock = std::lock_guard(mutables);
156
157 auto get_surface = [&, this] () -> std::pair<Surface*, Cairo::RefPtr<Cairo::Region>> {
158 // If there is a rectangle containing the requested area, just use that.
159 for (auto &s : surfaces) {
160 if (wrapped_contains(s.rect, area_tile)) {
161 return { &s, {} };
162 }
163 }
164
165 // Otherwise, recursively merge the requested area with all overlapping or touching rectangles, and paint the missing part.
166 std::vector<Surface> merged;
167 auto expanded = area_tile;
168
169 while (true) {
170 bool modified = false;
171
172 for (auto it = surfaces.begin(); it != surfaces.end(); ) {
173 if (wrapped_touches(expanded, it->rect)) {
174 expanded.unionWith(it->rect + round_down(expanded.max() - it->rect.min(), _pattern_resolution));
175 merged.emplace_back(std::move(*it));
176 *it = std::move(surfaces.back());
177 surfaces.pop_back();
178 modified = true;
179 } else {
180 ++it;
181 }
182 }
183
184 if (!modified) break;
185 }
186
187 // Canonicalise the expanded rectangle. (Stops Cairo's coordinates overflowing and the pattern disappearing.)
188 expanded = canonicalised(expanded);
189
190 // Create a new surface covering the expanded rectangle.
191 auto surface = Surface(expanded, device_scale);
192 auto cr = Cairo::Context::create(surface.surface);
193 cr->translate(-surface.rect.left(), -surface.rect.top());
194
195 // Paste all the old surfaces into the new surface, tracking the remaining dirty region.
196 auto dirty = Cairo::Region::create(geom_to_cairo(expanded));
197
198 for (auto &m : merged) {
199 wrapped_paint(m, expanded, cr, dirty);
200 }
201
202 // Emplace the surface, and return it along with the remaining dirty region.
203 surfaces.emplace_back(std::move(surface));
204 return std::make_pair(&surfaces.back(), std::move(dirty));
205 };
206
207 // Find an already-drawn surface containing the requested area, or create if it none exists.
208 auto [surface, dirty] = get_surface();
209
210 // Draw the pattern contents to the dirty areas of the surface, taking care of possible wrapping.
211 Inkscape::DrawingContext dc(surface->surface->cobj(), surface->rect.min());
212 if (rc.antialiasing_override) {
213 apply_antialias(dc, rc.antialiasing_override.value());
214 }
215
216 auto paint = [&, this] (Geom::IntRect const &rect) {
217 if (_overflow_steps == 1) {
218 render(dc, rc, rect);
219 } else {
220 // Overflow transforms need to be transformed to the old coordinate system
221 // before stretching to the pattern resolution.
222 auto const initial_transform = idt * _overflow_initial_transform * dt;
223 auto const step_transform = idt * _overflow_step_transform * dt;
224 dc.transform(initial_transform);
225 for (int i = 0; i < _overflow_steps; i++) {
226 // render() fails to handle transforms applied here when using cache.
227 render(dc, rc, rect, RENDER_BYPASS_CACHE);
228 dc.transform(step_transform);
229 // auto raw = pattern_surface.raw();
230 // auto filename = "drawing-pattern" + std::to_string(i) + ".png";
231 // cairo_surface_write_to_png(pattern_surface.raw(), filename.c_str());
232 }
233 }
234 };
235
236 if (dirty) {
237 for (int i = 0; i < dirty->get_num_rectangles(); i++) {
238 auto const rect = cairo_to_geom(dirty->get_rectangle(i));
239 for (int x = 0; x <= 1; x++) {
240 for (int y = 0; y <= 1; y++) {
241 auto const wrap = _pattern_resolution * Geom::IntPoint(x, y);
242 auto const rect2 = rect & Geom::IntRect(wrap, wrap + _pattern_resolution);
243 if (!rect2) continue;
244 auto save = DrawingContext::Save(dc);
245 // Clip to rectangle to be drawn.
246 dc.rectangle(*rect2);
247 dc.clip();
248 // Draw the pattern.
249 dc.translate(wrap);
250 paint(*rect2 - wrap);
251 // Apply opacity, if necessary.
252 if (opacity < 1.0 - 1e-3) {
253 dc.setOperator(CAIRO_OPERATOR_DEST_IN);
254 dc.setSource(0.0, 0.0, 0.0, opacity);
255 dc.paint();
256 }
257 }
258 }
259 }
260 dirty.reset();
261 }
262
263 // Debug: Show pattern tile.
264 // surface->surface->write_to_png("/tmp/patternsurface.png");
265
266 // Create and return pattern.
267 auto cp = cairo_pattern_create_for_surface(surface->surface->cobj());
268 auto const shift = surface->rect.min() + round_down(area_orig.min() - surface->rect.min(), _pattern_resolution);
269 ink_cairo_pattern_set_matrix(cp, pattern_to_tile * Geom::Translate(-shift));
270 cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
271 if (rc.antialiasing_override && rc.antialiasing_override.value() == Antialiasing::None) {
272 cairo_pattern_set_filter(cp, CAIRO_FILTER_NEAREST);
273 }
274 return cp;
275}
276
277unsigned DrawingPattern::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
278{
280
281 if (!_tile_rect || _tile_rect->hasZeroArea()) {
282 return STATE_NONE;
283 }
284
285 // Calculate the desired resolution of a pattern tile.
286 double const det_ctm = ctx.ctm.det();
287 double const det_ps2user = _pattern_to_user ? _pattern_to_user->det() : 1.0;
288 double scale = std::sqrt(std::abs(det_ctm * det_ps2user));
289 // Fixme: When scale is too big (zooming in a pattern), Cairo doesn't render the pattern.
290 // More precisely it fails when setting pattern matrix in DrawingPattern::renderPattern.
291 // Correct solution should make use of visible area and change pattern tile rect accordingly.
292 auto const c = _tile_rect->dimensions() * scale;
293 _pattern_resolution = c.ceil();
294
295 // Map tile rect to the origin and stretch it to the desired resolution.
296 auto const dt = Geom::Translate(-_tile_rect->min()) * Geom::Scale(_pattern_resolution / _tile_rect->dimensions());
297
298 // Apply this transform to the actual pattern tree.
299 return DrawingGroup::_updateItem(Geom::IntRect::infinite(), { dt }, flags, reset);
300}
301
303{
304 surfaces.clear();
305}
306
307} // namespace Inkscape
308
309/*
310 Local Variables:
311 mode:c++
312 c-file-style:"stroustrup"
313 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
314 indent-tabs-mode:nil
315 fill-column:99
316 End:
317*/
318// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
void ink_cairo_pattern_set_matrix(cairo_pattern_t *cp, Geom::Affine const &m)
Cairo integration helpers.
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord det() const
Calculate the determinant.
Definition affine.cpp:416
bool isIdentity(Coord eps=EPSILON) const
Check whether this matrix is an identity matrix.
Definition affine.cpp:109
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
bool hasZeroArea() const
Check whether the rectangle has zero area.
Axis aligned, non-empty, generic rectangle.
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
static CRect infinite()
Create infinite rectangle.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Two-dimensional point with integer coordinates.
Definition int-point.h:57
constexpr IntCoord x() const noexcept
Definition int-point.h:77
constexpr IntCoord y() const noexcept
Definition int-point.h:79
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
RAII idiom for saving the state of DrawingContext.
Minimal wrapper over Cairo.
void setSource(cairo_pattern_t *source)
void transform(Geom::Affine const &trans)
void paint(double alpha=1.0)
void rectangle(Geom::Rect const &r)
void setOperator(cairo_operator_t op)
void translate(Geom::Point const &t)
unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) override
unsigned render(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &area, unsigned flags=0, DrawingItem const *stop_at=nullptr) const
Rasterize items.
Drawing & drawing() const
void _markForUpdate(unsigned state, bool propagate)
Marks the item as needing a recomputation of internal data.
Geom::Affine _ctm
Total transform from item coords to display coords.
void _markForRendering()
Marks the current visual bounding box of the item for redrawing.
Geom::Affine transform() const
void _dropPatternCache() override
void setTileRect(Geom::Rect const &)
Set the tile rect position and dimensions in content coordinate system.
void setPatternToUserTransform(Geom::Affine const &)
Set the transformation from pattern to user coordinate systems.
std::unique_ptr< Geom::Affine > _pattern_to_user
std::vector< Surface > surfaces
void setOverflow(Geom::Affine const &initial_transform, int steps, Geom::Affine const &step_transform)
Turn on overflow rendering.
unsigned _updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset) override
Geom::Affine _overflow_step_transform
Geom::Affine _overflow_initial_transform
cairo_pattern_t * renderPattern(RenderContext &rc, Geom::IntRect const &area, float opacity, int device_scale) const
Render the pattern.
DrawingPattern(Drawing &drawing)
Geom::IntPoint _pattern_resolution
RectangularCluster rc
double c[8][4]
static char const *const current
Definition dir-util.cpp:71
Cairo drawing context with Inkscape extensions.
Canvas item belonging to an SVG drawing element.
Canvas belonging to SVG pattern.
_cairo_pattern cairo_pattern_t
Cairo surface that remembers its origin.
SVG drawing for display.
Specific geometry functions for Inkscape, not provided my lib2geom.
auto round_down(Geom::IntPoint const &a, Geom::IntPoint const &b)
Definition geom.h:60
void shift(T &a, T &b, T const &c)
Control handle rendering/caching.
GenericRect< IntCoord > IntRect
Definition forward.h:57
Affine identity()
Create an identity matrix.
Definition affine.h:210
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
T constexpr round_down(T a, T b)
Returns a rounded down to the nearest multiple of b, assuming b >= 1.
Definition mathfns.h:82
T constexpr round_up(T a, T b)
Returns a rounded up to the nearest multiple of b, assuming b >= 1.
Definition mathfns.h:89
Helper class to stream background task notifications as a series of messages.
void apply_antialias(DrawingContext &dc, Antialiasing antialias)
Apply antialias setting to Cairo.
auto constexpr PATTERN_MATRIX_EPSILON
Cairo::RefPtr< Cairo::ImageSurface > surface
Surface(Geom::IntRect const &rect, int device_scale)
double height
double width
std::unique_ptr< Toolbar >(* create)()
Definition toolbars.cpp:56
Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect)
Definition util.cpp:352
Geom::IntRect cairo_to_geom(const Cairo::RectangleInt &rect)
Definition util.cpp:357