diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -57,6 +57,7 @@ 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 diff --git a/libs/image/KisPrecisePaintDeviceWrapper.h b/libs/image/KisPrecisePaintDeviceWrapper.h new file mode 100644 --- /dev/null +++ b/libs/image/KisPrecisePaintDeviceWrapper.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * 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 + +#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 &rects); + + /** + * \see writeRect() + */ + void writeRects(const QVector &rects); + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif // KISPRECISEPAINTDEVICEWRAPPER_H diff --git a/libs/image/KisPrecisePaintDeviceWrapper.cpp b/libs/image/KisPrecisePaintDeviceWrapper.cpp new file mode 100644 --- /dev/null +++ b/libs/image/KisPrecisePaintDeviceWrapper.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018 Dmitry Kazakov + * + * 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 +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_sequential_iterator.h" + +#include +#include + +#include + +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(srcIt.rawData()); + const quint16 *precPtr = reinterpret_cast(precIt.rawDataConst()); + + for (int i = 0; i < channelCount; i++) { + *(srcPtr + i) = KoColorSpaceMaths::scaleToA(*(precPtr + i)); + } + } +} + +void KisPrecisePaintDeviceWrapper::readRects(const QVector &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(srcIt.rawDataConst()); + quint16 *precPtr = reinterpret_cast(precIt.rawData()); + + for (int i = 0; i < channelCount; i++) { + *(precPtr + i) = KoColorSpaceMaths::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 &rects) +{ + if (m_d->precDevice == m_d->srcDevice) return; + + Q_FOREACH (const QRect &rc, rects) { + writeRect(rc); + } +} + diff --git a/libs/image/kis_paint_device.cc b/libs/image/kis_paint_device.cc --- a/libs/image/kis_paint_device.cc +++ b/libs/image/kis_paint_device.cc @@ -1443,7 +1443,7 @@ 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()); } diff --git a/libs/image/kis_painter.h b/libs/image/kis_painter.h --- a/libs/image/kis_painter.h +++ b/libs/image/kis_painter.h @@ -635,6 +635,8 @@ 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 @@ */ 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 calculateAllMirroredRects(const QRect &rc); + /// Set the current pattern void setPattern(const KoPattern * pattern); @@ -764,9 +772,14 @@ 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 &rects); /** * Reset the selection to the given selection. All painter actions will be diff --git a/libs/image/kis_painter.cc b/libs/image/kis_painter.cc --- a/libs/image/kis_painter.cc +++ b/libs/image/kis_painter.cc @@ -399,6 +399,18 @@ } } +void KisPainter::addDirtyRects(const QVector &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 @@ 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 @@ KritaUtils::mirrorDab(direction, effectiveAxesCenter, dab); } + +const QVector KisPainter::calculateAllMirroredRects(const QRect &rc) +{ + QVector 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; +} diff --git a/libs/pigment/KoColor.h b/libs/pigment/KoColor.h --- a/libs/pigment/KoColor.h +++ b/libs/pigment/KoColor.h @@ -99,13 +99,25 @@ 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 @@ 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 @@ Q_DECLARE_METATYPE(KoColor) +KRITAPIGMENT_EXPORT QDebug operator<<(QDebug dbg, const KoColor &color); + + #endif diff --git a/libs/pigment/KoColor.cpp b/libs/pigment/KoColor.cpp --- a/libs/pigment/KoColor.cpp +++ b/libs/pigment/KoColor.cpp @@ -135,6 +135,20 @@ 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::subtract(const KoColor &value) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(*m_colorSpace == *value.colorSpace()); + + QVector channels1(m_colorSpace->channelCount()); + QVector 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 channels1(m_colorSpace->channelCount()); + QVector 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 @@ } return ls.join(" "); } + +QDebug operator<<(QDebug dbg, const KoColor &color) +{ + dbg.nospace() << "KoColor (" << color.colorSpace()->id(); + + QList 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(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::UINT16: { + const quint16 *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::UINT32: { + const quint32 *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::FLOAT16: { + const half *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::FLOAT32: { + const float *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::FLOAT64: { + const double *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::INT8: { + const qint8 *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::INT16: { + const qint16 *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << *ptr; + break; + } case KoChannelInfo::OTHER: { + const quint8 *ptr = reinterpret_cast(color.data() + ch->pos()); + dbg.nospace() << "undef(" << *ptr << ")"; + break; + } + } + } + dbg.nospace() << ")"; + return dbg.space(); +} diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.h +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.h @@ -34,11 +34,13 @@ #include "kis_rate_option.h" #include "kis_smudge_option.h" #include "kis_smudge_radius_option.h" +#include "KisPrecisePaintDeviceWrapper.h" class QPointF; class KoAbstractGradient; class KisBrushBasedPaintOpSettings; class KisPainter; +class KoColorSpace; class KisColorSmudgeOp: public KisBrushBasedPaintOp { @@ -60,10 +62,13 @@ private: bool m_firstRun; KisImageWSP m_image; + KisPrecisePaintDeviceWrapper m_preciseWrapper; + KoColor m_paintColor; KisPaintDeviceSP m_tempDev; - KisPainter* m_backgroundPainter; - KisPainter* m_smudgePainter; - KisPainter* m_colorRatePainter; + QScopedPointer m_backgroundPainter; + QScopedPointer m_smudgePainter; + QScopedPointer m_colorRatePainter; + QScopedPointer m_finalPainter; const KoAbstractGradient* m_gradient; KisPressureSizeOption m_sizeOption; KisPressureOpacityOption m_opacityOption; @@ -78,6 +83,8 @@ QRect m_dstDabRect; KisFixedPaintDeviceSP m_maskDab; QPointF m_lastPaintPos; + + const KoCompositeOp *m_preciseColorRateCompositeOp = 0; }; #endif // _KIS_COLORSMUDGEOP_H_ diff --git a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp --- a/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp +++ b/plugins/paintops/colorsmudge/kis_colorsmudgeop.cpp @@ -38,16 +38,19 @@ #include #include #include +#include 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 @@ // 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 @@ 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 @@ 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()); + } else { // get the pixel on the canvas that lies beneath the hot spot // of the dab and fill the temporary paint device with that color - KisCrossDeviceColorPickerInt colorPicker(painter()->device(), color); - colorPicker.pickColor(pt.x(), pt.y(), color.data()); - - m_smudgePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); + m_preciseWrapper.readRect(QRect(pt, QSize(1,1))); + KisCrossDeviceColorPickerInt colorPicker(m_preciseWrapper.preciseDevice(), dullingFillColor); + colorPicker.pickColor(pt.x(), pt.y(), dullingFillColor.data()); + KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); } } @@ -252,37 +263,64 @@ // or a gradient color (if enabled) // into the temporary painting device and use the user selected // composite mode - KoColor color = painter()->paintColor(); + KoColor color = m_paintColor; m_gradientOption.apply(color, m_gradient, info); - m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); + + if (!useDullingMode) { + KIS_SAFE_ASSERT_RECOVER(*m_colorRatePainter->device()->colorSpace() == *color.colorSpace()) { + color.convertTo(m_colorRatePainter->device()->colorSpace()); + } + + m_colorRatePainter->fill(0, 0, m_dstDabRect.width(), m_dstDabRect.height(), color); + } else { + KIS_SAFE_ASSERT_RECOVER(*dullingFillColor.colorSpace() == *color.colorSpace()) { + color.convertTo(dullingFillColor.colorSpace()); + } + KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); + + m_preciseColorRateCompositeOp->composite(dullingFillColor.data(), 0, + color.data(), 0, + 0, 0, + 1, 1, + m_colorRatePainter->opacity()); + } } + if (useDullingMode) { + KIS_SAFE_ASSERT_RECOVER_NOOP(*dullingFillColor.colorSpace() == *m_preciseWrapper.preciseColorSpace()); + m_tempDev->fill(QRect(0, 0, m_dstDabRect.width(), m_dstDabRect.height()), dullingFillColor); + } + + m_preciseWrapper.readRects(m_finalPainter->calculateAllMirroredRects(m_dstDabRect)); + // if color is disabled (only smudge) and "overlay mode" is enabled // then first blit the region under the brush from the image projection // to the painting device to prevent a rapid build up of alpha value // if the color to be smudged is semi transparent. if (m_image && m_overlayModeOption.isChecked() && !m_colorRateOption.isChecked()) { - painter()->setCompositeOp(COMPOSITE_COPY); - painter()->setOpacity(OPACITY_OPAQUE_U8); + m_finalPainter->setOpacity(OPACITY_OPAQUE_U8); m_image->blockUpdates(); - painter()->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); + // TODO: check if this code is correct in mirrored mode! Technically, the + // painter renders the mirrored dab only, so we should also prepare + // the overlay for it in all the places. + m_finalPainter->bitBlt(m_dstDabRect.topLeft(), m_image->projection(), m_dstDabRect); m_image->unblockUpdates(); } // set opacity calculated by the rate option - m_smudgeRateOption.apply(*painter(), info, 0.0, 1.0, fpOpacity); + m_smudgeRateOption.apply(*m_finalPainter, info, 0.0, 1.0, fpOpacity); // then blit the temporary painting device on the canvas at the current brush position // the alpha mask (maskDab) will be used here to only blit the pixels that are in the area (shape) of the brush - painter()->setCompositeOp(COMPOSITE_COPY); - painter()->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); - painter()->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); + m_finalPainter->bitBltWithFixedSelection(m_dstDabRect.x(), m_dstDabRect.y(), m_tempDev, m_maskDab, m_dstDabRect.width(), m_dstDabRect.height()); + m_finalPainter->renderMirrorMaskSafe(m_dstDabRect, m_tempDev, 0, 0, m_maskDab, !m_dabCache->needSeparateOriginal()); + + const QVector dirtyRects = m_finalPainter->takeDirtyRegion(); + m_preciseWrapper.writeRects(dirtyRects); - // restore orginal opacy and composite mode values - painter()->setOpacity(oldOpacity); - painter()->setCompositeOp(oldCompositeOpId); + painter()->addDirtyRects(dirtyRects); return spacingInfo; } diff --git a/plugins/paintops/colorsmudge/kis_smudge_radius_option.h b/plugins/paintops/colorsmudge/kis_smudge_radius_option.h --- a/plugins/paintops/colorsmudge/kis_smudge_radius_option.h +++ b/plugins/paintops/colorsmudge/kis_smudge_radius_option.h @@ -29,11 +29,13 @@ public: KisSmudgeRadiusOption(); + QRect sampleRect(const KisPaintInformation &info, qreal diameter, const QPoint &pos) const; + /** * Set the opacity of the painter based on the rate * and the curve (if checked) */ - void apply(KisPainter& painter, + void apply(KoColor *resultColor, const KisPaintInformation& info, qreal diameter, qreal posx, diff --git a/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp b/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp --- a/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp +++ b/plugins/paintops/colorsmudge/kis_smudge_radius_option.cpp @@ -48,7 +48,17 @@ setValueRange(0.0,300.0); } -void KisSmudgeRadiusOption::apply(KisPainter& painter, +QRect KisSmudgeRadiusOption::sampleRect(const KisPaintInformation& info, + qreal diameter, + const QPoint &pos) const +{ + const qreal sliderValue = computeSizeLikeValue(info); + const int smudgeRadius = ((sliderValue * diameter) * 0.5) / 100.0; + + return kisGrowRect(QRect(pos, QSize(1,1)), smudgeRadius + 1); +} + +void KisSmudgeRadiusOption::apply(KoColor *resultColor, const KisPaintInformation& info, qreal diameter, qreal posx, @@ -62,17 +72,17 @@ int smudgeRadius = ((sliderValue * diameter) * 0.5) / 100.0; - KoColor color = painter.paintColor(); + KoColor color(Qt::transparent, dev->colorSpace()); + if (smudgeRadius == 1) { dev->pixel(posx, posy, &color); - painter.setPaintColor(color); } else { const KoColorSpace* cs = dev->colorSpace(); - int pixelSize = cs->pixelSize(); + const int pixelSize = cs->pixelSize(); quint8* data = new quint8[pixelSize]; - static quint8** pixels = new quint8*[2]; + quint8* pixels[2]; qint16 weights[2]; pixels[1] = new quint8[pixelSize]; @@ -86,9 +96,10 @@ int i = 0; int k = 0; int j = 0; + KisRandomConstAccessorSP accessor = dev->createRandomConstAccessorNG(0, 0); - KisCrossDeviceColorPickerInt colorPicker(painter.device(), color); - colorPicker.pickColor(posx, posy, color.data()); + accessor->moveTo(posx, posy); + memcpy(color.data(), accessor->rawDataConst(), pixelSize); for (int y = 0; y <= smudgeRadius; y = y + loop_increment) { for (int x = 0; x <= smudgeRadius; x = x + loop_increment) { @@ -139,17 +150,15 @@ } - KoColor color = KoColor(pixels[0],cs); - painter.setPaintColor(color); + color = KoColor(pixels[0],cs); for (int l = 0; l < 2; l++){ delete[] pixels[l]; } - // delete[] pixels; delete[] data; } - + *resultColor = color.convertedTo(resultColor->colorSpace()); } void KisSmudgeRadiusOption::writeOptionSetting(KisPropertiesConfigurationSP setting) const