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