Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
canvas-item-rect.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Author:
8 * Tavmjong Bah
9 *
10 * Copyright (C) 2020 Tavmjong Bah
11 *
12 * Rewrite of CtrlRect
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17#include "canvas-item-rect.h"
18
19#include <cairo/cairo.h>
20#include <cairomm/pattern.h>
21
22#include "desktop.h"
23#include "display/cairo-utils.h"
24#include "helper/geom.h"
25#include "ui/util.h"
26#include "ui/widget/canvas.h"
27
28namespace Inkscape {
29
34 : CanvasItem(group)
35{
36 _name = "CanvasItemRect:Null";
37 _fill = 0;
38}
39
44 : CanvasItem(group)
45 , _rect(rect)
46{
47 _name = "CanvasItemRect";
48 _fill = 0;
49}
50
55{
56 defer([=, this] {
57 if (_rect == rect) return;
58 _rect = rect;
60 });
61}
62
66void CanvasItemRect::visit_page_rects(std::function<void(Geom::Rect const &)> const &f) const
67{
68 if (_is_page && _fill != 0) {
69 f(_rect);
70 }
71}
72
77bool CanvasItemRect::contains(Geom::Point const &p, double tolerance)
78{
79 if (tolerance != 0) {
80 std::cerr << "CanvasItemRect::contains: Non-zero tolerance not implemented!" << std::endl;
81 }
82
83 return _rect.contains(p * affine().inverse());
84}
85
90{
91 // Queue redraw of old area (erase previous content).
93
94 // Enlarge bbox by twice shadow size (to allow for shadow on any side with a 45deg rotation).
95 _bounds = _rect;
96 // note: add shadow size before applying transformation, since get_shadow_size accounts for scale
97 if (_shadow_width > 0 && !_dashed) {
98 _bounds->expandBy(2 * get_shadow_size());
99 }
100 *_bounds *= affine();
101
102 // Room for stroke and outline. Not doing the extra adjustment of 2 units
103 // leads to artifacts.
104 _bounds->expandBy(get_effective_outline() / 2 + 2);
105
106 // Queue redraw of new area
108}
109
114{
115 // Are we axis aligned?
116 auto const &aff = affine();
117 bool const axis_aligned = (Geom::are_near(aff[1], 0) && Geom::are_near(aff[2], 0))
118 || (Geom::are_near(aff[0], 0) && Geom::are_near(aff[3], 0));
119
120 // If we are and the effective outline is of odd width then snap the rectangle to the pixel grid.
121 auto rect = _rect;
122 if (axis_aligned) {
123 auto is_odd = static_cast<int>(std::round(get_effective_outline())) & 1;
124 auto shift = is_odd ? Geom::Point(0.5, 0.5) : Geom::Point();
125 rect = (floor(_rect * aff) + shift) * aff.inverse();
126 }
127
128 buf.cr->save();
129 buf.cr->translate(-buf.rect.left(), -buf.rect.top());
130
131 if (_inverted) {
132 cairo_set_operator(buf.cr->cobj(), CAIRO_OPERATOR_DIFFERENCE);
133 }
134
135 // Draw shadow first. Shadow extends under rectangle to reduce aliasing effects. Canvas draws page shadows in OpenGL mode.
136 if (_shadow_width > 0 && !_dashed && !(_is_page && get_canvas()->get_opengl_enabled())) {
137 // There's only one UI knob to adjust border and shadow color, so instead of using border color
138 // transparency as is, it is boosted by this function, since shadow attenuates it.
139 auto const alpha = (std::exp(-3 * SP_RGBA32_A_F(_shadow_color)) - 1) / (std::exp(-3) - 1);
140
141 // Flip shadow upside-down if y-axis is inverted.
142 auto doc2dt = Geom::identity();
143 if (auto desktop = get_canvas()->get_desktop()) {
144 doc2dt = desktop->doc2dt();
145 }
146
147 buf.cr->save();
148 buf.cr->transform(geom_to_cairo(doc2dt * aff));
149 ink_cairo_draw_drop_shadow(buf.cr, rect * doc2dt, get_shadow_size(), _shadow_color, alpha);
150 buf.cr->restore();
151 }
152
153 // Get the points we need transformed into window coordinates.
154 buf.cr->begin_new_path();
155 for (int i = 0; i < 4; ++i) {
156 auto pt = rect.corner(i) * aff;
157 buf.cr->line_to(pt.x(), pt.y());
158 }
159 buf.cr->close_path();
160
161 // Draw border.
162 static std::valarray<double> dashes = {4.0, 4.0};
163 if (_dashed) {
164 buf.cr->set_dash(dashes, -0.5);
165 }
166
167 // We maybe have painted the background, back to "normal" compositing
168
169 // Do outline
170 if (SP_RGBA32_A_U(_outline) > 0 && _outline_width > 0) {
172 buf.cr->set_line_width(get_effective_outline());
173 buf.cr->stroke_preserve();
174 }
175
176 // Do stroke
177 if (SP_RGBA32_A_U(_stroke) > 0 && _stroke_width > 0) {
179 buf.cr->set_line_width(_stroke_width);
180 buf.cr->stroke_preserve();
181 }
182
183 // Draw fill pattern
184 if (_fill_pattern) {
185 buf.cr->set_source(_fill_pattern);
186 buf.cr->fill_preserve();
187 }
188
189 // Draw fill
190 if (SP_RGBA32_A_U(_fill) > 0) {
192 buf.cr->fill_preserve();
193 }
194
195 // Highlight the border by drawing it in _shadow_color.
196 if (_shadow_width == 1 && _dashed) {
197 buf.cr->set_dash(dashes, 3.5); // Dash offset by dash length.
199 buf.cr->stroke_preserve();
200 }
201
202 buf.cr->begin_new_path(); // Clear path or get weird artifacts.
203
204 // Uncomment to show bounds
205 // Geom::Rect bounds = _bounds;
206 // bounds.expandBy(-1);
207 // bounds -= buf.rect.min();
208 // buf.cr->set_source_rgba(1.0, 0.0, _shadow_width / 3.0, 1.0);
209 // buf.cr->rectangle(bounds.min().x(), bounds.min().y(), bounds.width(), bounds.height());
210 // buf.cr->stroke();
211
212 buf.cr->restore();
213}
214
216{
217 defer([=, this] {
218 if (_is_page == is_page) return;
219 _is_page = is_page;
221 });
222}
223
224void CanvasItemRect::set_fill(uint32_t fill)
225{
226 defer([=, this] {
227 if (fill != _fill && _is_page) {
228 get_canvas()->set_page(fill);
229 }
230 _fill = fill;
232 });
233}
234
236{
237 defer([=, this] {
238 if (_dashed == dashed) return;
239 _dashed = dashed;
241 });
242}
243
245{
246 defer([=, this] {
247 if (_inverted == inverted) return;
248 _inverted = inverted;
250 });
251}
252
253void CanvasItemRect::set_shadow(uint32_t color, int width)
254{
255 defer([=, this] {
256 if (_shadow_color == color && _shadow_width == width) return;
257 _shadow_color = color;
260 if (_is_page) get_canvas()->set_border(_shadow_width > 0 ? color : 0x0);
261 });
262}
263
265{
266 // gradient drop shadow needs much more room than solid one, so inflating the size;
267 // fudge factor of 6 used to make sizes baked in svg documents work as steps:
268 // typical value of 2 will work out to 12 pixels which is a narrow shadow (b/c of exponential fall of)
269 auto size = _shadow_width * 6;
270 if (size < 0) {
271 size = 0;
272 } else if (size > 120) {
273 // arbitrarily selected max size, so Cairo gradient doesn't blow up if document has bogus shadow values
274 size = 120;
275 }
276 auto scale = affine().descrim();
277
278 // calculate space for gradient shadow; if divided by 'scale' it would be zoom independent (fixed in size);
279 // if 'scale' is not used, drop shadow will be getting smaller with document zoom;
280 // here hybrid approach is used: "unscaling" with square root of scale allows shadows to diminish
281 // more slowly at small zoom levels (so it's still perceptible) and grow more slowly at high mag (where it doesn't matter, b/c it's typically off-screen)
282 return size / (scale > 0 ? sqrt(scale) : 1);
283}
284} // namespace Inkscape
285
286/*
287 Local Variables:
288 mode:c++
289 c-file-style:"stroustrup"
290 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
291 indent-tabs-mode:nil
292 fill-column:99
293 End:
294*/
295// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
double scale
Definition aa.cpp:228
void ink_cairo_draw_drop_shadow(const Cairo::RefPtr< Cairo::Context > &ctx, const Geom::Rect &rect, double size, guint32 color, double color_alpha)
Draw drop shadow around the 'rect' with given 'size' and 'color'; shadow extends to the right and bot...
void ink_cairo_set_source_rgba32(cairo_t *ct, guint32 rgba)
Cairo integration helpers.
Inkscape canvas widget.
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
void set_rect(Geom::Rect const &rect)
Set a control rect.
void set_is_page(bool is_page)
void set_dashed(bool dash=true)
CanvasItemRect(CanvasItemGroup *group)
Create an null control rect.
void _render(Inkscape::CanvasItemBuffer &buf) const override
Render rect to screen via Cairo.
void set_fill(uint32_t color) override
void visit_page_rects(std::function< void(Geom::Rect const &)> const &) const override
Run a callback for each rectangle that should be filled and painted in the background.
bool contains(Geom::Point const &p, double tolerance=0) override
Returns true if point p (in canvas units) is within tolerance (canvas units) distance of rect.
void set_inverted(bool inverted=false)
void _update(bool propagate) override
Update and redraw control rect.
void set_shadow(uint32_t color, int width)
double get_effective_outline() const
Get the effective outline.
Cairo::RefPtr< Cairo::Pattern > _fill_pattern
Geom::OptRect _bounds
Geom::Affine const & affine() const
UI::Widget::Canvas * get_canvas() const
Definition canvas-item.h:61
void set_page(uint32_t rgba)
Set the page colour.
Definition canvas.cpp:1746
void set_border(uint32_t rgba)
Set the page border colour.
Definition canvas.cpp:1736
Geom::Affine const & doc2dt() const
Definition desktop.cpp:1337
constexpr uint32_t SP_RGBA32_A_U(uint32_t v)
Definition utils.h:39
constexpr double SP_RGBA32_A_F(uint32_t v)
Definition utils.h:55
Editable view implementation.
Specific geometry functions for Inkscape, not provided my lib2geom.
auto floor(Geom::Rect const &rect)
Definition geom.h:130
void shift(T &a, T &b, T const &c)
Affine identity()
Create an identity matrix.
Definition affine.h:210
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Helper class to stream background task notifications as a series of messages.
int buf
Class used when rendering canvas items.
SPDesktop * desktop
double width
Cairo::RectangleInt geom_to_cairo(const Geom::IntRect &rect)
Definition util.cpp:352