Commit a641fec9 authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implement dependent size changes for the Masking Brush

Now the masking brush changes according to these requirements:

1) When the brush size is changed in the brush editor, masking
   brush size is kept unchanged

2) When the brush size is changed using quick-controls, that is
   toolbox slider, shift+gesture, HUD display, the masking brush size
   is changed proportionally.

3) Technically, the masking brush supports disabling this "dependent
   size" functionality, but I'm not sure if it should be visible in GUI.
   It is already overcomplicated.

Technical changes:

1) Now the brushes are always **copied** when fetched from the
   brushes registry. That is, if you load the brush using
   KisBrush::fromXML(), you will always have your own copy of the
   brush object, not shared with the one in the server.

2) For the efficiency reasons, the brush tip QImage will be lazily
   shared with the one on the server using the Qt's internal algorithm.
   If you change the brush tip in you copy of the brush, Qt will deep-
   copy the corresponding QImage.

3) For the efficiency reasons, brush mipmap pyramid (KisQImagePyramid)
   is also shared among all the instances of the brush with
   the same brush tip QImage. Every time one changes the instance of
   the brush, the pyramid object is detached and reset. This basic
   lazy copying algorithm is implemented in KisSharedQImagePyramid.

CC:kimageshop@kde.org
parent 5c866db5
......@@ -20,6 +20,7 @@ set(kritalibbrush_LIB_SRCS
kis_png_brush.cpp
kis_svg_brush.cpp
kis_qimage_pyramid.cpp
KisSharedQImagePyramid.cpp
kis_text_brush.cpp
kis_auto_brush_factory.cpp
kis_text_brush_factory.cpp
......
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisSharedQImagePyramid.h"
#include <QMutexLocker>
#include "kis_qimage_pyramid.h"
#include "kis_brush.h"
KisSharedQImagePyramid::KisSharedQImagePyramid()
{
}
KisSharedQImagePyramid::~KisSharedQImagePyramid()
{
}
const KisQImagePyramid *KisSharedQImagePyramid::pyramid(const KisBrush *brush) const
{
const KisQImagePyramid * result = 0;
if (m_cachedPyramidPointer) {
result = m_cachedPyramidPointer;
} else {
QMutexLocker l(&m_mutex);
if (!m_pyramid) {
m_pyramid.reset(new KisQImagePyramid(brush->brushTipImage()));
}
m_cachedPyramidPointer = m_pyramid.data();
result = m_pyramid.data();
}
return result;
}
bool KisSharedQImagePyramid::isNull() const
{
QMutexLocker l(&m_mutex);
return bool(m_pyramid);
}
/*
* Copyright (c) 2017 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KISSHAREDQIMAGEPYRAMID_H
#define KISSHAREDQIMAGEPYRAMID_H
#include "kritabrush_export.h"
#include <QSharedPointer>
#include <QMutex>
#include <QAtomicPointer>
class KisQImagePyramid;
class KisBrush;
/**
* A special class for storing the shared brushes pyramid among different brushes,
* which can be used by different threads. All the calls to pyramid() are thread-safe.
*
* Please note, that one cannot alter the pyramid. If the brush alters the pyramid,
* it should just detach from this object and create a new, unshared one.
*/
class BRUSH_EXPORT KisSharedQImagePyramid
{
public:
KisSharedQImagePyramid();
~KisSharedQImagePyramid();
public:
// lazy create and return the pyramid
const KisQImagePyramid* pyramid(const KisBrush *brush) const;
// return true if the pyramid is already prepared
bool isNull() const;
private:
mutable QMutex m_mutex;
mutable QSharedPointer<const KisQImagePyramid> m_pyramid;
mutable QAtomicPointer<const KisQImagePyramid> m_cachedPyramidPointer;
};
#endif // KISSHAREDQIMAGEPYRAMID_H
......@@ -26,10 +26,8 @@
#include "kis_mask_generator.h"
#include <kis_dom_utils.h>
KisBrushSP KisAutoBrushFactory::getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy)
KisBrushSP KisAutoBrushFactory::createBrush(const QDomElement &brushDefinition)
{
Q_UNUSED(forceCopy);
KisMaskGenerator* mask = KisMaskGenerator::fromXML(brushDefinition.firstChildElement("MaskGenerator"));
double angle = KisDomUtils::toDouble(brushDefinition.attribute("angle", "0.0"));
double randomness = KisDomUtils::toDouble(brushDefinition.attribute("randomness", "0.0"));
......
......@@ -50,7 +50,7 @@ public:
* object. If this call leads to the creation of a resource, it should be
* added to the resource provider, too.
*/
KisBrushSP getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy) override;
KisBrushSP createBrush(const QDomElement& brushDefinition) override;
};
......
......@@ -46,6 +46,7 @@
#include <brushengine/kis_paint_information.h>
#include <kis_fixed_paint_device.h>
#include <kis_qimage_pyramid.h>
#include <KisSharedQImagePyramid.h>
#include <brushengine/kis_paintop_lod_limitations.h>
......@@ -126,7 +127,7 @@ struct KisBrush::Private {
double spacing;
QPointF hotSpot;
mutable QSharedPointer<const KisQImagePyramid> brushPyramid;
mutable QSharedPointer<KisSharedQImagePyramid> brushPyramid;
QImage brushTipImage;
......@@ -179,7 +180,6 @@ KisBrush::KisBrush(const KisBrush& rhs)
KisBrush::~KisBrush()
{
clearBrushPyramid();
delete d;
}
......@@ -348,9 +348,9 @@ void KisBrush::toXML(QDomDocument& /*document*/ , QDomElement& element) const
element.setAttribute("BrushVersion", "2");
}
KisBrushSP KisBrush::fromXML(const QDomElement& element, bool forceCopy)
KisBrushSP KisBrush::fromXML(const QDomElement& element)
{
KisBrushSP brush = KisBrushRegistry::instance()->getOrCreateBrush(element, forceCopy);
KisBrushSP brush = KisBrushRegistry::instance()->createBrush(element);
if (brush && element.attribute("BrushVersion", "1") == "1") {
brush->setScale(brush->scale() * 2.0);
}
......@@ -454,16 +454,9 @@ bool KisBrush::threadingAllowed() const
return d->threadingAllowed;
}
void KisBrush::prepareBrushPyramid() const
{
if (!d->brushPyramid) {
d->brushPyramid = toQShared(new KisQImagePyramid(brushTipImage()));
}
}
void KisBrush::clearBrushPyramid()
{
d->brushPyramid.clear();
d->brushPyramid.reset(new KisSharedQImagePyramid());
}
void KisBrush::mask(KisFixedPaintDeviceSP dst, const KoColor& color, KisDabShape const& shape, const KisPaintInformation& info, double subPixelX, double subPixelY, qreal softnessFactor) const
......@@ -489,8 +482,7 @@ void KisBrush::generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst,
Q_UNUSED(info_);
Q_UNUSED(softnessFactor);
prepareBrushPyramid();
QImage outputImage = d->brushPyramid->createImage(KisDabShape(
QImage outputImage = d->brushPyramid->pyramid(this)->createImage(KisDabShape(
shape.scale() * d->scale, shape.ratio(),
-normalizeAngle(shape.rotation() + d->angle)),
subPixelX, subPixelY);
......@@ -576,8 +568,7 @@ KisFixedPaintDeviceSP KisBrush::paintDevice(const KoColorSpace * colorSpace,
double angle = normalizeAngle(shape.rotation() + d->angle);
double scale = shape.scale() * d->scale;
prepareBrushPyramid();
QImage outputImage = d->brushPyramid->createImage(
QImage outputImage = d->brushPyramid->pyramid(this)->createImage(
KisDabShape(scale, shape.ratio(), -angle), subPixelX, subPixelY);
KisFixedPaintDeviceSP dab = new KisFixedPaintDevice(colorSpace);
......
......@@ -325,7 +325,7 @@ public:
*/
virtual void toXML(QDomDocument& , QDomElement&) const;
static KisBrushSP fromXML(const QDomElement& element, bool forceCopy = false);
static KisBrushSP fromXML(const QDomElement& element);
virtual const KisBoundary* boundary() const;
virtual QPainterPath outline() const;
......@@ -335,14 +335,13 @@ public:
virtual void setAngle(qreal _angle);
qreal angle() const;
void prepareBrushPyramid() const;
void clearBrushPyramid();
virtual void lodLimitations(KisPaintopLodLimitations *l) const;
virtual KisBrush* clone() const = 0;
//protected:
protected:
KisBrush(const KisBrush& rhs);
......@@ -352,12 +351,6 @@ public:
void setHotSpot(QPointF);
/**
* The image is used to represent the brush in the gui, and may also, depending on the brush type
* be used to define the actual brush instance.
*/
virtual void setBrushTipImage(const QImage& image);
/**
* XXX
*/
......@@ -365,6 +358,14 @@ public:
virtual void setHasColor(bool hasColor);
public:
/**
* The image is used to represent the brush in the gui, and may also, depending on the brush type
* be used to define the actual brush instance.
*/
virtual void setBrushTipImage(const QImage& image);
/**
* Returns true if the brush has a bunch of pixels almost
* fully transparent in the very center. If the brush is pierced,
......
......@@ -47,7 +47,7 @@ public:
* object. If this call leads to the creation of a resource, it should be
* added to the resource provider, too.
*/
virtual KisBrushSP getOrCreateBrush(const QDomElement& element, bool forceCopy) = 0;
virtual KisBrushSP createBrush(const QDomElement& element) = 0;
};
......
......@@ -62,7 +62,7 @@ KisBrushRegistry* KisBrushRegistry::instance()
}
KisBrushSP KisBrushRegistry::getOrCreateBrush(const QDomElement& element, bool forceCopy)
KisBrushSP KisBrushRegistry::createBrush(const QDomElement& element)
{
QString brushType = element.attribute("type");
......@@ -71,6 +71,6 @@ KisBrushSP KisBrushRegistry::getOrCreateBrush(const QDomElement& element, bool f
KisBrushFactory* factory = get(brushType);
if (!factory) return 0;
return factory->getOrCreateBrush(element, forceCopy);
return factory->createBrush(element);
}
......@@ -42,7 +42,7 @@ public:
static KisBrushRegistry* instance();
KisBrushSP getOrCreateBrush(const QDomElement& element, bool forceCopy = false);
KisBrushSP createBrush(const QDomElement& element);
private:
KisBrushRegistry(const KisBrushRegistry&);
......
......@@ -139,7 +139,6 @@ KisGbrBrush::KisGbrBrush(const KisGbrBrush& rhs)
, d(new Private(*rhs.d))
{
setName(rhs.name());
setBrushTipImage(rhs.brushTipImage());
d->data = QByteArray();
setValid(rhs.valid());
}
......
......@@ -38,7 +38,6 @@ KisPngBrush::KisPngBrush(const KisPngBrush &rhs)
: KisScalingSizeBrush(rhs)
{
setSpacing(rhs.spacing());
setBrushTipImage(rhs.brushTipImage());
if (brushTipImage().isGrayscale()) {
setBrushType(MASK);
setHasColor(false);
......
......@@ -33,7 +33,7 @@ QString KisPredefinedBrushFactory::id() const
return m_id;
}
KisBrushSP KisPredefinedBrushFactory::getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy)
KisBrushSP KisPredefinedBrushFactory::createBrush(const QDomElement& brushDefinition)
{
KisBrushResourceServer *rServer = KisBrushServer::instance()->brushServer();
QString brushFileName = brushDefinition.attribute("filename", "");
......@@ -49,11 +49,10 @@ KisBrushSP KisPredefinedBrushFactory::getOrCreateBrush(const QDomElement& brushD
brush = rServer->resources().first();
}
Q_ASSERT(brush);
KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(brush, 0);
if (forceCopy) {
brush = brush->clone();
}
// we always return a copy of the brush!
brush = brush->clone();
double spacing = KisDomUtils::toDouble(brushDefinition.attribute("spacing", "0.25"));
brush->setSpacing(spacing);
......
......@@ -32,7 +32,7 @@ public:
KisPredefinedBrushFactory(const QString &brushType);
QString id() const override;
KisBrushSP getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy) override;
KisBrushSP createBrush(const QDomElement& brushDefinition) override;
private:
const QString m_id;
......
......@@ -22,10 +22,8 @@
#include <kis_dom_utils.h>
#include "kis_text_brush.h"
KisBrushSP KisTextBrushFactory::getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy)
KisBrushSP KisTextBrushFactory::createBrush(const QDomElement& brushDefinition)
{
Q_UNUSED(forceCopy);
QString text = brushDefinition.attribute("text", "The quick brown fox ate your text");
QFont font;
font.fromString(brushDefinition.attribute("font"));
......
......@@ -46,7 +46,7 @@ public:
* object. If this call leads to the creation of a resource, it should be
* added to the resource provider, too.
*/
KisBrushSP getOrCreateBrush(const QDomElement& brushDefinition, bool forceCopy) override;
KisBrushSP createBrush(const QDomElement& brushDefinition) override;
};
......
......@@ -20,7 +20,7 @@ void KisAutoBrushFactoryTest::testXMLClone()
QDomDocument d;
QDomElement e = d.createElement("Brush");
brush->toXML(d, e);
KisBrushSP clone = KisAutoBrushFactory().getOrCreateBrush(e, false);
KisBrushSP clone = KisAutoBrushFactory().createBrush(e);
// Test that the clone has the same settings as the original brush.
QCOMPARE(brush->width(), clone->width());
......
......@@ -101,7 +101,6 @@ void KisGbrBrushTest::testImageGeneration()
Q_UNUSED(res);
Q_ASSERT(res);
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
......@@ -128,30 +127,38 @@ void KisGbrBrushTest::testImageGeneration()
}
}
#include "KisSharedQImagePyramid.h"
void KisGbrBrushTest::benchmarkPyramidCreation()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
QScopedPointer<KisGbrBrush> brush(new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"));
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
QBENCHMARK {
brush->prepareBrushPyramid();
brush->clearBrushPyramid();
KisSharedQImagePyramid sharedPyramid;
QVERIFY(sharedPyramid.pyramid(brush.data())); // avoid compiler elimination of unused code!
}
}
void KisGbrBrushTest::benchmarkScaling()
{
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
QScopedPointer<KisGbrBrush> brush(new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr"));
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
KisPaintInformation info(QPointF(100.0, 100.0), 0.5);
KisFixedPaintDeviceSP dab;
{
// warm up the pyramid!
dab = brush->paintDevice(cs, KisDabShape(qreal(qrand()) / RAND_MAX * 2.0, 1.0, 0.0), info);
QVERIFY(dab); // avoid compiler elimination of unused code!
dab.clear();
}
QBENCHMARK {
dab = brush->paintDevice(cs, KisDabShape(qreal(qrand()) / RAND_MAX * 2.0, 1.0, 0.0), info);
//dab->convertToQImage(0).save(QString("dab_%1_new_smooth.png").arg(i++));
......@@ -163,7 +170,6 @@ void KisGbrBrushTest::benchmarkRotation()
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
......@@ -180,7 +186,6 @@ void KisGbrBrushTest::benchmarkMaskScaling()
KisGbrBrush* brush = new KisGbrBrush(QString(FILES_DATA_DIR) + QDir::separator() + "testing_brush_512_bars.gbr");
brush->load();
QVERIFY(!brush->brushTipImage().isNull());
brush->prepareBrushPyramid();
qsrand(1);
const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8();
......
......@@ -23,6 +23,8 @@ namespace KisPaintOpUtils {
const char MaskingBrushPaintOpId[] = "paintbrush";
const char MaskingBrushEnabledTag[] = "MaskingBrush/Enabled";
const char MaskingBrushCompositeOpTag[] = "MaskingBrush/MaskingCompositeOp";
const char MaskingBrushUseMasterSizeTag[] = "MaskingBrush/UseMasterSize";
const char MaskingBrushMasterSizeCoeffTag[] = "MaskingBrush/MasterSizeCoeff";
const char MaskingBrushPresetPrefix[] = "MaskingBrush/Preset/";
}
......@@ -26,6 +26,8 @@ namespace KisPaintOpUtils {
KRITAIMAGE_EXPORT extern const char MaskingBrushPaintOpId[];
KRITAIMAGE_EXPORT extern const char MaskingBrushEnabledTag[];
KRITAIMAGE_EXPORT extern const char MaskingBrushCompositeOpTag[];
KRITAIMAGE_EXPORT extern const char MaskingBrushUseMasterSizeTag[];
KRITAIMAGE_EXPORT extern const char MaskingBrushMasterSizeCoeffTag[];
KRITAIMAGE_EXPORT extern const char MaskingBrushPresetPrefix[];
}
......
......@@ -161,6 +161,12 @@ KisPaintOpSettingsSP KisPaintOpSettings::createMaskingSettings() const
KisPaintOpSettingsSP maskingSettings = KisPaintOpRegistry::instance()->settings(pixelBrushId);
this->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, maskingSettings);
const bool useMasterSize = this->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true);
if (useMasterSize) {
const qreal masterSizeCoeff = getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0);
maskingSettings->setPaintOpSize(masterSizeCoeff * paintOpSize());
}
return maskingSettings;
}
......
......@@ -85,8 +85,10 @@ KisBrushOpSettingsWidget::KisBrushOpSettingsWidget(QWidget* parent)
addPaintOpOption(new KisTextureOption(), i18n("Pattern"));
addPaintOpOption(new KisCurveOptionWidget(new KisPressureTextureStrengthOption(), i18n("Weak"), i18n("Strong")), i18n("Strength"));
KisMaskingBrushOption::MasterBrushSizeAdapter sizeAdapter =
[this] () { return this->brush()->userEffectiveSize(); };
addPaintOpOption(new KisMaskingBrushOption(), i18n("Brush Tip"));
addPaintOpOption(new KisMaskingBrushOption(sizeAdapter), i18n("Brush Tip"));
{
......
......@@ -49,7 +49,7 @@ KisHairyPaintOp::KisHairyPaintOp(const KisPaintOpSettingsSP settings, KisPainter
m_dev = node ? node->paintDevice() : 0;
KisBrushOption brushOption;
brushOption.readOptionSettingForceCopy(settings);
brushOption.readOptionSetting(settings);
KisBrushSP brush = brushOption.brush();
KisFixedPaintDeviceSP dab = cachedDab(painter->device()->compositionSourceColorSpace());
if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) {
......
......@@ -65,12 +65,15 @@ struct KisMaskingBrushOption::Private
QScopedPointer<QWidget> ui;
KisPredefinedBrushChooser *brushChooser = 0;
QComboBox *compositeSelector = 0;
MasterBrushSizeAdapter masterBrushSizeAdapter;
};
KisMaskingBrushOption::KisMaskingBrushOption()
KisMaskingBrushOption::KisMaskingBrushOption(MasterBrushSizeAdapter masterBrushSizeAdapter)
: KisPaintOpOption(KisPaintOpOption::MASKING_BRUSH, false),
m_d(new Private())
{
m_d->masterBrushSizeAdapter = masterBrushSizeAdapter;
setObjectName("KisMaskingBrushOption");
setConfigurationPage(m_d->ui.data());
......@@ -91,13 +94,13 @@ void KisMaskingBrushOption::writeOptionSetting(KisPropertiesConfigurationSP sett
props.brush = m_d->brushChooser->brush();
props.compositeOpId = m_d->compositeSelector->currentData().toString();
props.write(setting.data());
props.write(setting.data(), m_d->masterBrushSizeAdapter());
}
void KisMaskingBrushOption::readOptionSetting(const KisPropertiesConfigurationSP setting)
{
KisMaskingBrushOptionProperties props;
props.read(setting.data());
props.read(setting.data(), m_d->masterBrushSizeAdapter());
setChecked(props.isEnabled);
......
......@@ -29,7 +29,10 @@
class PAINTOP_EXPORT KisMaskingBrushOption : public KisPaintOpOption
{
public:
KisMaskingBrushOption();
typedef std::function<qreal()> MasterBrushSizeAdapter;
public:
KisMaskingBrushOption(MasterBrushSizeAdapter masterBrushSizeAdapter);
~KisMaskingBrushOption() override;
void writeOptionSetting(KisPropertiesConfigurationSP setting) const override;
......
......@@ -25,15 +25,21 @@
KisMaskingBrushOptionProperties::KisMaskingBrushOptionProperties()
: isEnabled(false),
compositeOpId(COMPOSITE_MULT)
: compositeOpId(COMPOSITE_MULT)
{
}
void KisMaskingBrushOptionProperties::write(KisPropertiesConfiguration *setting) const
void KisMaskingBrushOptionProperties::write(KisPropertiesConfiguration *setting, qreal masterBrushSize) const
{
setting->setProperty(KisPaintOpUtils::MaskingBrushEnabledTag, isEnabled);
setting->setProperty(KisPaintOpUtils::MaskingBrushCompositeOpTag, compositeOpId);
setting->setProperty(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, useMasterSize);
const qreal masterSizeCoeff =
brush && masterBrushSize > 0 ? brush->userEffectiveSize() / masterBrushSize : 1.0;
setting->setProperty(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, masterSizeCoeff);
// TODO: skip saving in some cases?
// if (!isEnabled) return;
......@@ -60,10 +66,11 @@ void KisMaskingBrushOptionProperties::write(KisPropertiesConfiguration *setting)
}
}
void KisMaskingBrushOptionProperties::read(const KisPropertiesConfiguration *setting)
void KisMaskingBrushOptionProperties::read(const KisPropertiesConfiguration *setting, qreal masterBrushSize)
{
isEnabled = setting->getBool(KisPaintOpUtils::MaskingBrushEnabledTag);
compositeOpId = setting->getString(KisPaintOpUtils::MaskingBrushCompositeOpTag, COMPOSITE_MULT);
useMasterSize = setting->getBool(KisPaintOpUtils::MaskingBrushUseMasterSizeTag, true);
KisPropertiesConfigurationSP embeddedConfig = new KisPropertiesConfiguration();
setting->getPrefixedProperties(KisPaintOpUtils::MaskingBrushPresetPrefix, embeddedConfig);
......@@ -72,4 +79,9 @@ void KisMaskingBrushOptionProperties::read(const KisPropertiesConfiguration *set
option.readOptionSetting(embeddedConfig);
brush = option.brush();
if (brush && useMasterSize) {
const qreal masterSizeCoeff = setting->getDouble(KisPaintOpUtils::MaskingBrushMasterSizeCoeffTag, 1.0);
brush->setUserEffectiveSize(masterSizeCoeff * masterBrushSize);
}
}
......@@ -33,9 +33,10 @@ struct PAINTOP_EXPORT KisMaskingBrushOptionProperties
bool isEnabled = false;
KisBrushSP brush;
QString compositeOpId;
bool useMasterSize = true;
void write(KisPropertiesConfiguration *setting) const;
void read(const KisPropertiesConfiguration *setting);
void write(KisPropertiesConfiguration *setting, qreal masterBrushSize) const;
void read(const KisPropertiesConfiguration *setting, qreal masterBrushSize);
};
#endif // KISMASKINGBRUSHOPTIONPROPERTIES_H
......@@ -45,7 +45,7 @@ void TextBrushInitializationWorkaround::preinitialize(KisPropertiesConfiguration
{
if (KisBrushOption::isTextBrush(settings.data())) {
KisBrushOption brushOption;
brushOption.readOptionSettingForceCopy(settings);
brushOption.readOptionSetting(settings);
m_brush = brushOption.brush();
m_settings = settings;
}
......@@ -90,7 +90,7 @@ KisBrushBasedPaintOp::KisBrushBasedPaintOp(const KisPropertiesConfigurationSP se
if (!m_brush) {
KisBrushOption brushOption;
brushOption.readOptionSettingForceCopy(settings);
brushOption.readOptionSetting(settings);
m_brush = brushOption.brush();
}
......
......@@ -81,7 +81,7 @@ KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const
{
KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy<KisPaintOpSettings>::clone();
KisBrushBasedPaintOpSettingsSP settings = dynamic_cast<KisBrushBasedPaintOpSettings*>(_settings.data());
settings->m_savedBrush = this->brush();
settings->m_savedBrush = 0;
return settings;
}
......
......@@ -40,7 +40,6 @@
#include <KoResourceItemChooser.h>
#include <kis_icon.h>
#include "kis_brush_registry.h"
#include "kis_brush_server.h"
#include "widgets/kis_slider_spin_box.h"
#include "widgets/kis_multipliers_double_slider_spinbox.h"
......@@ -133,8 +132,8 @@ KisPredefinedBrushChooser::KisPredefinedBrushChooser(QWidget *parent, const char
KisBrushResourceServer* rServer = KisBrushServer::instance()->brushServer();
QSharedPointer<KisBrushResourceServerAdapter> adapter(new KisBrushResourceServerAdapter(rServer));
m_itemChooser = new KoResourceItemChooser(adapter, this);
m_itemChooser->setObjectName("brush_selector");
m_itemChooser->showTaggingBar(true);
m_itemChooser->setColumnCount(10);
......@@ -184,21 +183,52 @@ KisPredefinedBrushChooser::~KisPredefinedBrushChooser()
{
}
void KisPredefinedBrushChooser::setBrush(KisBrushSP _brush)
void KisPredefinedBrushChooser::setBrush(KisBrushSP brush)
{
m_itemChooser->setCurrentResource(_brush.data());
update(_brush.data());
/**
* Warning: since the brushes are always cloned after loading from XML or
* fetching from the server, we cannot just ask for that brush explicitly.
* Instead, we should search for the brush with the same filename and/or name
* and load it. Please take it into account that after selecting the brush
* explicitly in the chooser, m_itemChooser->currentResource() might be
* **not** the same as the value in m_brush.
*
* Ideally, if the resource is not found on the server, we should add it, but
* it might lead to a set of weird consequences. So for now we just
* select nothing.
*/
KisBrushResourceServer* server = KisBrushServer::instance()->brushServer();
KoResource *resource = server->resourceByFilename(brush->shortFilename()).data();
if (!resource) {