1 /*
2  *  Copyright (c) 2007 Boudewijn Rempt <boud@valdyas.org>
3  *  Copyright (c) 2008 Lukáš Tvrdý <lukast.dev@gmail.com>
4  *  Copyright (c) 2014 Mohit Goyal <mohit.bits2011@gmail.com>
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19 
20 #include <brushengine/kis_paintop_settings.h>
21 
22 #include <QImage>
23 #include <QColor>
24 #include <QPainterPath>
25 #include <QPointer>
26 
27 #include <KoPointerEvent.h>
28 #include <KoColor.h>
29 #include <KoCompositeOpRegistry.h>
30 #include <KoViewConverter.h>
31 
32 #include "kis_paint_layer.h"
33 #include "kis_image.h"
34 #include "kis_painter.h"
35 #include "kis_paint_device.h"
36 #include "kis_paintop_registry.h"
37 #include "kis_timing_information.h"
38 #include <brushengine/kis_paint_information.h>
39 #include "kis_paintop_config_widget.h"
40 #include <brushengine/kis_paintop_preset.h>
41 #include "kis_paintop_settings_update_proxy.h"
42 #include <time.h>
43 #include <kis_types.h>
44 #include <kis_signals_blocker.h>
45 
46 #include <brushengine/kis_locked_properties_server.h>
47 #include <brushengine/kis_locked_properties_proxy.h>
48 
49 #include "KisPaintopSettingsIds.h"
50 #include "kis_algebra_2d.h"
51 #include "kis_image_config.h"
52 
53 
54 struct Q_DECL_HIDDEN KisPaintOpSettings::Private {
PrivateKisPaintOpSettings::Private55     Private()
56         : disableDirtyNotifications(false)
57     {}
58 
59     QPointer<KisPaintOpConfigWidget> settingsWidget;
60     QString modelName;
61     KisPaintOpPresetWSP preset;
62     QList<KisUniformPaintOpPropertyWSP> uniformProperties;
63 
64     bool disableDirtyNotifications;
65 
66     class DirtyNotificationsLocker {
67     public:
DirtyNotificationsLocker(KisPaintOpSettings::Private * d)68         DirtyNotificationsLocker(KisPaintOpSettings::Private *d)
69             : m_d(d),
70               m_oldNotificationsState(d->disableDirtyNotifications)
71         {
72             m_d->disableDirtyNotifications = true;
73         }
74 
~DirtyNotificationsLocker()75         ~DirtyNotificationsLocker() {
76             m_d->disableDirtyNotifications = m_oldNotificationsState;
77         }
78 
79     private:
80         KisPaintOpSettings::Private *m_d;
81         bool m_oldNotificationsState;
82         Q_DISABLE_COPY(DirtyNotificationsLocker)
83     };
84 
updateProxyNoCreateKisPaintOpSettings::Private85     KisPaintopSettingsUpdateProxy* updateProxyNoCreate() const {
86         auto presetSP = preset.toStrongRef();
87         return presetSP ? presetSP->updateProxyNoCreate() : 0;
88     }
89 
updateProxyCreateKisPaintOpSettings::Private90     KisPaintopSettingsUpdateProxy* updateProxyCreate() const {
91         auto presetSP = preset.toStrongRef();
92         return presetSP ? presetSP->updateProxy() : 0;
93     }
94 };
95 
96 
KisPaintOpSettings()97 KisPaintOpSettings::KisPaintOpSettings()
98     : d(new Private)
99 {
100     d->preset = 0;
101 }
102 
~KisPaintOpSettings()103 KisPaintOpSettings::~KisPaintOpSettings()
104 {
105 }
106 
KisPaintOpSettings(const KisPaintOpSettings & rhs)107 KisPaintOpSettings::KisPaintOpSettings(const KisPaintOpSettings &rhs)
108     : KisPropertiesConfiguration(rhs)
109     , d(new Private)
110 {
111     d->settingsWidget = 0;
112     d->preset = rhs.preset();
113     d->modelName = rhs.modelName();
114 }
115 
setOptionsWidget(KisPaintOpConfigWidget * widget)116 void KisPaintOpSettings::setOptionsWidget(KisPaintOpConfigWidget* widget)
117 {
118     d->settingsWidget = widget;
119 }
setPreset(KisPaintOpPresetWSP preset)120 void KisPaintOpSettings::setPreset(KisPaintOpPresetWSP preset)
121 {
122     d->preset = preset;
123 }
preset() const124 KisPaintOpPresetWSP KisPaintOpSettings::preset() const
125 {
126     return d->preset;
127 }
128 
mousePressEvent(const KisPaintInformation & paintInformation,Qt::KeyboardModifiers modifiers,KisNodeWSP currentNode)129 bool KisPaintOpSettings::mousePressEvent(const KisPaintInformation &paintInformation, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode)
130 {
131     Q_UNUSED(modifiers);
132     Q_UNUSED(currentNode);
133     setRandomOffset(paintInformation);
134     return true; // ignore the event by default
135 }
136 
mouseReleaseEvent()137 bool KisPaintOpSettings::mouseReleaseEvent()
138 {
139     return true; // ignore the event by default
140 }
141 
setRandomOffset(const KisPaintInformation & paintInformation)142 void KisPaintOpSettings::setRandomOffset(const KisPaintInformation &paintInformation)
143 {
144 	bool disableDirtyBefore = d->disableDirtyNotifications;
145 	d->disableDirtyNotifications = true;
146     if (getBool("Texture/Pattern/Enabled")) {
147         if (getBool("Texture/Pattern/isRandomOffsetX")) {
148             setProperty("Texture/Pattern/OffsetX",
149                         paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetX")));
150         }
151         if (getBool("Texture/Pattern/isRandomOffsetY")) {
152             setProperty("Texture/Pattern/OffsetY",
153                         paintInformation.randomSource()->generate(0, KisPropertiesConfiguration::getInt("Texture/Pattern/MaximumOffsetY")));
154 
155         }
156     }
157 	d->disableDirtyNotifications = disableDirtyBefore;
158 }
159 
hasMaskingSettings() const160 bool KisPaintOpSettings::hasMaskingSettings() const
161 {
162     return getBool(KisPaintOpUtils::MaskingBrushEnabledTag, false);
163 }
164 
createMaskingSettings() const165 KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const
166 {
167     if (!hasMaskingSettings()) return KisPaintOpSettingsSP();
168 
169     const KoID pixelBrushId(KisPaintOpUtils::MaskingBrushPaintOpId, QString());
170 
171     KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId);
172     this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings);
173 
174     const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true);
175     if (useMasterSize) {
176         /**
177          * WARNING: cropping is a workaround for too big brushes due to
178          * the proportional scaling using shift+drag gesture.
179          *
180          * See this bug: https://bugs.kde.org/show_bug.cgi?id=423572
181          *
182          * TODO:
183          *
184          * 1) Implement a warning notifying the user that his masking
185          *    brush has been cropped
186          *
187          * 2) Make sure that the sliders in KisMaskingBrushOption have
188          *    correct limits (right now they are limited by usual
189          *    maximumBrushSize)
190          */
191 
192         const qreal maxBrushSize = KisImageConfig(true).readEntry("maximumBrushSize", 1000);
193         const qreal maxMaskingBrushSize = qMin(15000.0, 3.0 * maxBrushSize);
194 
195         const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0);
196         maskingSettings->setPaintOpSize(qMin(maxMaskingBrushSize, masterSizeCoeff * paintOpSize()));
197     }
198 
199     return maskingSettings;
200 }
201 
hasPatternSettings() const202 bool KisPaintOpSettings::hasPatternSettings() const
203 {
204     return false;
205 }
206 
maskingBrushCompositeOp() const207 QString KisPaintOpSettings::maskingBrushCompositeOp() const
208 {
209     return getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT);
210 }
211 
212 
213 
clone() const214 KisPaintOpSettingsSP KisPaintOpSettings::clone() const
215 {
216     QString paintopID = getString("paintop");
217     if (paintopID.isEmpty())
218         return 0;
219 
220     KisPaintOpSettingsSP settings = KisPaintOpRegistry::instance()->settings(KoID(paintopID));
221     QMapIterator<QString, QVariant> i(getProperties());
222     while (i.hasNext()) {
223         i.next();
224         settings->setProperty(i.key(), QVariant(i.value()));
225     }
226     settings->setPreset(this->preset());
227     return settings;
228 }
229 
resetSettings(const QStringList & preserveProperties)230 void KisPaintOpSettings::resetSettings(const QStringList &preserveProperties)
231 {
232     QStringList allKeys = preserveProperties;
233     allKeys << "paintop";
234 
235     QHash<QString, QVariant> preserved;
236     Q_FOREACH (const QString &key, allKeys) {
237         if (hasProperty(key)) {
238             preserved[key] = getProperty(key);
239         }
240     }
241 
242     clearProperties();
243 
244     for (auto it = preserved.constBegin(); it != preserved.constEnd(); ++it) {
245         setProperty(it.key(), it.value());
246     }
247 }
248 
activate()249 void KisPaintOpSettings::activate()
250 {
251 }
252 
setPaintOpOpacity(qreal value)253 void KisPaintOpSettings::setPaintOpOpacity(qreal value)
254 {
255     KisLockedPropertiesProxySP proxy(
256                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
257 
258     proxy->setProperty("OpacityValue", value);
259 }
260 
setPaintOpFlow(qreal value)261 void KisPaintOpSettings::setPaintOpFlow(qreal value)
262 {
263     KisLockedPropertiesProxySP proxy(
264                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
265 
266     proxy->setProperty("FlowValue", value);
267 }
268 
setPaintOpCompositeOp(const QString & value)269 void KisPaintOpSettings::setPaintOpCompositeOp(const QString &value)
270 {
271     KisLockedPropertiesProxySP proxy(
272                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
273 
274     proxy->setProperty("CompositeOp", value);
275 }
276 
paintOpOpacity()277 qreal KisPaintOpSettings::paintOpOpacity()
278 {
279     KisLockedPropertiesProxySP proxy = KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this);
280 
281     return proxy->getDouble("OpacityValue", 1.0);
282 }
283 
paintOpFlow()284 qreal KisPaintOpSettings::paintOpFlow()
285 {
286     KisLockedPropertiesProxySP proxy(
287                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
288 
289     return proxy->getDouble("FlowValue", 1.0);
290 }
291 
paintOpPatternSize()292 qreal KisPaintOpSettings::paintOpPatternSize()
293 {
294     KisLockedPropertiesProxySP proxy(
295         KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
296 
297     return proxy->getDouble("Texture/Pattern/Scale", 0.5);
298 }
299 
paintOpCompositeOp()300 QString KisPaintOpSettings::paintOpCompositeOp()
301 {
302     KisLockedPropertiesProxySP proxy(
303                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
304 
305     return proxy->getString("CompositeOp", COMPOSITE_OVER);
306 }
307 
setEraserMode(bool value)308 void KisPaintOpSettings::setEraserMode(bool value)
309 {
310     KisLockedPropertiesProxySP proxy(
311                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
312 
313     proxy->setProperty("EraserMode", value);
314 }
315 
eraserMode()316 bool KisPaintOpSettings::eraserMode()
317 {
318     KisLockedPropertiesProxySP proxy(
319                 KisLockedPropertiesServer::instance()->createLockedPropertiesProxy(this));
320 
321     return proxy->getBool("EraserMode", false);
322 }
323 
effectivePaintOpCompositeOp()324 QString KisPaintOpSettings::effectivePaintOpCompositeOp()
325 {
326     return !eraserMode() ? paintOpCompositeOp() : COMPOSITE_ERASE;
327 }
328 
savedEraserSize() const329 qreal KisPaintOpSettings::savedEraserSize() const
330 {
331     return getDouble("SavedEraserSize", 0.0);
332 }
333 
setSavedEraserSize(qreal value)334 void KisPaintOpSettings::setSavedEraserSize(qreal value)
335 {
336     setProperty("SavedEraserSize", value);
337     setPropertyNotSaved("SavedEraserSize");
338 }
339 
savedBrushSize() const340 qreal KisPaintOpSettings::savedBrushSize() const
341 {
342     return getDouble("SavedBrushSize", 0.0);
343 }
344 
setSavedBrushSize(qreal value)345 void KisPaintOpSettings::setSavedBrushSize(qreal value)
346 {
347     setProperty("SavedBrushSize", value);
348     setPropertyNotSaved("SavedBrushSize");
349 }
350 
savedEraserOpacity() const351 qreal KisPaintOpSettings::savedEraserOpacity() const
352 {
353     return getDouble("SavedEraserOpacity", 0.0);
354 }
355 
setSavedEraserOpacity(qreal value)356 void KisPaintOpSettings::setSavedEraserOpacity(qreal value)
357 {
358     setProperty("SavedEraserOpacity", value);
359     setPropertyNotSaved("SavedEraserOpacity");
360 }
361 
savedBrushOpacity() const362 qreal KisPaintOpSettings::savedBrushOpacity() const
363 {
364     return getDouble("SavedBrushOpacity", 0.0);
365 }
366 
setSavedBrushOpacity(qreal value)367 void KisPaintOpSettings::setSavedBrushOpacity(qreal value)
368 {
369     setProperty("SavedBrushOpacity", value);
370     setPropertyNotSaved("SavedBrushOpacity");
371 }
372 
modelName() const373 QString KisPaintOpSettings::modelName() const
374 {
375     return d->modelName;
376 }
377 
setModelName(const QString & modelName)378 void KisPaintOpSettings::setModelName(const QString & modelName)
379 {
380     d->modelName = modelName;
381 }
382 
optionsWidget() const383 KisPaintOpConfigWidget* KisPaintOpSettings::optionsWidget() const
384 {
385     if (d->settingsWidget.isNull())
386         return 0;
387 
388     return d->settingsWidget.data();
389 }
390 
isValid() const391 bool KisPaintOpSettings::isValid() const
392 {
393     return true;
394 }
395 
isLoadable()396 bool KisPaintOpSettings::isLoadable()
397 {
398     return isValid();
399 }
400 
indirectPaintingCompositeOp() const401 QString KisPaintOpSettings::indirectPaintingCompositeOp() const
402 {
403     return COMPOSITE_ALPHA_DARKEN;
404 }
405 
isAirbrushing() const406 bool KisPaintOpSettings::isAirbrushing() const
407 {
408     return getBool(AIRBRUSH_ENABLED, false);
409 }
410 
airbrushInterval() const411 qreal KisPaintOpSettings::airbrushInterval() const
412 {
413     qreal rate = getDouble(AIRBRUSH_RATE, 1.0);
414     if (rate == 0.0) {
415         return LONG_TIME;
416     }
417     else {
418         return 1000.0 / rate;
419     }
420 }
421 
useSpacingUpdates() const422 bool KisPaintOpSettings::useSpacingUpdates() const
423 {
424     return getBool(SPACING_USE_UPDATES, false);
425 }
426 
needsAsynchronousUpdates() const427 bool KisPaintOpSettings::needsAsynchronousUpdates() const
428 {
429     return false;
430 }
431 
brushOutline(const KisPaintInformation & info,const OutlineMode & mode,qreal alignForZoom)432 QPainterPath KisPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode, qreal alignForZoom)
433 {
434     QPainterPath path;
435     if (mode.isVisible) {
436         path = ellipseOutline(10, 10, 1.0, 0);
437 
438         if (mode.showTiltDecoration) {
439             path.addPath(makeTiltIndicator(info, QPointF(0.0, 0.0), 0.0, 2.0));
440         }
441 
442         path.translate(KisAlgebra2D::alignForZoom(info.pos(), alignForZoom));
443     }
444 
445     return path;
446 }
447 
ellipseOutline(qreal width,qreal height,qreal scale,qreal rotation)448 QPainterPath KisPaintOpSettings::ellipseOutline(qreal width, qreal height, qreal scale, qreal rotation)
449 {
450     QPainterPath path;
451     QRectF ellipse(0, 0, width * scale, height * scale);
452     ellipse.translate(-ellipse.center());
453     path.addEllipse(ellipse);
454 
455     QTransform m;
456     m.reset();
457     m.rotate(rotation);
458     path = m.map(path);
459     return path;
460 }
461 
makeTiltIndicator(KisPaintInformation const & info,QPointF const & start,qreal maxLength,qreal angle)462 QPainterPath KisPaintOpSettings::makeTiltIndicator(KisPaintInformation const& info,
463                                                    QPointF const& start, qreal maxLength, qreal angle)
464 {
465     if (maxLength == 0.0) maxLength = 50.0;
466     maxLength = qMax(maxLength, 50.0);
467     qreal const length = maxLength * (1 - info.tiltElevation(info, 60.0, 60.0, true));
468     qreal const baseAngle = 360.0 - fmod(KisPaintInformation::tiltDirection(info, true) * 360.0 + 270.0, 360.0);
469 
470     QLineF guideLine = QLineF::fromPolar(length, baseAngle + angle);
471     guideLine.translate(start);
472     QPainterPath ret;
473     ret.moveTo(guideLine.p1());
474     ret.lineTo(guideLine.p2());
475     guideLine.setAngle(baseAngle - angle);
476     ret.lineTo(guideLine.p2());
477     ret.lineTo(guideLine.p1());
478     return ret;
479 }
480 
setProperty(const QString & name,const QVariant & value)481 void KisPaintOpSettings::setProperty(const QString & name, const QVariant & value)
482 {
483     if (value != KisPropertiesConfiguration::getProperty(name) &&
484             !d->disableDirtyNotifications) {
485         KisPaintOpPresetSP presetSP = preset().toStrongRef();
486         if (presetSP) {
487             presetSP->setDirty(true);
488         }
489     }
490 
491     KisPropertiesConfiguration::setProperty(name, value);
492     onPropertyChanged();
493 }
494 
495 
onPropertyChanged()496 void KisPaintOpSettings::onPropertyChanged()
497 {
498     KisPaintopSettingsUpdateProxy *proxy = d->updateProxyNoCreate();
499 
500     if (proxy) {
501         proxy->notifySettingsChanged();
502     }
503 }
504 
isLodUserAllowed(const KisPropertiesConfigurationSP config)505 bool KisPaintOpSettings::isLodUserAllowed(const KisPropertiesConfigurationSP config)
506 {
507     return config->getBool("lodUserAllowed", true);
508 }
509 
setLodUserAllowed(KisPropertiesConfigurationSP config,bool value)510 void KisPaintOpSettings::setLodUserAllowed(KisPropertiesConfigurationSP config, bool value)
511 {
512     config->setProperty("lodUserAllowed", value);
513 }
514 
lodSizeThresholdSupported() const515 bool KisPaintOpSettings::lodSizeThresholdSupported() const
516 {
517     return true;
518 }
519 
lodSizeThreshold() const520 qreal KisPaintOpSettings::lodSizeThreshold() const
521 {
522     return getDouble("lodSizeThreshold", 100.0);
523 }
524 
setLodSizeThreshold(qreal value)525 void KisPaintOpSettings::setLodSizeThreshold(qreal value)
526 {
527     setProperty("lodSizeThreshold", value);
528 }
529 
530 #include "kis_standard_uniform_properties_factory.h"
531 
uniformProperties(KisPaintOpSettingsSP settings)532 QList<KisUniformPaintOpPropertySP> KisPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings)
533 {
534     QList<KisUniformPaintOpPropertySP> props =
535             listWeakToStrong(d->uniformProperties);
536 
537 
538     if (props.isEmpty()) {
539         using namespace KisStandardUniformPropertiesFactory;
540 
541         props.append(createProperty(opacity, settings, d->updateProxyCreate()));
542         props.append(createProperty(size, settings, d->updateProxyCreate()));
543         props.append(createProperty(flow, settings, d->updateProxyCreate()));
544 
545         d->uniformProperties = listStrongToWeak(props);
546     }
547 
548     return props;
549 }
550