1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * A widget for controlling object compositing (filter, opacity, etc.)
4 *
5 * Authors:
6 * Bryce W. Harrington <bryce@bryceharrington.org>
7 * Gustav Broberg <broberg@kth.se>
8 * Niko Kiirala <niko@kiirala.com>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2004--2008 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16 #include "ui/widget/object-composite-settings.h"
17
18 #include "desktop.h"
19
20 #include "desktop-style.h"
21 #include "document.h"
22 #include "document-undo.h"
23 #include "filter-chemistry.h"
24 #include "inkscape.h"
25 #include "style.h"
26 #include "svg/css-ostringstream.h"
27 #include "verbs.h"
28 #include "object/filters/blend.h"
29 #include "ui/widget/style-subject.h"
30
31 constexpr double BLUR_MULTIPLIER = 4.0;
32
33 namespace Inkscape {
34 namespace UI {
35 namespace Widget {
36
ObjectCompositeSettings(unsigned int verb_code,char const * history_prefix,int flags)37 ObjectCompositeSettings::ObjectCompositeSettings(unsigned int verb_code, char const *history_prefix, int flags)
38 : Gtk::Box(Gtk::ORIENTATION_VERTICAL),
39 _verb_code(verb_code),
40 _blend_tag(Glib::ustring(history_prefix) + ":blend"),
41 _blur_tag(Glib::ustring(history_prefix) + ":blur"),
42 _opacity_tag(Glib::ustring(history_prefix) + ":opacity"),
43 _isolation_tag(Glib::ustring(history_prefix) + ":isolation"),
44 _filter_modifier(flags),
45 _blocked(false)
46 {
47 set_name( "ObjectCompositeSettings");
48
49 // Filter Effects
50 pack_start(_filter_modifier, false, false, 2);
51
52 _filter_modifier.signal_blend_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged));
53 _filter_modifier.signal_blur_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_blendBlurValueChanged));
54 _filter_modifier.signal_opacity_changed().connect(sigc::mem_fun(*this, &ObjectCompositeSettings::_opacityValueChanged));
55 _filter_modifier.signal_isolation_changed().connect(
56 sigc::mem_fun(*this, &ObjectCompositeSettings::_isolationValueChanged));
57
58 show_all_children();
59 }
60
~ObjectCompositeSettings()61 ObjectCompositeSettings::~ObjectCompositeSettings() {
62 setSubject(nullptr);
63 }
64
setSubject(StyleSubject * subject)65 void ObjectCompositeSettings::setSubject(StyleSubject *subject) {
66 _subject_changed.disconnect();
67 if (subject) {
68 _subject = subject;
69 _subject_changed = _subject->connectChanged(sigc::mem_fun(*this, &ObjectCompositeSettings::_subjectChanged));
70 }
71 }
72
73 // We get away with sharing one callback for blend and blur as this is used by
74 // * the Layers dialog where only one layer can be selected at a time,
75 // * the Fill and Stroke dialog where only blur is used.
76 // If both blend and blur are used in a dialog where more than one object can
77 // be selected then this should be split into separate functions for blend and
78 // blur (like in the Objects dialog).
79 void
_blendBlurValueChanged()80 ObjectCompositeSettings::_blendBlurValueChanged()
81 {
82 if (!_subject) {
83 return;
84 }
85
86 SPDesktop *desktop = _subject->getDesktop();
87 if (!desktop) {
88 return;
89 }
90 SPDocument *document = desktop->getDocument();
91
92 if (_blocked)
93 return;
94 _blocked = true;
95
96 Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX);
97 double radius;
98 if (bbox) {
99 double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
100 double blur_value = _filter_modifier.get_blur_value() / 100.0;
101 radius = blur_value * blur_value * perimeter / BLUR_MULTIPLIER;
102 } else {
103 radius = 0;
104 }
105
106 //apply created filter to every selected item
107 std::vector<SPObject*> sel = _subject->list();
108 for (auto i : sel) {
109 if (!SP_IS_ITEM(i)) {
110 continue;
111 }
112 SPItem * item = SP_ITEM(i);
113 SPStyle *style = item->style;
114 g_assert(style != nullptr);
115 bool change_blend = (item->style->mix_blend_mode.set ? item->style->mix_blend_mode.value : SP_CSS_BLEND_NORMAL) != _filter_modifier.get_blend_mode();
116 // < 1.0 filter based blend removal
117 if (!item->style->mix_blend_mode.set && item->style->filter.set && item->style->getFilter()) {
118 remove_filter_legacy_blend(item);
119 }
120 item->style->mix_blend_mode.set = TRUE;
121 if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
122 item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
123 } else {
124 item->style->mix_blend_mode.value = _filter_modifier.get_blend_mode();
125 }
126
127 if (radius == 0 && item->style->filter.set
128 && filter_is_single_gaussian_blur(item->style->getFilter())) {
129 remove_filter(item, false);
130 } else if (radius != 0) {
131 SPFilter *filter = modify_filter_gaussian_blur_from_item(document, item, radius);
132 filter->update_filter_region(item);
133 sp_style_set_property_url(item, "filter", filter, false);
134 }
135 if (change_blend) { //we do blend so we need update display style
136 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
137 } else {
138 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
139 }
140 }
141
142 DocumentUndo::maybeDone(document, _blur_tag.c_str(), _verb_code,
143 _("Change blur/blend filter"));
144
145 _blocked = false;
146 }
147
148 void
_opacityValueChanged()149 ObjectCompositeSettings::_opacityValueChanged()
150 {
151 if (!_subject) {
152 return;
153 }
154
155 SPDesktop *desktop = _subject->getDesktop();
156 if (!desktop) {
157 return;
158 }
159
160 if (_blocked)
161 return;
162 _blocked = true;
163
164 SPCSSAttr *css = sp_repr_css_attr_new ();
165
166 Inkscape::CSSOStringStream os;
167 os << CLAMP (_filter_modifier.get_opacity_value() / 100, 0.0, 1.0);
168 sp_repr_css_set_property (css, "opacity", os.str().c_str());
169
170 _subject->setCSS(css);
171
172 sp_repr_css_attr_unref (css);
173
174 DocumentUndo::maybeDone(desktop->getDocument(), _opacity_tag.c_str(), _verb_code,
175 _("Change opacity"));
176
177 _blocked = false;
178 }
179
_isolationValueChanged()180 void ObjectCompositeSettings::_isolationValueChanged()
181 {
182 if (!_subject) {
183 return;
184 }
185
186 SPDesktop *desktop = _subject->getDesktop();
187 if (!desktop) {
188 return;
189 }
190
191 if (_blocked)
192 return;
193 _blocked = true;
194
195 for (auto item : _subject->list()) {
196 item->style->isolation.set = TRUE;
197 item->style->isolation.value = _filter_modifier.get_isolation_mode();
198 if (item->style->isolation.value == SP_CSS_ISOLATION_ISOLATE) {
199 item->style->mix_blend_mode.set = TRUE;
200 item->style->mix_blend_mode.value = SP_CSS_BLEND_NORMAL;
201 }
202 item->updateRepr(SP_OBJECT_WRITE_NO_CHILDREN | SP_OBJECT_WRITE_EXT);
203 }
204
205 DocumentUndo::maybeDone(desktop->getDocument(), _isolation_tag.c_str(), _verb_code, _("Change isolation"));
206
207 _blocked = false;
208 }
209
210 void
_subjectChanged()211 ObjectCompositeSettings::_subjectChanged() {
212 if (!_subject) {
213 return;
214 }
215
216 SPDesktop *desktop = _subject->getDesktop();
217 if (!desktop) {
218 return;
219 }
220
221 if (_blocked)
222 return;
223 _blocked = true;
224 SPStyle query(desktop->getDocument());
225 int result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_MASTEROPACITY);
226
227 switch (result) {
228 case QUERY_STYLE_NOTHING:
229 break;
230 case QUERY_STYLE_SINGLE:
231 case QUERY_STYLE_MULTIPLE_AVERAGED: // TODO: treat this slightly differently
232 case QUERY_STYLE_MULTIPLE_SAME:
233 _filter_modifier.set_opacity_value(100 * SP_SCALE24_TO_FLOAT(query.opacity.value));
234 break;
235 }
236
237 //query now for current filter mode and average blurring of selection
238 const int isolation_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_ISOLATION);
239 switch (isolation_result) {
240 case QUERY_STYLE_NOTHING:
241 _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false);
242 break;
243 case QUERY_STYLE_SINGLE:
244 case QUERY_STYLE_MULTIPLE_SAME:
245 _filter_modifier.set_isolation_mode(query.isolation.value, true); // here dont work mix_blend_mode.set
246 break;
247 case QUERY_STYLE_MULTIPLE_DIFFERENT:
248 _filter_modifier.set_isolation_mode(SP_CSS_ISOLATION_AUTO, false);
249 // TODO: set text
250 break;
251 }
252
253 // query now for current filter mode and average blurring of selection
254 const int blend_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLEND);
255 switch(blend_result) {
256 case QUERY_STYLE_NOTHING:
257 _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
258 break;
259 case QUERY_STYLE_SINGLE:
260 case QUERY_STYLE_MULTIPLE_SAME:
261 _filter_modifier.set_blend_mode(query.mix_blend_mode.value, true); // here dont work mix_blend_mode.set
262 break;
263 case QUERY_STYLE_MULTIPLE_DIFFERENT:
264 _filter_modifier.set_blend_mode(SP_CSS_BLEND_NORMAL, false);
265 break;
266 }
267
268 int blur_result = _subject->queryStyle(&query, QUERY_STYLE_PROPERTY_BLUR);
269 switch (blur_result) {
270 case QUERY_STYLE_NOTHING: // no blurring
271 _filter_modifier.set_blur_value(0);
272 break;
273 case QUERY_STYLE_SINGLE:
274 case QUERY_STYLE_MULTIPLE_AVERAGED:
275 case QUERY_STYLE_MULTIPLE_SAME:
276 Geom::OptRect bbox = _subject->getBounds(SPItem::GEOMETRIC_BBOX);
277 if (bbox) {
278 double perimeter =
279 bbox->dimensions()[Geom::X] +
280 bbox->dimensions()[Geom::Y]; // fixme: this is only half the perimeter, is that correct?
281 // update blur widget value
282 float radius = query.filter_gaussianBlur_deviation.value;
283 float percent = std::sqrt(radius * BLUR_MULTIPLIER / perimeter) * 100;
284 _filter_modifier.set_blur_value(percent);
285 }
286 break;
287 }
288
289 // If we have nothing selected, disable dialog.
290 if (result == QUERY_STYLE_NOTHING &&
291 blend_result == QUERY_STYLE_NOTHING ) {
292 _filter_modifier.set_sensitive( false );
293 } else {
294 _filter_modifier.set_sensitive( true );
295 }
296
297 _blocked = false;
298 }
299
300 }
301 }
302 }
303
304 /*
305 Local Variables:
306 mode:c++
307 c-file-style:"stroustrup"
308 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
309 indent-tabs-mode:nil
310 fill-column:99
311 End:
312 */
313 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
314