Commit cc19c756 authored by Dmitry Kazakov's avatar Dmitry Kazakov

2nd try of fixing color fluctuations in colorsmudge-dulling brushes

Now the colorsmudge brush does all the blending in the high bit-depth
colorspace. The patch introduces a special wrapper
(KisPrecisePaintDeviceWrapper) that ensures a proper color space is used
when needed. See the apidox in KisPrecisePaintDeviceWrapper.h.

NOTE: this patch fixes **both** dulling and smearing modes!

BUG:348267

Test Plan:
Needs to be tested:
1) Colorsmudge brushes in Dulling mode with Color Rate option enabled
2) Smudge Radius enabled or disabled: both should work correctly
3) Opacity option should work correctly
4) Overlay mode?

Reviewers: #krita
Differential Revision: https://phabricator.kde.org/D9686
parent 93a69d92
......@@ -57,6 +57,7 @@ set(kritaimage_LIB_SRCS
kis_painter.cc
kis_painter_blt_multi_fixed.cpp
kis_marker_painter.cpp
KisPrecisePaintDeviceWrapper.cpp
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
brushengine/kis_random_source.cpp
......
/*
* Copyright (c) 2018 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 "KisPrecisePaintDeviceWrapper.h"
#include <QRegion>
#include "kis_paint_device.h"
#include "kis_painter.h"
#include "kis_sequential_iterator.h"
#include <KoColorSpaceRegistry.h>
#include <KoColorModelStandardIds.h>
#include <KoColorSpaceMaths.h>
struct KisPrecisePaintDeviceWrapper::Private
{
KisPaintDeviceSP srcDevice;
KisPaintDeviceSP precDevice;
QRegion preparedRegion;
const KoColorSpace *precColorSpace = 0;
int keepRectsHistory = 50;
};
KisPrecisePaintDeviceWrapper::KisPrecisePaintDeviceWrapper(KisPaintDeviceSP device, int keepRectsHistory)
: m_d(new Private)
{
m_d->srcDevice = device;
m_d->keepRectsHistory = keepRectsHistory;
const KoColorSpace *baseSpace = device->colorSpace();
const bool useRoundingCorrection = baseSpace->colorDepthId() == Integer8BitsColorDepthID;
if (useRoundingCorrection) {
m_d->precColorSpace =
KoColorSpaceRegistry::instance()->colorSpace(
baseSpace->colorModelId().id(),
Integer16BitsColorDepthID.id(),
baseSpace->profile());
m_d->precDevice = new KisPaintDevice(m_d->precColorSpace);
} else {
// just use source device as a precise operation device
m_d->precDevice = device;
m_d->precColorSpace = device->colorSpace();
}
}
KisPrecisePaintDeviceWrapper::~KisPrecisePaintDeviceWrapper()
{
}
const KoColorSpace *KisPrecisePaintDeviceWrapper::preciseColorSpace() const
{
return m_d->precColorSpace;
}
KisPaintDeviceSP KisPrecisePaintDeviceWrapper::sourceDevice() const
{
return m_d->srcDevice;
}
KisPaintDeviceSP KisPrecisePaintDeviceWrapper::preciseDevice() const
{
return m_d->precDevice;
}
QRegion KisPrecisePaintDeviceWrapper::cachedRegion() const
{
return m_d->precDevice == m_d->srcDevice ? m_d->srcDevice->extent() : m_d->preparedRegion;
}
void KisPrecisePaintDeviceWrapper::resetCachedRegion()
{
m_d->preparedRegion = QRegion();
}
void KisPrecisePaintDeviceWrapper::readRect(const QRect &rect)
{
readRects({rect});
}
void KisPrecisePaintDeviceWrapper::writeRect(const QRect &rc)
{
if (m_d->precDevice == m_d->srcDevice) return;
const int channelCount = m_d->precColorSpace->channelCount();
KisSequentialIterator srcIt(m_d->srcDevice, rc);
KisSequentialConstIterator precIt(m_d->precDevice, rc);
while (srcIt.nextPixel() && precIt.nextPixel()) {
quint8 *srcPtr = reinterpret_cast<quint8*>(srcIt.rawData());
const quint16 *precPtr = reinterpret_cast<const quint16*>(precIt.rawDataConst());
for (int i = 0; i < channelCount; i++) {
*(srcPtr + i) = KoColorSpaceMaths<quint16, quint8>::scaleToA(*(precPtr + i));
}
}
}
void KisPrecisePaintDeviceWrapper::readRects(const QVector<QRect> &rects)
{
if (m_d->precDevice == m_d->srcDevice) return;
QRegion requestedRects;
Q_FOREACH (const QRect &rc, rects) {
requestedRects += rc;
}
QRegion diff(requestedRects);
diff -= m_d->preparedRegion;
const int channelCount = m_d->precColorSpace->channelCount();
Q_FOREACH (const QRect &rc, diff.rects()) {
KisSequentialConstIterator srcIt(m_d->srcDevice, rc);
KisSequentialIterator precIt(m_d->precDevice, rc);
while (srcIt.nextPixel() && precIt.nextPixel()) {
const quint8 *srcPtr = reinterpret_cast<const quint8*>(srcIt.rawDataConst());
quint16 *precPtr = reinterpret_cast<quint16*>(precIt.rawData());
for (int i = 0; i < channelCount; i++) {
*(precPtr + i) = KoColorSpaceMaths<quint8, quint16>::scaleToA(*(srcPtr + i));
}
}
}
/**
* Don't let the region grow too much. When the region has too many
* rects, it becomes really slow
*/
if (m_d->preparedRegion.rectCount() > m_d->keepRectsHistory) {
m_d->preparedRegion = requestedRects;
} else {
m_d->preparedRegion += requestedRects;
}
}
void KisPrecisePaintDeviceWrapper::writeRects(const QVector<QRect> &rects)
{
if (m_d->precDevice == m_d->srcDevice) return;
Q_FOREACH (const QRect &rc, rects) {
writeRect(rc);
}
}
/*
* Copyright (c) 2018 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 KISPRECISEPAINTDEVICEWRAPPER_H
#define KISPRECISEPAINTDEVICEWRAPPER_H
#include <QScopedPointer>
#include "kis_types.h"
#include "kritaimage_export.h"
class KoColorSpace;
class QRegion;
/**
* A special wrapper class for a paint device that allows working with
* parts of the source paint device as if it had higher bit depth.
*
* For example, you have an RGBA8 paint device, but you want all the
* blending happen in higher bit depth. You wrap your paint device (source paint
* device) into KisPrecisePaintDeviceWrapper, and the wrapper creates a temporary
* device (precise paint device) in RGBA16 colorspace. The you work with this precise
* paint device as usual, uploading and downloading pixel data to/from the source
* paint device using readRect() and writeRect()
*
* If the source device is already "precise", that is having the bit depth higher than
* 8 bit per channel, no temporary device is created. All the operations are forwarded
* directly to the source device
*
* Example:
*
* \code{.cpp}
* // initialize the wrapper with the source device,
* // it creates a precise device if needed
* KisPrecisePaintDeviceWrapper wrapper(sourceDevice);
*
* // download the data from the source device, the operation
* // might be cached due to keepRectsHistory option
* wrapper.readRect(accessRect);
*
* // start modifying the data
* KisPainter gc(wrapper.preciseDevice());
*
* // low opacity might be handled incorrectly in the original
* // color space, but we work in a precise one!
* gc.setOpacity(1);
* gc.bitBlt(accessRect.topLeft(), someOtherDevice, accessRect);
*
* // ... repeat multiple times if needed ...
*
* // upload the data back to the original source device
* wrapper.writeRect(accessRect);
* \endcode
*
*/
class KRITAIMAGE_EXPORT KisPrecisePaintDeviceWrapper
{
public:
/**
* Create a wrapper, attach it to \p device and create a temporary precise
* paint device if needed. The temporary device is created iff the source
* device has 8 bit bit-depth.
*
* \param device source device
* \param keepRectsHistory shown how many rects in readRect() should be cached
*/
KisPrecisePaintDeviceWrapper(KisPaintDeviceSP device, int keepRectsHistory = 50);
~KisPrecisePaintDeviceWrapper();
/**
* \return the color space of preciseDevice()
*/
const KoColorSpace* preciseColorSpace() const;
/**
* \return the source device attached to the wrapper
*/
KisPaintDeviceSP sourceDevice() const;
/**
* \return the precise device. If the source device color space is "precise", then
* there is no separate precise device, and the original device is returned
*/
KisPaintDeviceSP preciseDevice() const;
/**
* \return the region of the source device that is guaranteed to be cached by
* previous calls to readRect(). If one asks for reading a cached rect,
* it is not read and just reused.
*/
QRegion cachedRegion() const;
/**
* Reset the region of the cached data from the source paint device
*/
void resetCachedRegion();
/**
* Read rect \p rc from the source device and upload it into the precision device.
* If rounding correction is not used, the function does nothing.
*/
void readRect(const QRect &rc);
/**
* Write rect \p rc from the precision to source device
* If rounding correction is not used, the function does nothing.
*/
void writeRect(const QRect &rc);
/**
* \see readRect()
*/
void readRects(const QVector<QRect> &rects);
/**
* \see writeRect()
*/
void writeRects(const QVector<QRect> &rects);
private:
struct Private;
const QScopedPointer<Private> m_d;
};
#endif // KISPRECISEPAINTDEVICEWRAPPER_H
......@@ -1443,7 +1443,7 @@ void KisPaintDevice::clear(const QRect & rc)
void KisPaintDevice::fill(const QRect & rc, const KoColor &color)
{
Q_ASSERT(*color.colorSpace() == *colorSpace());
KIS_ASSERT_RECOVER_RETURN(*color.colorSpace() == *colorSpace());
m_d->currentStrategy()->fill(rc, color.data());
}
......
......@@ -399,6 +399,18 @@ void KisPainter::addDirtyRect(const QRect & rc)
}
}
void KisPainter::addDirtyRects(const QVector<QRect> &rects)
{
d->dirtyRects.reserve(d->dirtyRects.size() + rects.size());
Q_FOREACH (const QRect &rc, rects) {
const QRect r = rc.normalized();
if (r.isValid()) {
d->dirtyRects.append(rc);
}
}
}
inline bool KisPainter::Private::tryReduceSourceRect(const KisPaintDevice *srcDev,
QRect *srcRect,
qint32 *srcX,
......@@ -2640,6 +2652,13 @@ void KisPainter::setMirrorInformation(const QPointF& axesCenter, bool mirrorHori
d->mirrorVertically = mirrorVertically;
}
void KisPainter::copyMirrorInformationFrom(const KisPainter *other)
{
d->axesCenter = other->d->axesCenter;
d->mirrorHorizontally = other->d->mirrorHorizontally;
d->mirrorVertically = other->d->mirrorVertically;
}
bool KisPainter::hasMirroring() const
{
return d->mirrorHorizontally || d->mirrorVertically;
......@@ -2908,3 +2927,31 @@ void KisPainter::mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const
KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab);
}
const QVector<QRect> KisPainter::calculateAllMirroredRects(const QRect &rc)
{
QVector<QRect> rects;
KisLodTransform t(d->device);
QPoint effectiveAxesCenter = t.map(d->axesCenter).toPoint();
QRect baseRect = rc;
rects << baseRect;
if (d->mirrorHorizontally && d->mirrorVertically){
KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect);
rects << baseRect;
KritaUtils::mirrorRect(Qt::Vertical, effectiveAxesCenter, &baseRect);
rects << baseRect;
KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect);
rects << baseRect;
} else if (d->mirrorHorizontally) {
KritaUtils::mirrorRect(Qt::Horizontal, effectiveAxesCenter, &baseRect);
rects << baseRect;
} else if (d->mirrorVertically) {
KritaUtils::mirrorRect(Qt::Vertical, effectiveAxesCenter, &baseRect);
rects << baseRect;
}
return rects;
}
......@@ -635,6 +635,8 @@ public:
void setMirrorInformation(const QPointF &axesCenter, bool mirrorHorizontally, bool mirrorVertically);
void copyMirrorInformationFrom(const KisPainter *other);
/**
* Returns whether the mirroring methods will do any
* work when called
......@@ -663,6 +665,12 @@ public:
*/
void mirrorDab(Qt::Orientation direction, KisRenderedDab *dab) const;
/**
* Calculate the list of the mirrored rects that will be painted on the
* the canvas when calling renderMirrorMask() at al
*/
const QVector<QRect> calculateAllMirroredRects(const QRect &rc);
/// Set the current pattern
void setPattern(const KoPattern * pattern);
......@@ -764,9 +772,14 @@ public:
void setCompositeOp(const QString& op);
/**
* Add the r to the current dirty rect.
* Add \p r to the current set of dirty rects
*/
void addDirtyRect(const QRect &r);
/**
* Add \p rects to the current set of dirty rects
*/
void addDirtyRect(const QRect & r);
void addDirtyRects(const QVector<QRect> &rects);
/**
* Reset the selection to the given selection. All painter actions will be
......
......@@ -135,6 +135,20 @@ void KoColor::convertTo(const KoColorSpace * cs)
KoColorConversionTransformation::internalConversionFlags());
}
KoColor KoColor::convertedTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags) const
{
KoColor result(*this);
result.convertTo(cs, renderingIntent, conversionFlags);
return result;
}
KoColor KoColor::convertedTo(const KoColorSpace *cs) const
{
return convertedTo(cs,
KoColorConversionTransformation::internalRenderingIntent(),
KoColorConversionTransformation::internalConversionFlags());
}
void KoColor::setProfile(const KoColorProfile *profile)
{
const KoColorSpace *dstColorSpace =
......@@ -178,6 +192,54 @@ void KoColor::fromQColor(const QColor& c)
}
}
void KoColor::subtract(const KoColor &value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(*m_colorSpace == *value.colorSpace());
QVector<float> channels1(m_colorSpace->channelCount());
QVector<float> channels2(m_colorSpace->channelCount());
m_colorSpace->normalisedChannelsValue(m_data, channels1);
m_colorSpace->normalisedChannelsValue(value.data(), channels2);
for (int i = 0; i < channels1.size(); i++) {
channels1[i] -= channels2[i];
}
m_colorSpace->fromNormalisedChannelsValue(m_data, channels1);
}
KoColor KoColor::subtracted(const KoColor &value) const
{
KoColor result(*this);
result.subtract(value);
return result;
}
void KoColor::add(const KoColor &value)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(*m_colorSpace == *value.colorSpace());
QVector<float> channels1(m_colorSpace->channelCount());
QVector<float> channels2(m_colorSpace->channelCount());
m_colorSpace->normalisedChannelsValue(m_data, channels1);
m_colorSpace->normalisedChannelsValue(value.data(), channels2);
for (int i = 0; i < channels1.size(); i++) {
channels1[i] += channels2[i];
}
m_colorSpace->fromNormalisedChannelsValue(m_data, channels1);
}
KoColor KoColor::added(const KoColor &value) const
{
KoColor result(*this);
result.add(value);
return result;
}
#ifndef NDEBUG
void KoColor::dump() const
{
......@@ -296,3 +358,58 @@ QString KoColor::toQString(const KoColor &color)
}
return ls.join(" ");
}
QDebug operator<<(QDebug dbg, const KoColor &color)
{
dbg.nospace() << "KoColor (" << color.colorSpace()->id();
QList<KoChannelInfo*> channels = color.colorSpace()->channels();
for (auto it = channels.constBegin(); it != channels.constEnd(); ++it) {
KoChannelInfo *ch = (*it);
dbg.nospace() << ", " << ch->name() << ":";
switch (ch->channelValueType()) {
case KoChannelInfo::UINT8: {
const quint8 *ptr = reinterpret_cast<const quint8*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::UINT16: {
const quint16 *ptr = reinterpret_cast<const quint16*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::UINT32: {
const quint32 *ptr = reinterpret_cast<const quint32*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::FLOAT16: {
const half *ptr = reinterpret_cast<const half*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::FLOAT32: {
const float *ptr = reinterpret_cast<const float*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::FLOAT64: {
const double *ptr = reinterpret_cast<const double*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::INT8: {
const qint8 *ptr = reinterpret_cast<const qint8*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::INT16: {
const qint16 *ptr = reinterpret_cast<const qint16*>(color.data() + ch->pos());
dbg.nospace() << *ptr;
break;
} case KoChannelInfo::OTHER: {
const quint8 *ptr = reinterpret_cast<const quint8*>(color.data() + ch->pos());
dbg.nospace() << "undef(" << *ptr << ")";
break;
}
}
}
dbg.nospace() << ")";
return dbg.space();
}
......@@ -99,13 +99,25 @@ public:
const KoColorProfile *profile() const;
/// Convert this KoColor to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, do nothing. Returns the converted KoColor.
/// same as the original colorspace, do nothing
void convertTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags);
void convertTo(const KoColorSpace * cs);
/// Copies this color and converts it to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, just returns a copy
KoColor convertedTo(const KoColorSpace * cs,
KoColorConversionTransformation::Intent renderingIntent,
KoColorConversionTransformation::ConversionFlags conversionFlags) const;
/// Copies this color and converts it to the specified colorspace. If the specified colorspace is the
/// same as the original colorspace, just returns a copy
KoColor convertedTo(const KoColorSpace * cs) const;
/// assign new profile without converting pixel data
void setProfile(const KoColorProfile *profile);
......@@ -154,6 +166,35 @@ public:
return m_data;
}
/**
* Channelwise subtracts \p value from *this and stores the result in *this
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
void subtract(const KoColor &value);
/**
* Channelwise subtracts \p value from a copy of *this and returns the result
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
KoColor subtracted(const KoColor &value) const;
/**
* Channelwise adds \p value to *this and stores the result in *this
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
void add(const KoColor &value);
/**
* Channelwise adds \p value to a copy of *this and returns the result
*
* Throws a safe assert if the colorspaces of the two colors are different
*/
KoColor added(const KoColor &value) const;
/**
* Serialize this color following Create's swatch color specification available
* at http://create.freedesktop.org/wiki/index.php/Swatches_-_colour_file_format
......@@ -225,4 +266,7 @@ private:
Q_DECLARE_METATYPE(KoColor)
KRITAPIGMENT_EXPORT QDebug operator<<(QDebug dbg, const KoColor &color);
#endif
......@@ -38,16 +38,19 @@
#include <kis_fixed_paint_device.h>
#include <kis_lod_transform.h>
#include <kis_spacing_information.h>
#include <KoColorModelStandardIds.h>
KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPainter* painter, KisNodeSP node, KisImageSP image)
: KisBrushBasedPaintOp(settings, painter)
, m_firstRun(true)
, m_image(image)
, m_tempDev(painter->device()->createCompositionSourceDevice())
, m_preciseWrapper(painter->device())
, m_tempDev(m_preciseWrapper.preciseDevice()->createCompositionSourceDevice())
, m_backgroundPainter(new KisPainter(m_tempDev))
, m_smudgePainter(new KisPainter(m_tempDev))
, m_colorRatePainter(new KisPainter(m_tempDev))
, m_finalPainter(new KisPainter(m_preciseWrapper.preciseDevice()))
, m_smudgeRateOption()
, m_colorRateOption("ColorRate", KisPaintOpOption::GENERAL, false)
, m_smudgeRadiusOption()
......@@ -84,14 +87,19 @@ KisColorSmudgeOp::KisColorSmudgeOp(const KisPaintOpSettingsSP settings, KisPaint
// Smudge Painter works in default COMPOSITE_OVER mode
m_colorRatePainter->setCompositeOp(painter->compositeOp()->id());
m_finalPainter->setCompositeOp(COMPOSITE_COPY);
m_finalPainter->setSelection(painter->selection());
m_finalPainter->setChannelFlags(painter->channelFlags());
m_finalPainter->copyMirrorInformationFrom(painter);
m_paintColor = painter->paintColor().convertedTo(m_preciseWrapper.preciseColorSpace());
m_preciseColorRateCompositeOp = m_preciseWrapper.preciseColorSpace()->compositeOp(COMPOSITE_COPY);
m_rotationOption.applyFanCornersInfo(this);
}
KisColorSmudgeOp::~KisColorSmudgeOp()
{
delete m_backgroundPainter;
delete m_colorRatePainter;
delete m_smudgePainter;
}
void KisColorSmudgeOp::updateMask(const KisPaintInformation& info, double scale, double rotation, const QPointF &cursorPoint)
......@@ -199,10 +207,7 @@ KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
return spacingInfo;
}
// save the old opacity value and composite mode
quint8 oldOpacity = painter()->opacity();
QString oldCompositeOpId = painter()->compositeOp()->id();
qreal fpOpacity = (qreal(oldOpacity) / 255.0) * m_opacityOption.getOpacityf(info);
const qreal fpOpacity = (qreal(painter()->opacity()) / 255.0) * m_opacityOption.getOpacityf(info);
if (m_image && m_overlayModeOption.isChecked()) {
m_image->blockUpdates();
......@@ -215,28 +220,34 @@ KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
m_tempDev->clear(QRect(QPoint(), m_dstDabRect.size()));
}
if (m_smudgeRateOption.getMode() == KisSmudgeOption::SMEARING_MODE) {
m_smudgePainter->bitBlt(QPoint(), painter()->device(), srcDabRect);
const bool useDullingMode = m_smudgeRateOption.getMode() == KisSmudgeOption::DULLING_MODE;
// stored in the color space of the paintColor
KoColor dullingFillColor = m_paintColor;
if (!useDullingMode) {
m_preciseWrapper.readRect(srcDabRect);
m_smudgePainter->bitBlt(QPoint(), m_preciseWrapper.preciseDevice(), srcDabRect);
} else {
QPoint pt = (srcDabRect.topLeft() + hotSpot).toPoint();
if (m_smudgeRadiusOption.isChecked()) {
qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height());
m_smudgeRadiusOption.apply(*m_smudgePainter, info, effectiveSize, pt.x(), pt.y(), painter()->device());
const qreal effectiveSize = 0.5 * (m_dstDabRect.width() + m_dstDabRect.height());
KoColor color2 = m_smudgePainter->paintColor();
m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color2);
const QRect sampleRect = m_smudgeRadiusOption.sampleRect(info, effectiveSize, pt);
m_preciseWrapper.readRect(sampleRect);
} else {
KoColor color = painter()->paintColor();
m_smudgeRadiusOption.apply(&dullingFillColor, info, effectiveSize, pt.x(), pt.y(), m_preciseWrapper.preciseDevice());
KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()