Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
cairographics.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
3#include "ui/util.h"
4#include "cairographics.h"
5#include "stores.h"
6#include "prefs.h"
7#include "util.h"
8#include "framecheck.h"
9
10namespace Inkscape::UI::Widget {
11
12CairoGraphics::CairoGraphics(Prefs const &prefs, Stores const &stores, PageInfo const &pi)
13 : prefs(prefs)
14 , stores(stores)
15 , pi(pi) {}
16
17std::unique_ptr<Graphics> Graphics::create_cairo(Prefs const &prefs, Stores const &stores, PageInfo const &pi)
18{
19 return std::make_unique<CairoGraphics>(prefs, stores, pi);
20}
21
23{
24 outlines_enabled = enabled;
25 if (!enabled) {
26 store.outline_surface.reset();
28 }
29}
30
32{
33 auto surface_size = dims * scale_factor;
34
35 auto make_surface = [&, this] {
36 auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, surface_size.x(), surface_size.y());
37 cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); // No C++ API!
38 return surface;
39 };
40
41 // Recreate the store surface.
42 bool reuse_surface = store.surface && dimensions(store.surface) == surface_size;
43 if (!reuse_surface) {
44 store.surface = make_surface();
45 }
46
47 // Ensure the store surface is filled with the correct default background.
49 auto cr = Cairo::Context::create(store.surface);
51 } else if (reuse_surface) {
52 auto cr = Cairo::Context::create(store.surface);
53 cr->set_operator(Cairo::Context::Operator::CLEAR);
54 cr->paint();
55 }
56
57 // Do the same for the outline surface (except always clearing it to transparent).
58 if (outlines_enabled) {
59 bool reuse_outline_surface = store.outline_surface && dimensions(store.outline_surface) == surface_size;
60 if (!reuse_outline_surface) {
61 store.outline_surface = make_surface();
62 } else {
63 auto cr = Cairo::Context::create(store.outline_surface);
64 cr->set_operator(Cairo::Context::Operator::CLEAR);
65 cr->paint();
66 }
67 }
68}
69
71{
72 auto surface_size = dest.rect.dimensions() * scale_factor;
73
74 // Determine the geometry of the shift.
75 auto shift = dest.rect.min() - stores.store().rect.min();
76 auto reuse_rect = (dest.rect & cairo_to_geom(stores.store().drawn->get_extents())).regularized();
77 assert(reuse_rect); // Should not be called if there is no overlap.
78
79 auto make_surface = [&, this] {
80 auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, surface_size.x(), surface_size.y());
81 cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor); // No C++ API!
82 return surface;
83 };
84
85 // Create the new store surface.
86 bool reuse_surface = snapshot.surface && dimensions(snapshot.surface) == surface_size;
87 auto new_surface = reuse_surface ? std::move(snapshot.surface) : make_surface();
88
89 // Paint background into region of store not covered by next operation.
90 auto cr = Cairo::Context::create(new_surface);
91 if (background_in_stores || reuse_surface) {
92 auto reg = Cairo::Region::create(geom_to_cairo(dest.rect));
93 reg->subtract(geom_to_cairo(*reuse_rect));
94 reg->translate(-dest.rect.left(), -dest.rect.top());
95 cr->save();
96 region_to_path(cr, reg);
97 cr->clip();
99 paint_background(dest, pi, page, desk, cr);
100 } else { // otherwise, reuse_surface is true
101 cr->set_operator(Cairo::Context::Operator::CLEAR);
102 cr->paint();
103 }
104 cr->restore();
105 }
106
107 // Copy re-usuable contents of old store into new store, shifted.
108 cr->rectangle(reuse_rect->left() - dest.rect.left(), reuse_rect->top() - dest.rect.top(), reuse_rect->width(), reuse_rect->height());
109 cr->clip();
110 cr->set_source(store.surface, -shift.x(), -shift.y());
111 cr->set_operator(Cairo::Context::Operator::SOURCE);
112 cr->paint();
113
114 // Set the result as the new store surface.
115 snapshot.surface = std::move(store.surface);
116 store.surface = std::move(new_surface);
117
118 // Do the same for the outline store
119 if (outlines_enabled) {
120 // Create.
121 bool reuse_outline_surface = snapshot.outline_surface && dimensions(snapshot.outline_surface) == surface_size;
122 auto new_outline_surface = reuse_outline_surface ? std::move(snapshot.outline_surface) : make_surface();
123 // Background.
124 auto cr = Cairo::Context::create(new_outline_surface);
125 if (reuse_outline_surface) {
126 cr->set_operator(Cairo::Context::Operator::CLEAR);
127 cr->paint();
128 }
129 // Copy.
130 cr->rectangle(reuse_rect->left() - dest.rect.left(), reuse_rect->top() - dest.rect.top(), reuse_rect->width(), reuse_rect->height());
131 cr->clip();
132 cr->set_source(store.outline_surface, -shift.x(), -shift.y());
133 cr->set_operator(Cairo::Context::Operator::SOURCE);
134 cr->paint();
135 // Set.
137 store.outline_surface = std::move(new_outline_surface);
138 }
139}
140
142{
143 std::swap(store, snapshot);
144}
145
147{
148 auto copy = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &from,
149 Cairo::RefPtr<Cairo::ImageSurface> const &to) {
150 auto cr = Cairo::Context::create(to);
151 cr->set_antialias(Cairo::ANTIALIAS_NONE);
152 cr->set_operator(Cairo::Context::Operator::SOURCE);
153 cr->translate(-stores.snapshot().rect.left(), -stores.snapshot().rect.top());
155 cr->translate(-1.0, -1.0);
157 cr->translate(1.0, 1.0);
158 cr->clip();
159 cr->set_source(from, stores.store().rect.left(), stores.store().rect.top());
160 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
161 cr->paint();
162 };
163
166}
167
169{
170 // Create the new fragment.
171 auto content_size = dest.rect.dimensions() * scale_factor;
172
173 auto make_surface = [&] {
174 auto result = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, content_size.x(), content_size.y());
175 cairo_surface_set_device_scale(result->cobj(), scale_factor, scale_factor); // No C++ API!
176 return result;
177 };
178
180 fragment.surface = make_surface();
181 if (outlines_enabled) fragment.outline_surface = make_surface();
182
183 auto copy = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &store_from,
184 Cairo::RefPtr<Cairo::ImageSurface> const &snapshot_from,
185 Cairo::RefPtr<Cairo::ImageSurface> const &to, bool background) {
186 auto cr = Cairo::Context::create(to);
187 cr->set_antialias(Cairo::ANTIALIAS_NONE);
188 cr->set_operator(Cairo::Context::Operator::SOURCE);
189 if (background) paint_background(dest, pi, page, desk, cr);
190 cr->translate(-dest.rect.left(), -dest.rect.top());
191 cr->transform(geom_to_cairo(stores.snapshot().affine.inverse() * dest.affine));
193 cr->set_source(snapshot_from, stores.snapshot().rect.left(), stores.snapshot().rect.top());
194 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
195 cr->fill();
197 cr->translate(-1.0, -1.0);
199 cr->translate(1.0, 1.0);
200 cr->clip();
201 cr->set_source(store_from, stores.store().rect.left(), stores.store().rect.top());
202 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
203 cr->paint();
204 };
205
207 if (outlines_enabled) copy(store.outline_surface, snapshot.outline_surface, fragment.outline_surface, false);
208
209 snapshot = std::move(fragment);
210}
211
212Cairo::RefPtr<Cairo::ImageSurface> CairoGraphics::request_tile_surface(Geom::IntRect const &rect, bool /*nogl*/)
213{
214 // Create temporary surface, isolated from store.
215 auto surface = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, rect.width() * scale_factor, rect.height() * scale_factor);
216 cairo_surface_set_device_scale(surface->cobj(), scale_factor, scale_factor);
217 return surface;
218}
219
220void CairoGraphics::draw_tile(Fragment const &fragment, Cairo::RefPtr<Cairo::ImageSurface> surface, Cairo::RefPtr<Cairo::ImageSurface> outline_surface)
221{
222 // Blit from the temporary surface to the store.
223 auto diff = fragment.rect.min() - stores.store().rect.min();
224
225 auto cr = Cairo::Context::create(store.surface);
226 cr->set_operator(Cairo::Context::Operator::SOURCE);
227 cr->set_source(surface, diff.x(), diff.y());
228 cr->rectangle(diff.x(), diff.y(), fragment.rect.width(), fragment.rect.height());
229 cr->fill();
230
231 if (outlines_enabled) {
232 auto cr = Cairo::Context::create(store.outline_surface);
233 cr->set_operator(Cairo::Context::Operator::SOURCE);
234 cr->set_source(outline_surface, diff.x(), diff.y());
235 cr->rectangle(diff.x(), diff.y(), fragment.rect.width(), fragment.rect.height());
236 cr->fill();
237 }
238}
239
240void CairoGraphics::paint_widget(Fragment const &view, PaintArgs const &a, Cairo::RefPtr<Cairo::Context> const &cr)
241{
242 auto f = FrameCheck::Event();
243
244 // Turn off anti-aliasing while compositing the widget for large performance gains. (We can usually
245 // get away with it without any negative visual impact; when we can't, we turn it back on.)
246 cr->set_antialias(Cairo::ANTIALIAS_NONE);
247
248 // Draw background if solid colour optimisation is not enabled. (If enabled, it is baked into the stores.)
250 if (prefs.debug_framecheck) f = FrameCheck::Event("background");
251 paint_background(view, pi, page, desk, cr);
252 }
253
254 // Even if in solid colour mode, draw the part of background that is not going to be rendered.
256 auto const &s = stores.mode() == Stores::Mode::Decoupled ? stores.snapshot() : stores.store();
257 if (!(Geom::Parallelogram(s.rect) * s.affine.inverse() * view.affine).contains(view.rect)) {
258 if (prefs.debug_framecheck) f = FrameCheck::Event("background", 2);
259 cr->save();
260 cr->set_fill_rule(Cairo::Context::FillRule::EVEN_ODD);
261 cr->rectangle(0, 0, view.rect.width(), view.rect.height());
262 cr->translate(-view.rect.left(), -view.rect.top());
263 cr->transform(geom_to_cairo(s.affine.inverse() * view.affine));
264 cr->rectangle(s.rect.left(), s.rect.top(), s.rect.width(), s.rect.height());
265 cr->clip();
266 cr->transform(geom_to_cairo(view.affine.inverse() * s.affine));
267 cr->translate(view.rect.left(), view.rect.top());
268 paint_background(view, pi, page, desk, cr);
269 cr->restore();
270 }
271 }
272
273 auto draw_store = [&, this] (Cairo::RefPtr<Cairo::ImageSurface> const &store, Cairo::RefPtr<Cairo::ImageSurface> const &snapshot_store) {
275 // Blit store to view.
277 cr->save();
278 auto const &r = stores.store().rect;
279 cr->translate(-view.rect.left(), -view.rect.top());
280 cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine)); // Almost always the identity.
281 cr->rectangle(r.left(), r.top(), r.width(), r.height());
282 cr->set_source(store, r.left(), r.top());
283 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
284 cr->fill();
285 cr->restore();
286 } else {
287 // Draw transformed snapshot, clipped to the complement of the store's clean region.
288 if (prefs.debug_framecheck) f = FrameCheck::Event("composite", 1);
289
290 cr->save();
291 cr->set_fill_rule(Cairo::Context::FillRule::EVEN_ODD);
292 cr->rectangle(0, 0, view.rect.width(), view.rect.height());
293 cr->translate(-view.rect.left(), -view.rect.top());
294 cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine));
297 cr->clip();
298 auto const &r = stores.snapshot().rect;
299 cr->rectangle(r.left(), r.top(), r.width(), r.height());
300 cr->clip();
301 cr->set_source(snapshot_store, r.left(), r.top());
302 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
303 cr->paint();
305 cr->set_source_rgba(0, 0, 1, 0.2);
306 cr->set_operator(Cairo::Context::Operator::OVER);
307 cr->paint();
308 }
309 cr->restore();
310
311 // Draw transformed store, clipped to drawn region.
312 if (prefs.debug_framecheck) f = FrameCheck::Event("composite", 0);
313 cr->save();
314 cr->translate(-view.rect.left(), -view.rect.top());
315 cr->transform(geom_to_cairo(stores.store().affine.inverse() * view.affine));
316 cr->set_source(store, stores.store().rect.left(), stores.store().rect.top());
317 Cairo::SurfacePattern(cr->get_source()->cobj()).set_filter(Cairo::SurfacePattern::Filter::FAST);
319 cr->fill();
320 cr->restore();
321 }
322 };
323
324 auto draw_overlay = [&, this] {
325 // Get whitewash opacity.
326 double outline_overlay_opacity = prefs.outline_overlay_opacity / 100.0;
327
328 // Partially obscure drawing by painting semi-transparent white, then paint outline content.
329 // Note: Unfortunately this also paints over the background, but this is unavoidable.
330 cr->save();
331 cr->set_operator(Cairo::Context::Operator::OVER);
332 cr->set_source_rgb(1.0, 1.0, 1.0);
333 cr->paint_with_alpha(outline_overlay_opacity);
335 cr->restore();
336 };
337
339
340 // Calculate the clipping rectangles for split view.
341 auto [store_clip, outline_clip] = calc_splitview_cliprects(view.rect.dimensions(), a.splitfrac, a.splitdir);
342
343 // Draw normal content.
344 cr->save();
345 cr->rectangle(store_clip.left(), store_clip.top(), store_clip.width(), store_clip.height());
346 cr->clip();
347 cr->set_operator(background_in_stores ? Cairo::Context::Operator::SOURCE : Cairo::Context::Operator::OVER);
348 draw_store(store.surface, snapshot.surface);
349 if (a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY) draw_overlay();
350 cr->restore();
351
352 // Draw outline.
354 cr->save();
355 cr->translate(outline_clip.left(), outline_clip.top());
356 paint_background(Fragment{view.affine, view.rect.min() + outline_clip}, pi, page, desk, cr);
357 cr->restore();
358 }
359 cr->save();
360 cr->rectangle(outline_clip.left(), outline_clip.top(), outline_clip.width(), outline_clip.height());
361 cr->clip();
362 cr->set_operator(Cairo::Context::Operator::OVER);
364 cr->restore();
365
366 } else {
367
368 // Draw the normal content over the whole view.
369 cr->set_operator(background_in_stores ? Cairo::Context::Operator::SOURCE : Cairo::Context::Operator::OVER);
370 draw_store(store.surface, snapshot.surface);
371 if (a.render_mode == Inkscape::RenderMode::OUTLINE_OVERLAY) draw_overlay();
372
373 // Draw outline if in X-ray mode.
375 // Clip to circle
376 cr->set_antialias(Cairo::ANTIALIAS_DEFAULT);
377 cr->arc(a.mouse->x(), a.mouse->y(), prefs.xray_radius, 0, 2 * M_PI);
378 cr->clip();
379 cr->set_antialias(Cairo::ANTIALIAS_NONE);
380 // Draw background.
381 paint_background(view, pi, page, desk, cr);
382 // Draw outline.
383 cr->set_operator(Cairo::Context::Operator::OVER);
385 }
386 }
387
388 // The rest can be done with antialiasing.
389 cr->set_antialias(Cairo::ANTIALIAS_DEFAULT);
390
393 }
394}
395
396} // namespace Inkscape::UI::Widget
397
398/*
399 Local Variables:
400 mode:c++
401 c-file-style:"stroustrup"
402 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
403 indent-tabs-mode:nil
404 fill-column:99
405 End:
406*/
407// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Cairo::RefPtr< Cairo::ImageSurface > surface
Definition canvas.cpp:137
Cairo::RefPtr< Cairo::ImageSurface > outline_surface
Definition canvas.cpp:138
Fragment fragment
Definition canvas.cpp:136
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Axis aligned, non-empty, generic rectangle.
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint dimensions() const
Get rectangle's width and height as a point.
Two-dimensional point with integer coordinates.
Definition int-point.h:57
Paralellogram, representing a linear transformation of a rectangle.
CairoGraphics(Prefs const &prefs, Stores const &stores, PageInfo const &pi)
void shift_store(Fragment const &dest) override
Called when the store fragment shifts position to dest.
void fast_snapshot_combine() override
Paste the store onto the snapshot.
void set_outlines_enabled(bool) override
Whether to maintain a second layer of outline content.
void snapshot_combine(Fragment const &dest) override
Paste the snapshot followed by the store onto a new snapshot at dest.
void draw_tile(Fragment const &fragment, Cairo::RefPtr< Cairo::ImageSurface > surface, Cairo::RefPtr< Cairo::ImageSurface > outline_surface) override
Commit the contents of a surface previously issued by request_tile_surface() to the canvas.
void recreate_store(Geom::IntPoint const &dimensions) override
Set the store to a surface of the given size, of unspecified contents.
void paint_widget(Fragment const &view, PaintArgs const &args, Cairo::RefPtr< Cairo::Context > const &cr) override
void swap_stores() override
Exchange the store and snapshot surfaces.
Cairo::RefPtr< Cairo::ImageSurface > request_tile_surface(Geom::IntRect const &rect, bool nogl) override
Return a surface for drawing on.
static void paint_splitview_controller(Geom::IntPoint const &size, Geom::Point const &splitfrac, SplitDirection splitdir, SplitDirection hoverdir, Cairo::RefPtr< Cairo::Context > const &cr)
Definition graphics.cpp:119
static std::unique_ptr< Graphics > create_cairo(Prefs const &prefs, Stores const &stores, PageInfo const &pi)
static void paint_background(Fragment const &fragment, PageInfo const &pi, std::uint32_t page, std::uint32_t desk, Cairo::RefPtr< Cairo::Context > const &cr)
Definition graphics.cpp:54
static std::pair< Geom::IntRect, Geom::IntRect > calc_splitview_cliprects(Geom::IntPoint const &size, Geom::Point const &splitfrac, SplitDirection splitdir)
Definition graphics.cpp:96
Pref< int > outline_overlay_opacity
Definition prefs.h:20
Pref< bool > debug_show_snapshot
Definition prefs.h:53
Pref< bool > debug_framecheck
Definition prefs.h:47
Pref< int > xray_radius
Definition prefs.h:19
@ Normal
Not initialised or just reset; no stores exist yet.
@ Decoupled
Normal mode consisting of just a backing store.
Store const & snapshot() const
Definition stores.h:72
Store const & store() const
Definition stores.h:71
Css & result
void shift(T &a, T &b, T const &c)
Custom widgets.
Definition desktop.h:126
void region_to_path(Cairo::RefPtr< Cairo::Context > const &cr, Cairo::RefPtr< Cairo::Region > const &reg)
Turn a Cairo region into a path on a given Cairo context.
Definition util.cpp:10
Cairo::RefPtr< Cairo::Region > shrink_region(Cairo::RefPtr< Cairo::Region > const &reg, int d, int t)
Shrink a region by d/2 in all directions, while also translating it by (d/2 + t, d/2 + t).
Definition util.cpp:18
Abstraction of the store/snapshot mechanism.
RAII object that logs a timing event for the duration of its lifetime.
Definition framecheck.h:11
Cairo::RefPtr< Cairo::ImageSurface > outline_surface
Cairo::RefPtr< Cairo::ImageSurface > surface
A "fragment" is a rectangle of drawn content at a specfic place.
Definition fragment.h:12
std::optional< Geom::Point > mouse
Definition graphics.h:79
Cairo::RefPtr< Cairo::Region > drawn
The region of space containing drawn content.
Definition stores.h:46
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
Geom::IntPoint dimensions(const Cairo::RefPtr< Cairo::ImageSurface > &surface)
Definition util.cpp:367