diff --git a/3rdparty/ext_openexr/CMakeLists.txt b/3rdparty/ext_openexr/CMakeLists.txt --- a/3rdparty/ext_openexr/CMakeLists.txt +++ b/3rdparty/ext_openexr/CMakeLists.txt @@ -1,17 +1,33 @@ -SET(PREFIX_ext_openexr "${EXTPREFIX}" ) +SET(EXTPREFIX_openexr "${EXTPREFIX}" ) +if (MSVC OR MINGW) ExternalProject_Add( ext_openexr DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} URL http://files.kde.org/krita/build/dependencies/openexr-2.2.0.tar.gz URL_MD5 b64e931c82aa3790329c21418373db4e - + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/openexr.diff COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/patch_mingw.patch - INSTALL_DIR ${PREFIX_ext_openexr} - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${PREFIX_ext_openexr} -DILMBASE_PACKAGE_PREFIX=${PREFIX_ext_openexr} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DNAMESPACE_VERSIONING=OFF - + INSTALL_DIR ${EXTPREFIX_openexr} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_openexr} -DILMBASE_PACKAGE_PREFIX=${EXTPREFIX_openexr} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DNAMESPACE_VERSIONING=OFF + UPDATE_COMMAND "" ALWAYS 0 DEPENDS ext_ilmbase ext_zlib ) +else() +ExternalProject_Add(ext_openexr + DOWNLOAD_DIR ${EXTERNALS_DOWNLOAD_DIR} + URL http://files.kde.org/krita/build/dependencies/openexr-2.2.0.tar.gz + URL_MD5 b64e931c82aa3790329c21418373db4e + + PATCH_COMMAND ${PATCH_COMMAND} -p1 -i ${CMAKE_CURRENT_SOURCE_DIR}/openexr.diff + + INSTALL_DIR ${EXTPREFIX_openexr} + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTPREFIX_openexr} -DILMBASE_PACKAGE_PREFIX=${EXTPREFIX_openexr} -DCMAKE_BUILD_TYPE=${GLOBAL_BUILD_TYPE} ${GLOBAL_PROFILE} -DNAMESPACE_VERSIONING=OFF + UPDATE_COMMAND "" + ALWAYS 0 + DEPENDS ext_ilmbase ext_zlib +) +endif() diff --git a/krita/krita.action b/krita/krita.action --- a/krita/krita.action +++ b/krita/krita.action @@ -2208,6 +2208,18 @@ false + + filterMask + &Colorize Mask + + Colorize Mask + Colorize Mask + 100000 + 0 + + false + + transformMask &Transform Mask... diff --git a/libs/image/CMakeLists.txt b/libs/image/CMakeLists.txt --- a/libs/image/CMakeLists.txt +++ b/libs/image/CMakeLists.txt @@ -112,6 +112,11 @@ generator/kis_generator_registry.cpp floodfill/kis_fill_interval_map.cpp floodfill/kis_scanline_fill.cpp + lazybrush/kis_min_cut_worker.cpp + lazybrush/kis_lazy_fill_tools.cpp + lazybrush/kis_multiway_cut.cpp + lazybrush/kis_colorize_mask.cpp + lazybrush/kis_colorize_job.cpp kis_adjustment_layer.cc kis_selection_based_layer.cpp kis_node_filter_interface.cpp diff --git a/libs/image/floodfill/kis_scanline_fill.h b/libs/image/floodfill/kis_scanline_fill.h --- a/libs/image/floodfill/kis_scanline_fill.h +++ b/libs/image/floodfill/kis_scanline_fill.h @@ -34,9 +34,34 @@ KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect); ~KisScanlineFill(); + /** + * Fill the source device with \p fillColor + */ void fillColor(const KoColor &fillColor); + + /** + * Fill \p externalDevice with \p fillColor basing on the contents + * of the source device. + */ + void fillColor(const KoColor &fillColor, KisPaintDeviceSP externalDevice); + + /** + * Fill \p pixelSelection with the opacity of the contiguous area + */ void fillSelection(KisPixelSelectionSP pixelSelection); + /** + * Clear the contiguous non-zero area of the device + * + * WARNING: the threshold parameter is not counted! + */ + void clearNonZeroComponent(); + + /** + * Set the threshold of the filling operation + * + * Used in all functions except clearNonZeroComponent() + */ void setThreshold(int threshold); private: diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -92,6 +92,46 @@ int m_pixelSize; }; +template +class FillWithColorExternal : public BaseClass +{ +public: + typedef KisRandomConstAccessorSP SourceAccessorType; + + SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { + return device->createRandomConstAccessorNG(0, 0); + } + +public: + void setDestinationDevice(KisPaintDeviceSP device) { + m_externalDevice = device; + m_it = m_externalDevice->createRandomAccessorNG(0,0); + } + + void setFillColor(const KoColor &sourceColor) { + m_sourceColor = sourceColor; + m_pixelSize = sourceColor.colorSpace()->pixelSize(); + m_data = m_sourceColor.data(); + } + + ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { + Q_UNUSED(dstPtr); + + m_it->moveTo(x, y); + if (opacity == MAX_SELECTED) { + memcpy(m_it->rawData(), m_data, m_pixelSize); + } + } + +private: + KisPaintDeviceSP m_externalDevice; + KisRandomAccessorSP m_it; + + KoColor m_sourceColor; + const quint8 *m_data; + int m_pixelSize; +}; + class DifferencePolicySlow { public: @@ -188,6 +228,40 @@ int m_threshold; }; +class IsNonNullPolicySlow +{ +public: + ALWAYS_INLINE void initDifferencies(KisPaintDeviceSP device, const KoColor &srcPixel) { + Q_UNUSED(srcPixel); + + m_pixelSize = device->pixelSize(); + m_testPixel.resize(m_pixelSize); + } + + ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { + return memcmp(m_testPixel.data(), pixelPtr, m_pixelSize); + } + +private: + int m_pixelSize; + QByteArray m_testPixel; +}; + +template +class IsNonNullPolicyOptimized +{ +public: + ALWAYS_INLINE void initDifferencies(KisPaintDeviceSP device, const KoColor &srcPixel) { + Q_UNUSED(device); + Q_UNUSED(srcPixel); + } + + ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { + SrcPixelType *pixel = reinterpret_cast(pixelPtr); + return *pixel == 0; + } +}; + struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; @@ -427,6 +501,46 @@ } } +void KisScanlineFill::fillColor(const KoColor &fillColor, KisPaintDeviceSP externalDevice) +{ + KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); + KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); + + const int pixelSize = m_d->device->pixelSize(); + + if (pixelSize == 1) { + SelectionPolicy, FillWithColorExternal> + policy(m_d->device, srcColor, m_d->threshold); + policy.setDestinationDevice(externalDevice); + policy.setFillColor(fillColor); + runImpl(policy); + } else if (pixelSize == 2) { + SelectionPolicy, FillWithColorExternal> + policy(m_d->device, srcColor, m_d->threshold); + policy.setDestinationDevice(externalDevice); + policy.setFillColor(fillColor); + runImpl(policy); + } else if (pixelSize == 4) { + SelectionPolicy, FillWithColorExternal> + policy(m_d->device, srcColor, m_d->threshold); + policy.setDestinationDevice(externalDevice); + policy.setFillColor(fillColor); + runImpl(policy); + } else if (pixelSize == 8) { + SelectionPolicy, FillWithColorExternal> + policy(m_d->device, srcColor, m_d->threshold); + policy.setDestinationDevice(externalDevice); + policy.setFillColor(fillColor); + runImpl(policy); + } else { + SelectionPolicy + policy(m_d->device, srcColor, m_d->threshold); + policy.setDestinationDevice(externalDevice); + policy.setFillColor(fillColor); + runImpl(policy); + } +} + void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); @@ -462,6 +576,39 @@ } } +void KisScanlineFill::clearNonZeroComponent() +{ + const int pixelSize = m_d->device->pixelSize(); + KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); + + if (pixelSize == 1) { + SelectionPolicy, FillWithColor> + policy(m_d->device, srcColor, m_d->threshold); + policy.setFillColor(srcColor); + runImpl(policy); + } else if (pixelSize == 2) { + SelectionPolicy, FillWithColor> + policy(m_d->device, srcColor, m_d->threshold); + policy.setFillColor(srcColor); + runImpl(policy); + } else if (pixelSize == 4) { + SelectionPolicy, FillWithColor> + policy(m_d->device, srcColor, m_d->threshold); + policy.setFillColor(srcColor); + runImpl(policy); + } else if (pixelSize == 8) { + SelectionPolicy, FillWithColor> + policy(m_d->device, srcColor, m_d->threshold); + policy.setFillColor(srcColor); + runImpl(policy); + } else { + SelectionPolicy + policy(m_d->device, srcColor, m_d->threshold); + policy.setFillColor(srcColor); + runImpl(policy); + } +} + void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/kis_cached_paint_device.h --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/kis_cached_paint_device.h @@ -21,7 +21,6 @@ #include "tiles3/kis_lockless_stack.h" - class KisCachedPaintDevice { public: @@ -45,4 +44,26 @@ KisLocklessStack m_stack; }; +class KisCachedSelection +{ +public: + KisSelectionSP getSelection() { + KisSelectionSP selection; + + if(!m_stack.pop(selection)) { + selection = new KisSelection(); + } + + return selection; + } + + void putSelection(KisSelectionSP selection) { + selection->clear(); + m_stack.push(selection); + } + +private: + KisLocklessStack m_stack; +}; + #endif /* __KIS_CACHED_PAINT_DEVICE_H */ diff --git a/libs/image/kis_convolution_worker_spatial.h b/libs/image/kis_convolution_worker_spatial.h --- a/libs/image/kis_convolution_worker_spatial.h +++ b/libs/image/kis_convolution_worker_spatial.h @@ -270,7 +270,7 @@ qreal channelPixelValue; if (additionalMultiplierActive) { - channelPixelValue = (interimConvoResult * m_kernelFactor + m_absoluteOffset[channel]) * additionalMultiplier; + channelPixelValue = (interimConvoResult * m_kernelFactor) * additionalMultiplier + m_absoluteOffset[channel]; } else { channelPixelValue = interimConvoResult * m_kernelFactor + m_absoluteOffset[channel]; } @@ -287,6 +287,11 @@ if (m_alphaCachePos >= 0) { qreal alphaValue = convolveOneChannelFromCache(dstPtr, m_alphaCachePos); + // TODO: we need a special case for applying LoG filter, + // when the alpha i suniform and therefore should not be + // filtered! + //alphaValue = 255.0; + if (alphaValue != 0.0) { qreal alphaValueInv = 1.0 / alphaValue; diff --git a/libs/image/kis_gaussian_kernel.h b/libs/image/kis_gaussian_kernel.h --- a/libs/image/kis_gaussian_kernel.h +++ b/libs/image/kis_gaussian_kernel.h @@ -50,6 +50,14 @@ qreal xRadius, qreal yRadius, const QBitArray &channelFlags, KoUpdater *updater); + + static Matrix createLoGMatrix(qreal radius); + + static void applyLoG(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, + const QBitArray &channelFlags, + KoUpdater *progressUpdater); }; #endif /* __KIS_GAUSSIAN_KERNEL_H */ diff --git a/libs/image/kis_gaussian_kernel.cpp b/libs/image/kis_gaussian_kernel.cpp --- a/libs/image/kis_gaussian_kernel.cpp +++ b/libs/image/kis_gaussian_kernel.cpp @@ -18,6 +18,7 @@ #include "kis_gaussian_kernel.h" +#include "kis_global.h" #include "kis_convolution_kernel.h" #include #include @@ -145,3 +146,94 @@ painter.applyMatrix(kernelVertical, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } } + +Matrix +KisGaussianKernel::createLoGMatrix(qreal radius) +{ + int kernelSize = 4 * std::ceil(radius) + 1; + Matrix matrix(kernelSize, kernelSize); + + const qreal sigma = radius/* / sqrt(2)*/; + const qreal multiplicand = -1 / (M_PI * pow2(pow2(sigma))); + const qreal exponentMultiplicand = 1 / (2 * pow2(sigma)); + + /** + * The kernel size should always be odd, then the position of the + * central pixel can be easily calculated + */ + KIS_ASSERT_RECOVER_NOOP(kernelSize & 0x1); + const int center = kernelSize / 2; + + for (int y = 0; y < kernelSize; y++) { + const qreal yDistance = center - y; + for (int x = 0; x < kernelSize; x++) { + const qreal xDistance = center - x; + const qreal distance = pow2(xDistance) + pow2(yDistance); + const qreal normalizedDistance = exponentMultiplicand * distance; + + matrix(x, y) = multiplicand * + (1.0 - normalizedDistance) * + exp(-normalizedDistance); + } + } + + qreal lateral = matrix.sum() - matrix(center, center); + matrix(center, center) = -lateral; + + qreal positiveSum = 0; + qreal sideSum = 0; + qreal quarterSum = 0; + + for (int y = 0; y < kernelSize; y++) { + for (int x = 0; x < kernelSize; x++) { + const qreal value = matrix(x, y); + if (value > 0) { + positiveSum += value; + } + if (x > center) { + sideSum += value; + } + if (x > center && y > center) { + quarterSum += value; + } + } + } + + + const qreal scale = 2.0 / positiveSum; + matrix *= scale; + positiveSum *= scale; + sideSum *= scale; + quarterSum *= scale; + + //qDebug() << ppVar(positiveSum) << ppVar(sideSum) << ppVar(quarterSum); + + return matrix; +} + +#include "kis_transaction.h" + +void KisGaussianKernel::applyLoG(KisPaintDeviceSP device, + const QRect& rect, + qreal radius, + const QBitArray &channelFlags, + KoUpdater *progressUpdater) +{ + QPoint srcTopLeft = rect.topLeft(); + + KisConvolutionPainter painter(device); + painter.setChannelFlags(channelFlags); + painter.setProgress(progressUpdater); + + Matrix matrix = createLoGMatrix(radius); + qDebug() << ppVar(matrix.sum()); + KisConvolutionKernelSP kernel = + KisConvolutionKernel::fromMatrix(matrix, + 0, + 0); + + // TODO: move applying transaction to a higher level! + KisTransaction t(device); + painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); + t.end(); +} diff --git a/libs/image/kis_indirect_painting_support.h b/libs/image/kis_indirect_painting_support.h --- a/libs/image/kis_indirect_painting_support.h +++ b/libs/image/kis_indirect_painting_support.h @@ -29,6 +29,7 @@ class KisPainter; class KUndo2MagicString; class KoCompositeOp; +class KoColor; /** * For classes that support indirect painting. @@ -47,8 +48,9 @@ bool hasTemporaryTarget() const; + virtual void setCurrentColor(const KoColor &color); void setTemporaryTarget(KisPaintDeviceSP t); - void setTemporaryCompositeOp(const KoCompositeOp* c); + void setTemporaryCompositeOp(const QString &id); void setTemporaryOpacity(quint8 o); void setTemporaryChannelFlags(const QBitArray& channelFlags); void setTemporarySelection(KisSelectionSP selection); @@ -65,8 +67,7 @@ * Writes the temporary target into the paint device of the layer. * This action will lock the temporary target itself. */ - void mergeToLayer(KisNodeSP layer, KisUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID = -1); - void mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID = -1); + virtual void mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID = -1); /** @@ -84,8 +85,12 @@ */ void unlockTemporaryTarget() const; - KisPaintDeviceSP temporaryTarget(); - const KisPaintDeviceSP temporaryTarget() const; + KisPaintDeviceSP temporaryTarget() const; + +protected: + void mergeToLayerImpl(KisPaintDeviceSP dst, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText, int timedID = -1); + virtual void writeMergeData(KisPainter *painter, KisPaintDeviceSP src); + void lockTemporaryTargetForWrite() const; private: friend class KisPainterBasedStrokeStrategy; diff --git a/libs/image/kis_indirect_painting_support.cpp b/libs/image/kis_indirect_painting_support.cpp --- a/libs/image/kis_indirect_painting_support.cpp +++ b/libs/image/kis_indirect_painting_support.cpp @@ -35,7 +35,7 @@ struct Q_DECL_HIDDEN KisIndirectPaintingSupport::Private { // To simulate the indirect painting KisPaintDeviceSP temporaryTarget; - const KoCompositeOp* compositeOp; + QString compositeOp; quint8 compositeOpacity; QBitArray channelFlags; KisSelectionSP selection; @@ -47,22 +47,26 @@ KisIndirectPaintingSupport::KisIndirectPaintingSupport() : d(new Private) { - d->compositeOp = 0; } KisIndirectPaintingSupport::~KisIndirectPaintingSupport() { delete d; } +void KisIndirectPaintingSupport::setCurrentColor(const KoColor &color) +{ + Q_UNUSED(color); +} + void KisIndirectPaintingSupport::setTemporaryTarget(KisPaintDeviceSP t) { d->temporaryTarget = t; } -void KisIndirectPaintingSupport::setTemporaryCompositeOp(const KoCompositeOp* c) +void KisIndirectPaintingSupport::setTemporaryCompositeOp(const QString &id) { - d->compositeOp = c; + d->compositeOp = id; } void KisIndirectPaintingSupport::setTemporaryOpacity(quint8 o) @@ -85,17 +89,17 @@ d->lock.lockForRead(); } -void KisIndirectPaintingSupport::unlockTemporaryTarget() const +void KisIndirectPaintingSupport::lockTemporaryTargetForWrite() const { - d->lock.unlock(); + d->lock.lockForWrite(); } -KisPaintDeviceSP KisIndirectPaintingSupport::temporaryTarget() +void KisIndirectPaintingSupport::unlockTemporaryTarget() const { - return d->temporaryTarget; + d->lock.unlock(); } -const KisPaintDeviceSP KisIndirectPaintingSupport::temporaryTarget() const +KisPaintDeviceSP KisIndirectPaintingSupport::temporaryTarget() const { return d->temporaryTarget; } @@ -112,32 +116,24 @@ void KisIndirectPaintingSupport::setupTemporaryPainter(KisPainter *painter) const { - painter->setOpacity(d->compositeOpacity); - painter->setCompositeOp(d->compositeOp); - painter->setChannelFlags(d->channelFlags); - painter->setSelection(d->selection); -} - -void KisIndirectPaintingSupport::mergeToLayer(KisNodeSP layer, KisUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) -{ - mergeToLayerImpl(layer, undoAdapter, transactionText,timedID); + painter->setOpacity(d->compositeOpacity); + painter->setCompositeOp(d->compositeOp); + painter->setChannelFlags(d->channelFlags); + painter->setSelection(d->selection); } void KisIndirectPaintingSupport::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) { - mergeToLayerImpl(layer, undoAdapter, transactionText,timedID); + mergeToLayerImpl(layer->paintDevice(), undoAdapter, transactionText, timedID); } -template -void KisIndirectPaintingSupport::mergeToLayerImpl(KisNodeSP layer, - UndoAdapter *undoAdapter, - const KUndo2MagicString &transactionText,int timedID) +void KisIndirectPaintingSupport::mergeToLayerImpl(KisPaintDeviceSP dst, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText, int timedID) { /** * We do not apply selection here, because it has already * been taken into account in a tool code */ - KisPainter gc(layer->paintDevice()); + KisPainter gc(dst); setupTemporaryPainter(&gc); d->lock.lockForWrite(); @@ -148,9 +144,9 @@ if(undoAdapter) { gc.beginTransaction(transactionText,timedID); } - Q_FOREACH (const QRect &rc, d->temporaryTarget->region().rects()) { - gc.bitBlt(rc.topLeft(), d->temporaryTarget, rc); - } + + writeMergeData(&gc, d->temporaryTarget); + releaseResources(); if(undoAdapter) { @@ -160,6 +156,13 @@ d->lock.unlock(); } +void KisIndirectPaintingSupport::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) +{ + Q_FOREACH (const QRect &rc, src->region().rects()) { + painter->bitBlt(rc.topLeft(), src, rc); + } +} + void KisIndirectPaintingSupport::releaseResources() { d->temporaryTarget = 0; diff --git a/libs/image/kis_layer_properties_icons.h b/libs/image/kis_layer_properties_icons.h --- a/libs/image/kis_layer_properties_icons.h +++ b/libs/image/kis_layer_properties_icons.h @@ -40,6 +40,9 @@ static const KoID passThrough; static const KoID selectionActive; static const KoID colorLabelIndex; + static const KoID colorizeNeedsUpdate; + static const KoID colorizeShowKeyStrokes; + static const KoID colorizeShowColoring; static KisLayerPropertiesIcons* instance(); diff --git a/libs/image/kis_layer_properties_icons.cpp b/libs/image/kis_layer_properties_icons.cpp --- a/libs/image/kis_layer_properties_icons.cpp +++ b/libs/image/kis_layer_properties_icons.cpp @@ -35,6 +35,9 @@ const KoID KisLayerPropertiesIcons::passThrough("pass-through", ki18n("Pass Through")); const KoID KisLayerPropertiesIcons::selectionActive("selection-active", ki18n("Active")); const KoID KisLayerPropertiesIcons::colorLabelIndex("color-label", ki18n("Color Label")); +const KoID KisLayerPropertiesIcons::colorizeNeedsUpdate("colorize-needs-update", ki18n("Update")); +const KoID KisLayerPropertiesIcons::colorizeShowKeyStrokes("colorize-show-key-strokes", ki18n("Show Key Strokes")); +const KoID KisLayerPropertiesIcons::colorizeShowColoring("colorize-show-coloring", ki18n("Show Coloring")); struct IconsPair { @@ -80,6 +83,9 @@ m_d->icons.insert(onionSkins.id(), IconsPair(KisIconUtils::loadIcon("onionOn"), KisIconUtils::loadIcon("onionOff"))); m_d->icons.insert(passThrough.id(), IconsPair(KisIconUtils::loadIcon("passthrough-enabled"), KisIconUtils::loadIcon("passthrough-disabled"))); m_d->icons.insert(selectionActive.id(), IconsPair(KisIconUtils::loadIcon("local_selection_active"), KisIconUtils::loadIcon("local_selection_inactive"))); + m_d->icons.insert(colorizeNeedsUpdate.id(), IconsPair(KisIconUtils::loadIcon("local_selection_active"), KisIconUtils::loadIcon("local_selection_inactive"))); + m_d->icons.insert(colorizeShowKeyStrokes.id(), IconsPair(KisIconUtils::loadIcon("onionOn"), KisIconUtils::loadIcon("onionOff"))); + m_d->icons.insert(colorizeShowColoring.id(), IconsPair(KisIconUtils::loadIcon("transparency-locked"), KisIconUtils::loadIcon("transparency-unlocked"))); } KisBaseNode::Property KisLayerPropertiesIcons::getProperty(const KoID &id, bool state) diff --git a/libs/image/kis_mask.h b/libs/image/kis_mask.h --- a/libs/image/kis_mask.h +++ b/libs/image/kis_mask.h @@ -64,7 +64,7 @@ XXX: For now, all masks are 8 bit. Make the channel depth settable. */ -class KRITAIMAGE_EXPORT KisMask : public KisNode, KisIndirectPaintingSupport +class KRITAIMAGE_EXPORT KisMask : public KisNode, public KisIndirectPaintingSupport { Q_OBJECT diff --git a/libs/image/kis_paint_device.h b/libs/image/kis_paint_device.h --- a/libs/image/kis_paint_device.h +++ b/libs/image/kis_paint_device.h @@ -243,6 +243,12 @@ QRegion region() const; /** + * The slow version of region() that searches for exact bounds of + * each rectangle in the region + */ + QRegion regionExact() const; + + /** * Cut the paint device down to the specified rect. If the crop * area is bigger than the paint device, nothing will happen. */ 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 @@ -66,6 +66,7 @@ #include "kis_transform_worker.h" #include "kis_filter_strategy.h" +#include "krita_utils.h" struct KisPaintDeviceSPStaticRegistrar { @@ -1287,6 +1288,28 @@ return endRect; } +QRegion KisPaintDevice::regionExact() const +{ + QRegion resultRegion; + QVector rects = region().rects(); + + Impl::CheckNonDefault compareOp(pixelSize(), defaultPixel()); + + Q_FOREACH (const QRect &rc1, rects) { + const int patchSize = 64; + QVector smallerRects = KritaUtils::splitRectIntoPatches(rc1, QSize(patchSize, patchSize)); + Q_FOREACH (const QRect &rc2, smallerRects) { + + const QRect result = + Impl::calculateExactBoundsImpl(this, rc2, QRect(), compareOp); + + if (!result.isEmpty()) { + resultRegion += result; + } + } + } + return resultRegion; +} void KisPaintDevice::crop(qint32 x, qint32 y, qint32 w, qint32 h) { 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 @@ -103,6 +103,9 @@ const QRect &originalSrcRect, KisSelectionSP selection); + static KisPaintDeviceSP convertToAlphaAsAlpha(KisPaintDeviceSP src); + static KisPaintDeviceSP convertToAlphaAsGray(KisPaintDeviceSP src); + /** * Start painting on the specified device. Not undoable. */ 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 @@ -264,6 +264,44 @@ } } +KisPaintDeviceSP KisPainter::convertToAlphaAsAlpha(KisPaintDeviceSP src) +{ + const KoColorSpace *srcCS = src->colorSpace(); + const QRect processRect = src->extent(); + KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + + KisSequentialIterator srcIt(src, processRect); + KisSequentialIterator dstIt(dst, processRect); + + do { + quint8 *srcPtr = srcIt.rawData(); + quint8 *alpha8Ptr = dstIt.rawData(); + + *alpha8Ptr = srcCS->opacityU8(srcPtr); + } while (srcIt.nextPixel() && dstIt.nextPixel()); + + return dst; +} + +KisPaintDeviceSP KisPainter::convertToAlphaAsGray(KisPaintDeviceSP src) +{ + const KoColorSpace *srcCS = src->colorSpace(); + const QRect processRect = src->extent(); + KisPaintDeviceSP dst = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + + KisSequentialIterator srcIt(src, processRect); + KisSequentialIterator dstIt(dst, processRect); + + do { + quint8 *srcPtr = srcIt.rawData(); + quint8 *alpha8Ptr = dstIt.rawData(); + + *alpha8Ptr = srcCS->intensity8(srcPtr); + } while (srcIt.nextPixel() && dstIt.nextPixel()); + + return dst; +} + void KisPainter::begin(KisPaintDeviceSP device) { begin(device, d->selection); diff --git a/libs/image/kis_pixel_selection.h b/libs/image/kis_pixel_selection.h --- a/libs/image/kis_pixel_selection.h +++ b/libs/image/kis_pixel_selection.h @@ -81,6 +81,11 @@ void clear(); /** + * Copies alpha channel form the specified \p src device + */ + void copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect); + + /** * Apply a selection to the selection using the specified selection mode */ void applySelection(KisPixelSelectionSP selection, SelectionAction action); diff --git a/libs/image/kis_pixel_selection.cpp b/libs/image/kis_pixel_selection.cpp --- a/libs/image/kis_pixel_selection.cpp +++ b/libs/image/kis_pixel_selection.cpp @@ -155,6 +155,25 @@ } } +void KisPixelSelection::copyAlphaFrom(KisPaintDeviceSP src, const QRect &processRect) +{ + const KoColorSpace *srcCS = src->colorSpace(); + + KisSequentialConstIterator srcIt(src, processRect); + KisSequentialIterator dstIt(this, processRect); + + do { + const quint8 *srcPtr = srcIt.rawDataConst(); + quint8 *alpha8Ptr = dstIt.rawData(); + + *alpha8Ptr = srcCS->opacityU8(srcPtr); + } while (srcIt.nextPixel() && dstIt.nextPixel()); + + m_d->outlineCacheValid = false; + m_d->outlineCache = QPainterPath(); + m_d->invalidateThumbnailImage(); +} + void KisPixelSelection::addSelection(KisPixelSelectionSP selection) { QRect r = selection->selectedRect(); diff --git a/libs/image/krita_utils.h b/libs/image/krita_utils.h --- a/libs/image/krita_utils.h +++ b/libs/image/krita_utils.h @@ -133,6 +133,9 @@ QImage KRITAIMAGE_EXPORT convertQImageToGrayA(const QImage &image); QColor KRITAIMAGE_EXPORT blendColors(const QColor &c1, const QColor &c2, qreal r1); + + void KRITAIMAGE_EXPORT applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func); + void KRITAIMAGE_EXPORT filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func); } #endif /* __KRITA_UTILS_H */ diff --git a/libs/image/krita_utils.cpp b/libs/image/krita_utils.cpp --- a/libs/image/krita_utils.cpp +++ b/libs/image/krita_utils.cpp @@ -38,6 +38,7 @@ #include "kis_image_config.h" #include "kis_debug.h" #include "kis_node.h" +#include "kis_sequential_iterator.h" namespace KritaUtils @@ -506,4 +507,20 @@ c1.greenF() * r1 + c2.greenF() * r2, c1.blueF() * r1 + c2.blueF() * r2); } + + void applyToAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func) { + KisSequentialConstIterator dstIt(dev, rc); + do { + const quint8 *dstPtr = dstIt.rawDataConst(); + func(*dstPtr); + } while (dstIt.nextPixel()); + } + + void filterAlpha8Device(KisPaintDeviceSP dev, const QRect &rc, std::function func) { + KisSequentialIterator dstIt(dev, rc); + do { + quint8 *dstPtr = dstIt.rawData(); + *dstPtr = func(*dstPtr); + } while (dstIt.nextPixel()); + } } diff --git a/libs/image/lazybrush/kis_colorize_job.h b/libs/image/lazybrush/kis_colorize_job.h new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_colorize_job.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 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 __KIS_COLORIZE_JOB_H +#define __KIS_COLORIZE_JOB_H + +#include +#include + +#include "kis_spontaneous_job.h" +#include "kis_multiway_cut.h" + + +class KisColorizeJob : public QObject, public KisSpontaneousJob +{ + Q_OBJECT +public: + KisColorizeJob(KisPaintDeviceSP src, + KisPaintDeviceSP dst, + KisPaintDeviceSP filteredSource, + const QRect &boundingRect); + ~KisColorizeJob(); + + void addKeyStroke(KisPaintDeviceSP dev, const KoColor &color); + + void run(); + bool overrides(const KisSpontaneousJob *otherJob); + int levelOfDetail() const; + +Q_SIGNALS: + void sigFinished(); + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif /* __KIS_COLORIZE_JOB_H */ diff --git a/libs/image/lazybrush/kis_colorize_job.cpp b/libs/image/lazybrush/kis_colorize_job.cpp new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_colorize_job.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2016 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 "kis_colorize_job.h" + +#include + + +#include "kis_paint_device.h" +#include "kis_lazy_fill_tools.h" +#include "kis_gaussian_kernel.h" +#include "kis_painter.h" + +using namespace KisLazyFillTools; + +struct KisColorizeJob::Private +{ + KisPaintDeviceSP src; + KisPaintDeviceSP dst; + KisPaintDeviceSP filteredSource; + QRect boundingRect; + + QVector keyStrokes; +}; + +KisColorizeJob::KisColorizeJob(KisPaintDeviceSP src, + KisPaintDeviceSP dst, + KisPaintDeviceSP filteredSource, + const QRect &boundingRect) + : m_d(new Private) +{ + m_d->src = src; + m_d->dst = dst; + m_d->filteredSource = filteredSource; + m_d->boundingRect = boundingRect; +} + +KisColorizeJob::~KisColorizeJob() +{ +} + +void KisColorizeJob::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color) +{ + m_d->keyStrokes << KeyStroke(dev, color); +} + +void KisColorizeJob::run() +{ + const QRect &rect = m_d->boundingRect; + + // for b/w + noalpha layer + //KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(m_d->src); + + // for alpha-based layers + KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsAlpha(m_d->src); + + // optional filtering + // KisGaussianKernel::applyLoG(filteredMainDev, + // m_d->boundingRect, + // 1.0, + // QBitArray(), 0); + + normalizeAndInvertAlpha8Device(filteredMainDev, m_d->boundingRect); + + KisMultiwayCut cut(filteredMainDev, m_d->dst, m_d->boundingRect); + + Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { + cut.addKeyStroke(new KisPaintDevice(*stroke.dev), stroke.color); + } + + cut.run(); + + m_d->filteredSource->makeCloneFrom(filteredMainDev, m_d->boundingRect); + + emit sigFinished(); +} + +bool KisColorizeJob::overrides(const KisSpontaneousJob *_otherJob) +{ + const KisColorizeJob *otherJob = + dynamic_cast(_otherJob); + + return otherJob->m_d->src == m_d->src && + otherJob->m_d->dst == m_d->dst; +} + +int KisColorizeJob::levelOfDetail() const +{ + return 0; +} diff --git a/libs/image/lazybrush/kis_colorize_mask.h b/libs/image/lazybrush/kis_colorize_mask.h new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_colorize_mask.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2016 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 __KIS_COLORIZE_MASK_H +#define __KIS_COLORIZE_MASK_H + +#include + +#include "kis_types.h" +#include "kis_effect_mask.h" +#include "kritaimage_export.h" + +class KoColor; + + +class KRITAIMAGE_EXPORT KisColorizeMask : public KisEffectMask +{ + Q_OBJECT +public: + KisColorizeMask(); + ~KisColorizeMask(); + + KisColorizeMask(const KisColorizeMask& rhs); + + KisPaintDeviceSP paintDevice() const; + KisPaintDeviceSP original() const; + KisPaintDeviceSP projection() const; + + KisNodeSP clone() const { + return KisNodeSP(new KisColorizeMask(*this)); + } + + QIcon icon() const; + + void setImage(KisImageWSP image); + bool accept(KisNodeVisitor &v); + void accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter); + + QRect decorateRect(KisPaintDeviceSP &src, + KisPaintDeviceSP &dst, + const QRect & rc, + PositionToFilthy maskPos) const; + + void setCurrentColor(const KoColor &color); + void mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID); + void writeMergeData(KisPainter *painter, KisPaintDeviceSP src); + + QRect exactBounds() const; + QRect extent() const; + + bool needsUpdate() const; + void setNeedsUpdate(bool value); + + bool showColoring() const; + void setShowColoring(bool value); + + bool showKeyStrokes() const; + void setShowKeyStrokes(bool value); + + void setSectionModelProperties(const KisBaseNode::PropertyList &properties); + KisBaseNode::PropertyList sectionModelProperties() const; + + +private Q_SLOTS: + void slotUpdateRegenerateFilling(); + void slotRegenerationFinished(); + +private: + KisImageSP fetchImage() const; + +private: + struct Private; + const QScopedPointer m_d; +}; + +#endif /* __KIS_COLORIZE_MASK_H */ diff --git a/libs/image/lazybrush/kis_colorize_mask.cpp b/libs/image/lazybrush/kis_colorize_mask.cpp new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_colorize_mask.cpp @@ -0,0 +1,449 @@ +/* + * Copyright (c) 2016 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 "kis_colorize_mask.h" + +#include + +#include +#include "kis_pixel_selection.h" + +#include "kis_icon_utils.h" + +#include "kis_node_visitor.h" +#include "kis_processing_visitor.h" +#include "kis_painter.h" +#include "kis_fill_painter.h" +#include "kis_lazy_fill_tools.h" +#include "kis_cached_paint_device.h" +#include "kis_paint_device_debug_utils.h" +#include "kis_layer_properties_icons.h" +#include "kis_signal_compressor.h" + +#include "kis_colorize_job.h" +#include "kis_multiway_cut.h" +#include "kis_image.h" +#include "kis_layer.h" + + +using namespace KisLazyFillTools; + +struct KisColorizeMask::Private +{ + Private() + : showKeyStrokes(true), showColoring(true), needsUpdate(true), + updateCompressor(1000, KisSignalCompressor::POSTPONE) + { + } + + Private(const Private &rhs) + : showKeyStrokes(rhs.showKeyStrokes), + showColoring(rhs.showColoring), + cachedSelection(), + needsUpdate(false), + updateCompressor(1000, KisSignalCompressor::POSTPONE) + { + } + + QList keyStrokes; + KisPaintDeviceSP coloringProjection; + KisPaintDeviceSP fakePaintDevice; + KisPaintDeviceSP filteredSource; + + KoColor currentColor; + KisPaintDeviceSP currentKeyStrokeDevice; + + bool showKeyStrokes; + bool showColoring; + + KisCachedSelection cachedSelection; + KisCachedSelection cachedConversionSelection; + + bool needsUpdate; + + KisSignalCompressor updateCompressor; +}; + +KisColorizeMask::KisColorizeMask() + : m_d(new Private) +{ + // TODO: correct initialization of the layer's color space + m_d->fakePaintDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + + connect(&m_d->updateCompressor, + SIGNAL(timeout()), + SLOT(slotUpdateRegenerateFilling())); + + m_d->updateCompressor.moveToThread(qApp->thread()); +} + +KisColorizeMask::~KisColorizeMask() +{ +} + +KisColorizeMask::KisColorizeMask(const KisColorizeMask& rhs) + : KisEffectMask(rhs), + m_d(new Private(*rhs.m_d)) +{ + connect(&m_d->updateCompressor, + SIGNAL(timeout()), + SLOT(slotUpdateRegenerateFilling())); + + m_d->updateCompressor.moveToThread(qApp->thread()); +} + +bool KisColorizeMask::needsUpdate() const +{ + return m_d->needsUpdate; +} + +void KisColorizeMask::setNeedsUpdate(bool value) +{ + if (value != m_d->needsUpdate) { + m_d->needsUpdate = value; + baseNodeChangedCallback(); + + if (!value) { + m_d->updateCompressor.start(); + } + } +} + +void KisColorizeMask::slotUpdateRegenerateFilling() +{ + KisPaintDeviceSP src = parent()->original(); + KIS_ASSERT_RECOVER_RETURN(src); + + if (!m_d->coloringProjection) { + m_d->coloringProjection = new KisPaintDevice(src->colorSpace()); + m_d->filteredSource = new KisPaintDevice(src->colorSpace()); + } else { + // TODO: sync colorspaces + m_d->coloringProjection->clear(); + m_d->filteredSource->clear(); + } + + KisLayerSP parentLayer = dynamic_cast(parent().data()); + if (!parentLayer) return; + + KisImageSP image = parentLayer->image(); + if (image) { + KisColorizeJob *job = new KisColorizeJob(src, m_d->coloringProjection, m_d->filteredSource, image->bounds()); + Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { + job->addKeyStroke(stroke.dev, stroke.color); + } + + connect(job, SIGNAL(sigFinished()), SLOT(slotRegenerationFinished())); + image->addSpontaneousJob(job); + } +} + +void KisColorizeMask::slotRegenerationFinished() +{ + setNeedsUpdate(true); + setDirty(); +} + +KisBaseNode::PropertyList KisColorizeMask::sectionModelProperties() const +{ + KisBaseNode::PropertyList l = KisMask::sectionModelProperties(); + l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeNeedsUpdate, needsUpdate()); + l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowKeyStrokes, showKeyStrokes()); + l << KisLayerPropertiesIcons::getProperty(KisLayerPropertiesIcons::colorizeShowColoring, showColoring()); + + return l; +} + +void KisColorizeMask::setSectionModelProperties(const KisBaseNode::PropertyList &properties) +{ + KisMask::setSectionModelProperties(properties); + + Q_FOREACH (const KisBaseNode::Property &property, properties) { + if (property.id == KisLayerPropertiesIcons::colorizeNeedsUpdate.id()) { + if (m_d->needsUpdate != property.state.toBool()) { + setNeedsUpdate(property.state.toBool()); + } + } + if (property.id == KisLayerPropertiesIcons::colorizeShowKeyStrokes.id()) { + if (m_d->showKeyStrokes != property.state.toBool()) { + setShowKeyStrokes(property.state.toBool()); + } + } + if (property.id == KisLayerPropertiesIcons::colorizeShowColoring.id()) { + if (m_d->showColoring != property.state.toBool()) { + setShowColoring(property.state.toBool()); + } + } + } +} + +KisPaintDeviceSP KisColorizeMask::paintDevice() const +{ + return m_d->fakePaintDevice; +} + +KisPaintDeviceSP KisColorizeMask::original() const +{ + return 0; +} + +KisPaintDeviceSP KisColorizeMask::projection() const +{ + return 0; +} + +QIcon KisColorizeMask::icon() const +{ + return KisIconUtils::loadIcon("filterMask"); +} + + +bool KisColorizeMask::accept(KisNodeVisitor &v) +{ + return v.visit(this); +} + +void KisColorizeMask::accept(KisProcessingVisitor &visitor, KisUndoAdapter *undoAdapter) +{ + return visitor.visit(this, undoAdapter); +} + +QRect KisColorizeMask::decorateRect(KisPaintDeviceSP &src, + KisPaintDeviceSP &dst, + const QRect &rect, + PositionToFilthy maskPos) const +{ + Q_UNUSED(maskPos); + + KIS_ASSERT(dst != src); + + // Draw the filling and the original layer + { + KisPainter gc(dst); + gc.setOpacity(128); + if (m_d->showColoring && m_d->coloringProjection) { + gc.bitBlt(rect.topLeft(), m_d->coloringProjection, rect); + } + + if (m_d->showKeyStrokes && + m_d->filteredSource && + !m_d->filteredSource->extent().isEmpty()) { + + // TODO: the filtered source should be converted back into alpha! + gc.setOpacity(128); + gc.bitBlt(rect.topLeft(), m_d->filteredSource, rect); + } else { + gc.setOpacity(255); + gc.bitBlt(rect.topLeft(), src, rect); + } + } + + // Draw the key strokes + if (m_d->showKeyStrokes) { + lockTemporaryTarget(); + + KisSelectionSP selection = m_d->cachedSelection.getSelection(); + KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); + KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); + + KisFillPainter gc(dst); + Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { + selection->pixelSelection()->makeCloneFromRough(stroke.dev, rect); + gc.setSelection(selection); + + if (stroke.color == m_d->currentColor) { + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + + if (temporaryTarget) { + tempSelection->copyAlphaFrom(temporaryTarget, rect); + + KisPainter selectionPainter(selection->pixelSelection()); + setupTemporaryPainter(&selectionPainter); + selectionPainter.bitBlt(rect.topLeft(), tempSelection, rect); + } + } + + gc.fillSelection(rect, stroke.color); + } + + m_d->cachedSelection.putSelection(selection); + m_d->cachedSelection.putSelection(conversionSelection); + + unlockTemporaryTarget(); + } + + return rect; +} + +QRect KisColorizeMask::extent() const +{ + QRect rc; + + if (m_d->showColoring && m_d->coloringProjection) { + rc |= m_d->coloringProjection->extent(); + } + + if (m_d->showKeyStrokes) { + Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { + rc |= stroke.dev->extent(); + } + + lockTemporaryTarget(); + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + if (temporaryTarget) { + rc |= temporaryTarget->extent(); + } + unlockTemporaryTarget(); + } + + return rc; +} + +QRect KisColorizeMask::exactBounds() const +{ + QRect rc; + + if (m_d->showColoring && m_d->coloringProjection) { + rc |= m_d->coloringProjection->exactBounds(); + } + + if (m_d->showKeyStrokes) { + Q_FOREACH (const KeyStroke &stroke, m_d->keyStrokes) { + rc |= stroke.dev->exactBounds(); + } + + lockTemporaryTarget(); + KisPaintDeviceSP temporaryTarget = this->temporaryTarget(); + if (temporaryTarget) { + rc |= temporaryTarget->exactBounds(); + } + unlockTemporaryTarget(); + } + + return rc; + +} + +KisImageSP KisColorizeMask::fetchImage() const +{ + KisLayerSP parentLayer = dynamic_cast(parent().data()); + if (!parentLayer) return 0; + + return parentLayer->image(); +} + +void KisColorizeMask::setImage(KisImageWSP image) +{ + auto it = m_d->keyStrokes.begin(); + for(; it != m_d->keyStrokes.end(); ++it) { + it->dev->setDefaultBounds(new KisDefaultBounds(image)); + } +} + +void KisColorizeMask::setCurrentColor(const KoColor &color) +{ + lockTemporaryTargetForWrite(); + + setNeedsUpdate(true); + + QList::const_iterator it = + std::find_if(m_d->keyStrokes.constBegin(), + m_d->keyStrokes.constEnd(), + [color] (const KeyStroke &s) { + return s.color == color; + }); + + KisPaintDeviceSP activeDevice; + + if (it == m_d->keyStrokes.constEnd()) { + activeDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + activeDevice->setParentNode(this); + activeDevice->setDefaultBounds(new KisDefaultBounds(fetchImage())); + m_d->keyStrokes.append(KeyStroke(activeDevice, color)); + } else { + activeDevice = it->dev; + } + + m_d->currentColor = color; + m_d->currentKeyStrokeDevice = activeDevice; + m_d->fakePaintDevice->convertTo(colorSpace()); + + unlockTemporaryTarget(); +} + +void KisColorizeMask::mergeToLayer(KisNodeSP layer, KisPostExecutionUndoAdapter *undoAdapter, const KUndo2MagicString &transactionText,int timedID) +{ + Q_UNUSED(layer); + mergeToLayerImpl(m_d->currentKeyStrokeDevice, undoAdapter, transactionText, timedID); +} + +void KisColorizeMask::writeMergeData(KisPainter *painter, KisPaintDeviceSP src) +{ + KisSelectionSP conversionSelection = m_d->cachedConversionSelection.getSelection(); + KisPixelSelectionSP tempSelection = conversionSelection->pixelSelection(); + + Q_FOREACH (const QRect &rc, src->region().rects()) { + tempSelection->copyAlphaFrom(src, rc); + painter->bitBlt(rc.topLeft(), tempSelection, rc); + } + + m_d->cachedSelection.putSelection(conversionSelection); + + m_d->currentKeyStrokeDevice = 0; + m_d->currentColor = KoColor(); + m_d->fakePaintDevice->clear(); +} + +bool KisColorizeMask::showColoring() const +{ + return m_d->showColoring; +} + +void KisColorizeMask::setShowColoring(bool value) +{ + QRect savedExtent; + if (m_d->showColoring && !value) { + savedExtent = extent(); + } + + m_d->showColoring = value; + + if (!savedExtent.isEmpty()) { + setDirty(savedExtent); + } +} + +bool KisColorizeMask::showKeyStrokes() const +{ + return m_d->showKeyStrokes; +} + +void KisColorizeMask::setShowKeyStrokes(bool value) +{ + QRect savedExtent; + if (m_d->showKeyStrokes && !value) { + savedExtent = extent(); + } + + m_d->showKeyStrokes = value; + + if (!savedExtent.isEmpty()) { + setDirty(savedExtent); + } +} + diff --git a/libs/image/lazybrush/kis_lazy_fill_capacity_map.h b/libs/image/lazybrush/kis_lazy_fill_capacity_map.h new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_lazy_fill_capacity_map.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016 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 __KIS_LAZY_FILL_CAPACITY_MAP_H +#define __KIS_LAZY_FILL_CAPACITY_MAP_H + +#include + +#include "kis_lazy_fill_graph.h" +#include "kis_paint_device.h" +#include "kis_types.h" +#include "kis_painter.h" +#include "kis_random_accessor_ng.h" +#include "kis_global.h" + + +class KisLazyFillCapacityMap +{ + typedef KisLazyFillCapacityMap type; + typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; + typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; + +public: + typedef EdgeDescriptor key_type; + typedef float value_type; + typedef const float& reference; + typedef boost::readable_property_map_tag category; + + KisLazyFillCapacityMap(KisPaintDeviceSP mainImage, + KisPaintDeviceSP aLabelImage, + KisPaintDeviceSP bLabelImage, + KisPaintDeviceSP maskImage, + const QRect &boundingRect) + : m_mainImage(mainImage), + m_aLabelImage(aLabelImage), + m_bLabelImage(bLabelImage), + m_maskImage(maskImage), + m_mainRect(boundingRect), + m_aLabelRect(m_aLabelImage->exactBounds() & boundingRect), + m_bLabelRect(m_bLabelImage->exactBounds() & boundingRect), + m_colorSpace(mainImage->colorSpace()), + m_pixelSize(m_colorSpace->pixelSize()), + m_graph(m_mainRect, + m_aLabelImage->regionExact() & boundingRect, + m_bLabelImage->regionExact() & boundingRect) + { + KIS_ASSERT_RECOVER_NOOP(m_mainImage->colorSpace()->pixelSize() == 1); + KIS_ASSERT_RECOVER_NOOP(m_aLabelImage->colorSpace()->pixelSize() == 1); + KIS_ASSERT_RECOVER_NOOP(m_bLabelImage->colorSpace()->pixelSize() == 1); + + + const QPoint pt = m_mainRect.topLeft(); + + m_mainAccessor = m_mainImage->createRandomConstAccessorNG(pt.x(), pt.y()); + m_aAccessor = m_aLabelImage->createRandomConstAccessorNG(pt.x(), pt.y()); + m_bAccessor = m_bLabelImage->createRandomConstAccessorNG(pt.x(), pt.y()); + m_maskAccessor = m_maskImage->createRandomConstAccessorNG(pt.x(), pt.y()); + m_srcPixelBuf.resize(m_pixelSize); + } + + int maxCapacity() const { + const int k = 2 * (m_mainRect.width() + m_mainRect.height()); + return k + 1; + } + + friend value_type get(type &map, + const key_type &key) + { + VertexDescriptor src = source(key, map.m_graph); + VertexDescriptor dst = target(key, map.m_graph); + + if (src.type == VertexDescriptor::NORMAL) { + map.m_maskAccessor->moveTo(src.x, src.y); + if (*map.m_maskAccessor->rawDataConst()) { + return 0; + } + } + + if (dst.type == VertexDescriptor::NORMAL) { + map.m_maskAccessor->moveTo(dst.x, dst.y); + if (*map.m_maskAccessor->rawDataConst()) { + return 0; + } + } + + bool srcLabelA = src.type == VertexDescriptor::LABEL_A; + bool srcLabelB = src.type == VertexDescriptor::LABEL_B; + bool dstLabelA = dst.type == VertexDescriptor::LABEL_A; + bool dstLabelB = dst.type == VertexDescriptor::LABEL_B; + + if (srcLabelA || srcLabelB) { + std::swap(src, dst); + std::swap(srcLabelA, dstLabelA); + std::swap(srcLabelB, dstLabelB); + } + + Q_ASSERT(!srcLabelA && !srcLabelB); + + + // TODO: precalculate! + const int k = 2 * (map.m_mainRect.width() + map.m_mainRect.height()); + + float value = 0.0; + + if (dstLabelA) { + map.m_aAccessor->moveTo(src.x, src.y); + const int i0 = *((quint8*)map.m_aAccessor->rawDataConst()); + value = i0 / 255.0 * k; + + } else if (dstLabelB) { + map.m_bAccessor->moveTo(src.x, src.y); + const int i0 = *((quint8*)map.m_bAccessor->rawDataConst()); + value = i0 / 255.0 * k; + + } else { + map.m_mainAccessor->moveTo(src.x, src.y); + memcpy(map.m_srcPixelBuf.data(), map.m_mainAccessor->rawDataConst(), map.m_pixelSize); + map.m_mainAccessor->moveTo(dst.x, dst.y); + + //const quint8 diff = map.m_colorSpace->differenceA((quint8*)map.m_srcPixelBuf.data(), map.m_mainAccessor->rawDataConst()); + //const quint8 i0 = map.m_colorSpace->intensity8((quint8*)map.m_srcPixelBuf.data()); + //const quint8 i1 = map.m_colorSpace->intensity8(map.m_mainAccessor->rawDataConst()); + + const quint8 i0 = *((quint8*)map.m_srcPixelBuf.data()); + const quint8 i1 = *map.m_mainAccessor->rawDataConst(); + + const quint8 diff = qAbs(i1 - i0); + + const qreal diffPenalty = qBound(0.0, qreal(diff) / 10.0, 1.0); + const qreal intensityPenalty = 1.0 - i1 / 255.0; + + const qreal totalPenalty = qMax(0.0 * diffPenalty, intensityPenalty); + + value = 1.0 + k * (1.0 - pow2(totalPenalty)); + } + + return value; + } + + KisLazyFillGraph& graph() { + return m_graph; + } + +private: + KisPaintDeviceSP m_mainImage; + KisPaintDeviceSP m_aLabelImage; + KisPaintDeviceSP m_bLabelImage; + KisPaintDeviceSP m_maskImage; + + QRect m_mainRect; + QRect m_aLabelRect; + QRect m_bLabelRect; + + const KoColorSpace *m_colorSpace; + int m_pixelSize; + KisRandomConstAccessorSP m_mainAccessor; + KisRandomConstAccessorSP m_aAccessor; + KisRandomConstAccessorSP m_bAccessor; + KisRandomConstAccessorSP m_maskAccessor; + QByteArray m_srcPixelBuf; + + KisLazyFillGraph m_graph; +}; + +#endif /* __KIS_LAZY_FILL_CAPACITY_MAP_H */ diff --git a/libs/image/lazybrush/kis_lazy_fill_graph.h b/libs/image/lazybrush/kis_lazy_fill_graph.h new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_lazy_fill_graph.h @@ -0,0 +1,1035 @@ +/* + * Copyright (c) 2016 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 __KIS_LAZY_FILL_GRAPH_H +#define __KIS_LAZY_FILL_GRAPH_H + +#include +#include +#include + +#include +#include +#include +#include + +#include + + +using namespace boost; + +/* +BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); //read flow-values from vertices +*/ + +class KisLazyFillGraph; + +template +struct MyScribbleLabelMap { + typedef typename Graph::vertex_descriptor key_type; + typedef float value_type; + typedef const float& reference; + typedef readable_property_map_tag category; + + friend value_type get(const MyScribbleLabelMap &map, + const key_type &key) { + return 0; + } + + friend QRegion region(const MyScribbleLabelMap &map) { + return QRegion(); + } + + friend QRect boundingRect(const MyScribbleLabelMap &map) { + return QRect(10,10,100,100); + } +}; + +//=================== +// Index Property Map +//=================== + +template +struct lazy_fill_graph_index_map { +public: + typedef Index value_type; + typedef Index reference_type; + typedef reference_type reference; + typedef Descriptor key_type; + typedef readable_property_map_tag category; + + lazy_fill_graph_index_map() { } + + lazy_fill_graph_index_map(const Graph& graph) : + m_graph(&graph) { } + + value_type operator[](key_type key) const { + value_type index = m_graph->index_of(key); + KIS_ASSERT(index >= 0); + return index; + } + + friend inline Index + get(const lazy_fill_graph_index_map& index_map, + const typename lazy_fill_graph_index_map::key_type& key) + { + return (index_map[key]); + } + +protected: + const Graph* m_graph; +}; + +//========================== +// Reverse Edge Property Map +//========================== + +template +struct lazy_fill_graph_reverse_edge_map { +public: + typedef Descriptor value_type; + typedef Descriptor reference_type; + typedef reference_type reference; + typedef Descriptor key_type; + typedef readable_property_map_tag category; + + lazy_fill_graph_reverse_edge_map() { } + + value_type operator[](const key_type& key) const { + return (value_type(key.second, key.first)); + } + + friend inline Descriptor + get(const lazy_fill_graph_reverse_edge_map& rev_map, + const typename lazy_fill_graph_reverse_edge_map::key_type& key) + { + return (rev_map[key]); + } +}; + +//================= +// Function Objects +//================= + +namespace kis_detail { + + // vertex_at + template + struct lazy_fill_graph_vertex_at { + + typedef typename graph_traits::vertex_descriptor result_type; + + lazy_fill_graph_vertex_at() : m_graph(0) {} + + lazy_fill_graph_vertex_at(const Graph* graph) : + m_graph(graph) { } + + result_type + operator() + (typename graph_traits::vertices_size_type vertex_index) const { + return (vertex(vertex_index, *m_graph)); + } + + private: + const Graph* m_graph; + }; + + // out_edge_at + template + struct lazy_fill_graph_out_edge_at { + + private: + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + + public: + typedef typename graph_traits::edge_descriptor result_type; + + lazy_fill_graph_out_edge_at() : m_vertex(), m_graph(0) {} + + lazy_fill_graph_out_edge_at(vertex_descriptor source_vertex, + const Graph* graph) : + m_vertex(source_vertex), + m_graph(graph) { } + + result_type + operator() + (typename graph_traits::degree_size_type out_edge_index) const { + return (out_edge_at(m_vertex, out_edge_index, *m_graph)); + } + + private: + vertex_descriptor m_vertex; + const Graph* m_graph; + }; + + // in_edge_at + template + struct lazy_fill_graph_in_edge_at { + + private: + typedef typename graph_traits::vertex_descriptor vertex_descriptor; + + public: + typedef typename graph_traits::edge_descriptor result_type; + + lazy_fill_graph_in_edge_at() : m_vertex(), m_graph(0) {} + + lazy_fill_graph_in_edge_at(vertex_descriptor target_vertex, + const Graph* graph) : + m_vertex(target_vertex), + m_graph(graph) { } + + result_type + operator() + (typename graph_traits::degree_size_type in_edge_index) const { + return (in_edge_at(m_vertex, in_edge_index, *m_graph)); + } + + private: + vertex_descriptor m_vertex; + const Graph* m_graph; + }; + + // edge_at + template + struct lazy_fill_graph_edge_at { + + typedef typename graph_traits::edge_descriptor result_type; + + lazy_fill_graph_edge_at() : m_graph(0) {} + + lazy_fill_graph_edge_at(const Graph* graph) : + m_graph(graph) { } + + result_type + operator() + (typename graph_traits::edges_size_type edge_index) const { + return (edge_at(edge_index, *m_graph)); + } + + private: + const Graph* m_graph; + }; + + // adjacent_vertex_at + template + struct lazy_fill_graph_adjacent_vertex_at { + + public: + typedef typename graph_traits::vertex_descriptor result_type; + + lazy_fill_graph_adjacent_vertex_at(result_type source_vertex, + const Graph* graph) : + m_vertex(source_vertex), + m_graph(graph) { } + + result_type + operator() + (typename graph_traits::degree_size_type adjacent_index) const { + return (target(out_edge_at(m_vertex, adjacent_index, *m_graph), *m_graph)); + } + + private: + result_type m_vertex; + const Graph* m_graph; + }; + +} // namespace kis_detail + + +class KisLazyFillGraph +{ +public: + typedef KisLazyFillGraph type; + + + typedef long VertexIndex; + typedef long EdgeIndex; + + // sizes + typedef VertexIndex vertices_size_type; + typedef EdgeIndex edges_size_type; + typedef EdgeIndex degree_size_type; + + struct VertexDescriptor : public equality_comparable + { + enum VertexType { + NORMAL = 0, + LABEL_A, + LABEL_B + }; + + + vertices_size_type x; + vertices_size_type y; + VertexType type; + + VertexDescriptor(vertices_size_type _x, vertices_size_type _y, VertexType _type = NORMAL) + : x(_x), y(_y), type(_type) {} + + VertexDescriptor(VertexType _type) + : x(0), y(0), type(_type) {} + + VertexDescriptor() + : x(0), y(0), type(NORMAL) {} + + bool operator ==(const VertexDescriptor &rhs) const { + return rhs.x == x && rhs.y == y && rhs.type == type; + } + }; + + // descriptors + typedef VertexDescriptor vertex_descriptor; + typedef std::pair edge_descriptor; + + friend QDebug operator<<(QDebug dbg, const KisLazyFillGraph::edge_descriptor &e); + friend QDebug operator<<(QDebug dbg, const KisLazyFillGraph::vertex_descriptor &v); + + // vertex_iterator + typedef counting_iterator vertex_index_iterator; + typedef kis_detail::lazy_fill_graph_vertex_at vertex_function; + typedef transform_iterator vertex_iterator; + + // edge_iterator + typedef counting_iterator edge_index_iterator; + typedef kis_detail::lazy_fill_graph_edge_at edge_function; + typedef transform_iterator edge_iterator; + + // out_edge_iterator + typedef counting_iterator degree_iterator; + typedef kis_detail::lazy_fill_graph_out_edge_at out_edge_function; + typedef transform_iterator out_edge_iterator; + + // adjacency_iterator + typedef kis_detail::lazy_fill_graph_adjacent_vertex_at adjacent_vertex_function; + typedef transform_iterator adjacency_iterator; + + // categories + typedef directed_tag directed_category; + typedef disallow_parallel_edge_tag edge_parallel_category; + struct traversal_category : virtual public incidence_graph_tag, + virtual public adjacency_graph_tag, + virtual public vertex_list_graph_tag, + virtual public edge_list_graph_tag, + virtual public adjacency_matrix_tag { }; + + static inline vertex_descriptor null_vertex() + { + vertex_descriptor maxed_out_vertex( + std::numeric_limits::max(), + std::numeric_limits::max(), + vertex_descriptor::NORMAL); + + return (maxed_out_vertex); + } + + KisLazyFillGraph() {} + + KisLazyFillGraph(const QRect &mainRect, + const QRegion &aLabelRegion, + const QRegion &bLabelRegion) + : m_x(mainRect.x()), + m_y(mainRect.y()), + m_width(mainRect.width()), + m_height(mainRect.height()) + { + m_mainArea = mainRect; + m_aLabelArea = aLabelRegion.boundingRect(); + m_bLabelArea = bLabelRegion.boundingRect(); + m_aLabelRects = aLabelRegion.rects(); + m_bLabelRects = bLabelRegion.rects(); + + KIS_ASSERT(m_mainArea.contains(m_aLabelArea)); + KIS_ASSERT(m_mainArea.contains(m_bLabelArea)); + + m_numVertices = m_width * m_height + 2; + + m_edgeBins << EdgeIndexBin(0, m_mainArea.adjusted(0, 0, -1, 0), HORIZONTAL); + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, -1, 0), HORIZONTAL_REVERSED); + + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, 0, -1), VERTICAL); + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), m_mainArea.adjusted(0, 0, 0, -1), VERTICAL_REVERSED); + + Q_FOREACH (const QRect &rc, m_aLabelRects) { + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_A); + } + + // out_edge_at relies on the sequential layout of reversed edges of one type + m_aReversedEdgesStart = m_edgeBins.last().last() + 1; + + Q_FOREACH (const QRect &rc, m_aLabelRects) { + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_A_REVERSED); + } + + m_numAEdges = m_edgeBins.last().last() + 1 - m_aReversedEdgesStart; + + Q_FOREACH (const QRect &rc, m_bLabelRects) { + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_B); + } + + // out_edge_at relies on the sequential layout of reversed edges of one type + m_bReversedEdgesStart = m_edgeBins.last().last() + 1; + + Q_FOREACH (const QRect &rc, m_bLabelRects) { + m_edgeBins << EdgeIndexBin(m_edgeBins.last(), rc, LABEL_B_REVERSED); + } + + m_numBEdges = m_edgeBins.last().last() + 1 - m_bReversedEdgesStart; + + m_numEdges = m_edgeBins.last().last() + 1; + } + + ~KisLazyFillGraph() { + + } + + QSize size() const { return QSize(m_width, m_height); } + QRect rect() const { return QRect(m_x, m_y, m_width, m_height); } + + + vertices_size_type m_x; + vertices_size_type m_y; + + vertices_size_type m_width; + vertices_size_type m_height; + + vertices_size_type m_numVertices; + vertices_size_type m_numEdges; + + vertices_size_type m_aReversedEdgesStart; + vertices_size_type m_bReversedEdgesStart; + vertices_size_type m_numAEdges; + vertices_size_type m_numBEdges; + + enum EdgeIndexBinId { + HORIZONTAL, + HORIZONTAL_REVERSED, + VERTICAL, + VERTICAL_REVERSED, + LABEL_A, + LABEL_A_REVERSED, + LABEL_B, + LABEL_B_REVERSED, + }; + + struct EdgeIndexBin { + EdgeIndexBin() + : start(0), stride(0), size(0) {} + + EdgeIndexBin(edges_size_type _start, + const QRect &_rect, + EdgeIndexBinId _binId) + : start(_start), + stride(_rect.width()), + size(_rect.width() * _rect.height()), + xOffset(_rect.x()), + yOffset(_rect.y()), + binId(_binId), + isReversed(int(_binId) & 0x1), + rect(_rect) {} + + EdgeIndexBin(const EdgeIndexBin &putAfter, + const QRect &_rect, + EdgeIndexBinId _binId) + : start(putAfter.last() + 1), + stride(_rect.width()), + size(_rect.width() * _rect.height()), + xOffset(_rect.x()), + yOffset(_rect.y()), + binId(_binId), + isReversed(int(_binId) & 0x1), + rect(_rect) {} + + edges_size_type last() const { + return start + size - 1; + } + + bool contains(edges_size_type index) const { + index -= start; + return index >= 0 && index < size; + } + + bool contains(const edge_descriptor &edge) const { + return indexOf(edge) >= 0; + } + + edges_size_type indexOf(const edge_descriptor &edge) const { + vertex_descriptor src_vertex = source(edge, *this); + vertex_descriptor dst_vertex = target(edge, *this); + + const bool srcColoredA = src_vertex.type == vertex_descriptor::LABEL_A; + const bool dstColoredA = dst_vertex.type == vertex_descriptor::LABEL_A; + const bool srcColoredB = src_vertex.type == vertex_descriptor::LABEL_B; + const bool dstColoredB = dst_vertex.type == vertex_descriptor::LABEL_B; + + if (srcColoredA || dstColoredA) { + const bool edgeReversed = srcColoredA; + + if (isReversed != edgeReversed || + (binId != LABEL_A && binId != LABEL_A_REVERSED) || + (srcColoredA && (dst_vertex.type != vertex_descriptor::NORMAL)) || + (dstColoredA && (src_vertex.type != vertex_descriptor::NORMAL))) { + + return -1; + } + } else if (srcColoredB || dstColoredB) { + const bool edgeReversed = srcColoredB; + + if (isReversed != edgeReversed || + (binId != LABEL_B && binId != LABEL_B_REVERSED) || + (srcColoredB && (dst_vertex.type != vertex_descriptor::NORMAL)) || + (dstColoredB && (src_vertex.type != vertex_descriptor::NORMAL))) { + + return -1; + } + } else { + const vertices_size_type xDiff = dst_vertex.x - src_vertex.x; + const vertices_size_type yDiff = dst_vertex.y - src_vertex.y; + const vertices_size_type xAbsDiff = qAbs(xDiff); + const vertices_size_type yAbsDiff = qAbs(yDiff); + const bool edgeReversed = xDiff < 0 || yDiff < 0; + + if (isReversed != edgeReversed || + (xAbsDiff && binId != HORIZONTAL && binId != HORIZONTAL_REVERSED) || + (yAbsDiff && binId != VERTICAL && binId != VERTICAL_REVERSED) || + xAbsDiff > 1 || + yAbsDiff > 1 || + xAbsDiff == yAbsDiff) { + + return -1; + } + } + + if (isReversed) { + std::swap(src_vertex, dst_vertex); + } + + if (!rect.contains(QPoint(src_vertex.x, src_vertex.y))) { + return -1; + } + + edges_size_type internalIndex = + (src_vertex.x - xOffset) + + (src_vertex.y - yOffset) * stride; + + KIS_ASSERT_RECOVER(internalIndex >= 0 && internalIndex < size) { + return -1; + } + + return internalIndex + start; + } + + + edge_descriptor edgeAt(edges_size_type index) const { + edges_size_type localOffset = index - start; + + if (localOffset < 0 || localOffset >= size) { + return edge_descriptor(); + } + + const edges_size_type x = localOffset % stride + xOffset; + const edges_size_type y = localOffset / stride + yOffset; + + vertex_descriptor src_vertex(x, y, vertex_descriptor::NORMAL); + vertex_descriptor dst_vertex; + + switch (binId) { + case HORIZONTAL: + case HORIZONTAL_REVERSED: + dst_vertex.x = x + 1; + dst_vertex.y = y; + dst_vertex.type = vertex_descriptor::NORMAL; + break; + case VERTICAL: + case VERTICAL_REVERSED: + dst_vertex.x = x; + dst_vertex.y = y + 1; + dst_vertex.type = vertex_descriptor::NORMAL; + break; + case LABEL_A: + case LABEL_A_REVERSED: + dst_vertex.type = vertex_descriptor::LABEL_A; + break; + case LABEL_B: + case LABEL_B_REVERSED: + dst_vertex.type = vertex_descriptor::LABEL_B; + break; + } + + if (isReversed) { + std::swap(src_vertex, dst_vertex); + } + + return std::make_pair(src_vertex, dst_vertex); + } + + edges_size_type start; + edges_size_type stride; + edges_size_type size; + edges_size_type xOffset; + edges_size_type yOffset; + EdgeIndexBinId binId; + bool isReversed; + QRect rect; + }; + + QVector m_edgeBins; + + QRect m_aLabelArea; + QRect m_bLabelArea; + QRect m_mainArea; + + QVector m_aLabelRects; + QVector m_bLabelRects; + +protected: +public: + + // Returns the number of vertices in the graph + inline vertices_size_type num_vertices() const { + return (m_numVertices); + } + + // Returns the number of edges in the graph + inline edges_size_type num_edges() const { + return (m_numEdges); + } + + // Returns the index of [vertex] (See also vertex_at) + vertices_size_type index_of(vertex_descriptor vertex) const { + vertices_size_type vertex_index = -1; + + switch (vertex.type) { + case vertex_descriptor::NORMAL: + vertex_index = vertex.x - m_x + (vertex.y - m_y) * m_width; + break; + case vertex_descriptor::LABEL_A: + vertex_index = m_numVertices - 2; + break; + case vertex_descriptor::LABEL_B: + vertex_index = m_numVertices - 1; + break; + } + + return vertex_index; + } + + // Returns the vertex whose index is [vertex_index] (See also + // index_of(vertex_descriptor)) + vertex_descriptor vertex_at (vertices_size_type vertex_index) const { + vertex_descriptor vertex; + + if (vertex_index == m_numVertices - 2) { + vertex.type = vertex_descriptor::LABEL_A; + } else if (vertex_index == m_numVertices - 1) { + vertex.type = vertex_descriptor::LABEL_B; + } else if (vertex_index >= 0) { + vertex.x = vertex_index % m_width + m_x; + vertex.y = vertex_index / m_width + m_y; + vertex.type = vertex_descriptor::NORMAL; + } + + return vertex; + } + + // Returns the edge whose index is [edge_index] (See also + // index_of(edge_descriptor)). NOTE: The index mapping is + // dependent upon dimension wrapping. + edge_descriptor edge_at(edges_size_type edge_index) const { + + int binIndex = 0; + + while (binIndex < m_edgeBins.size() && + !m_edgeBins[binIndex].contains(edge_index)) { + + binIndex++; + } + + if (binIndex >= m_edgeBins.size()) { + return edge_descriptor(); + } + + return m_edgeBins[binIndex].edgeAt(edge_index); + } + + // Returns the index for [edge] (See also edge_at) + edges_size_type index_of(edge_descriptor edge) const { + edges_size_type index = -1; + + Q_FOREACH (const EdgeIndexBin &bin, m_edgeBins) { + index = bin.indexOf(edge); + if (index >= 0) break; + } + + + //ENTER_FUNCTION() << ppVar(edge) << ppVar(index); + //KIS_ASSERT(index >= 0); + + return index; + } + +private: + static vertices_size_type numVacantEdges(const vertex_descriptor &vertex, const QRect &rc) { + vertices_size_type vacantEdges = 4; + + if (vertex.x == rc.x()) { + vacantEdges--; + } + + if (vertex.y == rc.y()) { + vacantEdges--; + } + + if (vertex.x == rc.right()) { + vacantEdges--; + } + + if (vertex.y == rc.bottom()) { + vacantEdges--; + } + + return vacantEdges; + } + + static inline bool findInRects(const QVector &rects, const QPoint &pt) { + bool result = false; + Q_FOREACH (const QRect &rc, rects) { + if (rc.contains(pt)) { + result = true; + break; + } + } + return result; + } +public: + // Returns the number of out-edges for [vertex] + degree_size_type out_degree(vertex_descriptor vertex) const { + degree_size_type out_edge_count = 0; + if (index_of(vertex) < 0) return out_edge_count; + + switch (vertex.type) { + case vertex_descriptor::NORMAL: { + out_edge_count = numVacantEdges(vertex, m_mainArea); + + const QPoint pt = QPoint(vertex.x, vertex.y); + + if (m_aLabelArea.contains(pt) && findInRects(m_aLabelRects, pt)) { + out_edge_count++; + } + + if (m_bLabelArea.contains(pt) && findInRects(m_bLabelRects, pt)) { + out_edge_count++; + } + + break; + } + case vertex_descriptor::LABEL_A: + out_edge_count = m_numAEdges; + break; + case vertex_descriptor::LABEL_B: + out_edge_count = m_numBEdges; + break; + } + + return (out_edge_count); + } + + // Returns an out-edge for [vertex] by index. Indices are in the + // range [0, out_degree(vertex)). + edge_descriptor out_edge_at (vertex_descriptor vertex, + degree_size_type out_edge_index) const { + + const QPoint pt = QPoint(vertex.x, vertex.y); + vertex_descriptor dst_vertex = vertex; + + switch (vertex.type) { + case vertex_descriptor::NORMAL: + if (vertex.x > m_mainArea.x() && !out_edge_index--) { + dst_vertex.x--; + } else if (vertex.y > m_mainArea.y() && !out_edge_index--) { + dst_vertex.y--; + } else if (vertex.x < m_mainArea.right() && !out_edge_index--) { + dst_vertex.x++; + } else if (vertex.y < m_mainArea.bottom() && !out_edge_index--) { + dst_vertex.y++; + } else if (m_aLabelArea.contains(pt) && findInRects(m_aLabelRects, pt) && !out_edge_index--) { + dst_vertex = vertex_descriptor(0, 0, vertex_descriptor::LABEL_A); + } else if (m_bLabelArea.contains(pt) && findInRects(m_bLabelRects, pt) && !out_edge_index--) { + dst_vertex = vertex_descriptor(0, 0, vertex_descriptor::LABEL_B); + } else { + qDebug() << ppVar(vertex) << ppVar(out_edge_index) << ppVar(out_degree(vertex)); + qFatal("Wrong edge sub-index"); + } + break; + case vertex_descriptor::LABEL_A: { + edge_descriptor edge = edge_at(m_aReversedEdgesStart + out_edge_index); + dst_vertex = edge.second; + break; + } + case vertex_descriptor::LABEL_B: { + edge_descriptor edge = edge_at(m_bReversedEdgesStart + out_edge_index); + dst_vertex = edge.second; + break; + } + } + + return std::make_pair(vertex, dst_vertex); + } + +public: + + //================ + // VertexListGraph + //================ + + friend inline std::pair + vertices(const type& graph) { + typedef typename type::vertex_iterator vertex_iterator; + typedef typename type::vertex_function vertex_function; + typedef typename type::vertex_index_iterator vertex_index_iterator; + + return (std::make_pair + (vertex_iterator(vertex_index_iterator(0), + vertex_function(&graph)), + vertex_iterator(vertex_index_iterator(graph.num_vertices()), + vertex_function(&graph)))); + } + + friend inline typename type::vertices_size_type + num_vertices(const type& graph) { + return (graph.num_vertices()); + } + + friend inline typename type::vertex_descriptor + vertex(typename type::vertices_size_type vertex_index, + const type& graph) { + + return (graph.vertex_at(vertex_index)); + } + + //=============== + // IncidenceGraph + //=============== + + friend inline std::pair + out_edges(typename type::vertex_descriptor vertex, + const type& graph) { + typedef typename type::degree_iterator degree_iterator; + typedef typename type::out_edge_function out_edge_function; + typedef typename type::out_edge_iterator out_edge_iterator; + + return (std::make_pair + (out_edge_iterator(degree_iterator(0), + out_edge_function(vertex, &graph)), + out_edge_iterator(degree_iterator(graph.out_degree(vertex)), + out_edge_function(vertex, &graph)))); + } + + friend inline typename type::degree_size_type + out_degree + (typename type::vertex_descriptor vertex, + const type& graph) { + return (graph.out_degree(vertex)); + } + + friend inline typename type::edge_descriptor + out_edge_at(typename type::vertex_descriptor vertex, + typename type::degree_size_type out_edge_index, + const type& graph) { + return (graph.out_edge_at(vertex, out_edge_index)); + } + + //=============== + // AdjacencyGraph + //=============== + + friend typename std::pair + adjacent_vertices (typename type::vertex_descriptor vertex, + const type& graph) { + typedef typename type::degree_iterator degree_iterator; + typedef typename type::adjacent_vertex_function adjacent_vertex_function; + typedef typename type::adjacency_iterator adjacency_iterator; + + return (std::make_pair + (adjacency_iterator(degree_iterator(0), + adjacent_vertex_function(vertex, &graph)), + adjacency_iterator(degree_iterator(graph.out_degree(vertex)), + adjacent_vertex_function(vertex, &graph)))); + } + + //================== + // Adjacency Matrix + //================== + + friend std::pair + edge (typename type::vertex_descriptor source_vertex, + typename type::vertex_descriptor destination_vertex, + const type& graph) { + + std::pair edge_exists = + std::make_pair(std::make_pair(source_vertex, destination_vertex), false); + + const edges_size_type index = graph.index_of(edge_exists.first); + + edge_exists.second = index >= 0; + + return edge_exists; + } + + //============== + // EdgeListGraph + //============== + + friend inline typename type::edges_size_type + num_edges(const type& graph) { + return (graph.num_edges()); + } + + friend inline typename type::edge_descriptor + edge_at(typename type::edges_size_type edge_index, + const type& graph) { + return (graph.edge_at(edge_index)); + } + + friend inline std::pair + edges(const type& graph) { + typedef typename type::edge_index_iterator edge_index_iterator; + typedef typename type::edge_function edge_function; + typedef typename type::edge_iterator edge_iterator; + + return (std::make_pair + (edge_iterator(edge_index_iterator(0), + edge_function(&graph)), + edge_iterator(edge_index_iterator(graph.num_edges()), + edge_function(&graph)))); + } + + //============================= + // Index Property Map Functions + //============================= + + friend inline typename type::vertices_size_type + get(vertex_index_t, + const type& graph, + typename type::vertex_descriptor vertex) { + + type::vertices_size_type index = graph.index_of(vertex); + KIS_ASSERT(index >= 0); + return index; + } + + friend inline typename type::edges_size_type + get(edge_index_t, + const type& graph, + typename type::edge_descriptor edge) { + + type::edges_size_type index = graph.index_of(edge); + KIS_ASSERT(index >= 0); + return index; + } + + friend inline lazy_fill_graph_index_map< + type, + typename type::vertex_descriptor, + typename type::vertices_size_type> + get(vertex_index_t, const type& graph) { + return (lazy_fill_graph_index_map< + type, + typename type::vertex_descriptor, + typename type::vertices_size_type>(graph)); + } + + friend inline lazy_fill_graph_index_map< + type, + typename type::edge_descriptor, + typename type::edges_size_type> + get(edge_index_t, const type& graph) { + return (lazy_fill_graph_index_map< + type, + typename type::edge_descriptor, + typename type::edges_size_type>(graph)); + } + + friend inline lazy_fill_graph_reverse_edge_map< + typename type::edge_descriptor> + get(edge_reverse_t, const type& graph) { + Q_UNUSED(graph); + return (lazy_fill_graph_reverse_edge_map< + typename type::edge_descriptor>()); + } + + template + friend struct lazy_fill_graph_index_map; + + template + friend struct lazy_fill_graph_reverse_edge_map; +}; + +namespace boost { + template <> + struct property_map { + typedef lazy_fill_graph_index_map::vertex_descriptor, + typename graph_traits::vertices_size_type> type; + typedef type const_type; + }; + + template<> + struct property_map { + typedef lazy_fill_graph_index_map::edge_descriptor, + typename graph_traits::edges_size_type> type; + typedef type const_type; + }; +} + +namespace boost { + template <> + struct property_map { + typedef lazy_fill_graph_reverse_edge_map::edge_descriptor> type; + typedef type const_type; + }; +} + +QDebug operator<<(QDebug dbg, const KisLazyFillGraph::vertex_descriptor &v) { + const QString type = + v.type == KisLazyFillGraph::vertex_descriptor::NORMAL ? "normal" : + v.type == KisLazyFillGraph::vertex_descriptor::LABEL_A ? "label_A" : + v.type == KisLazyFillGraph::vertex_descriptor::LABEL_B ? "label_B" : ""; + + dbg.nospace() << "(" << v.x << ", " << v.y << ", " << type << ")"; + return dbg.space(); +} + +QDebug operator<<(QDebug dbg, const KisLazyFillGraph::edge_descriptor &e) { + KisLazyFillGraph::vertex_descriptor src = e.first; + KisLazyFillGraph::vertex_descriptor dst = e.second; + + dbg.nospace() << "(" << src << " -> " << dst << ")"; + return dbg.space(); +} + +#endif /* __KIS_LAZY_FILL_GRAPH_H */ diff --git a/libs/image/lazybrush/kis_lazy_fill_tools.h b/libs/image/lazybrush/kis_lazy_fill_tools.h new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_lazy_fill_tools.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2016 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 __KIS_LAZY_FILL_TOOLS_H +#define __KIS_LAZY_FILL_TOOLS_H + +#include "kis_types.h" +#include "kritaimage_export.h" +#include + + +class KoColor; + +namespace KisLazyFillTools +{ + KRITAIMAGE_EXPORT + void normalizeAndInvertAlpha8Device(KisPaintDeviceSP dev, const QRect &rect); + + /** + * Uses Boykov-Kolmogorov Max-Flow/Min-Cut algorithm to split the + * device \src into two parts. The first part is defined by \p + * colorScribble and the second part --- by \p + * backgroundScribble. In the result of the split the area defined + * by \p colorScribble in \p resultDevice is filled with \p + * color. Also the same area in \p maskDevice is filled with a + * non-null scribble index. + * + * \p maskDevice is used for limiting the area used for filling + * the color. + */ + KRITAIMAGE_EXPORT + void cutOneWay(const KoColor &color, + KisPaintDeviceSP src, + KisPaintDeviceSP colorScribble, + KisPaintDeviceSP backgroundScribble, + KisPaintDeviceSP resultDevice, + KisPaintDeviceSP maskDevice, + const QRect &boundingRect); + + /** + * Returns one pixel from each connected component of \p src. + * + * WARNING: \p src is used as a temporary device, so it will be + * cleared(!) after the execution of the algorithm + */ + + KRITAIMAGE_EXPORT + QVector splitIntoConnectedComponents(KisPaintDeviceSP src, const QRect &boundingRect); + + struct KRITAIMAGE_EXPORT KeyStroke { + KeyStroke(); + KeyStroke(KisPaintDeviceSP _dev, const KoColor &_color); + + KisPaintDeviceSP dev; + KoColor color; + }; +}; + +#endif /* __KIS_LAZY_FILL_TOOLS_H */ diff --git a/libs/image/lazybrush/kis_lazy_fill_tools.cpp b/libs/image/lazybrush/kis_lazy_fill_tools.cpp new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_lazy_fill_tools.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2016 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 "kis_lazy_fill_tools.h" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "lazybrush/kis_lazy_fill_graph.h" +#include "lazybrush/kis_lazy_fill_capacity_map.h" + +#include "kis_sequential_iterator.h" +#include + +#include "krita_utils.h" + +namespace KisLazyFillTools { + +void normalizeAndInvertAlpha8Device(KisPaintDeviceSP dev, const QRect &rect) +{ + quint8 maxPixel = std::numeric_limits::min(); + quint8 minPixel = std::numeric_limits::max(); + KritaUtils::applyToAlpha8Device(dev, rect, + [&minPixel, &maxPixel](quint8 pixel) { + if (pixel > maxPixel) { + maxPixel = pixel; + } + if (pixel < minPixel) { + minPixel = pixel; + } + }); + + const qreal scale = 255.0 / (maxPixel - minPixel); + KritaUtils::filterAlpha8Device(dev, rect, + [minPixel, scale](quint8 pixel) { + return pow2(255 - quint8((pixel - minPixel) * scale)) / 255; + }); +} + +void cutOneWay(const KoColor &color, + KisPaintDeviceSP src, + KisPaintDeviceSP colorScribble, + KisPaintDeviceSP backgroundScribble, + KisPaintDeviceSP resultDevice, + KisPaintDeviceSP maskDevice, + const QRect &boundingRect) +{ + using namespace boost; + + KIS_ASSERT_RECOVER_RETURN(src->pixelSize() == 1); + KIS_ASSERT_RECOVER_RETURN(colorScribble->pixelSize() == 1); + KIS_ASSERT_RECOVER_RETURN(backgroundScribble->pixelSize() == 1); + KIS_ASSERT_RECOVER_RETURN(maskDevice->pixelSize() == 1); + KIS_ASSERT_RECOVER_RETURN(*resultDevice->colorSpace() == *color.colorSpace()); + + KisLazyFillCapacityMap capacityMap(src, colorScribble, backgroundScribble, maskDevice, boundingRect); + KisLazyFillGraph &graph = capacityMap.graph(); + + std::vector groups(num_vertices(graph)); + std::vector residual_capacity(num_edges(graph), 0); + + std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); + std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); + + auto vertexIndexMap = get(boost::vertex_index, graph); + + typedef KisLazyFillGraph::vertex_descriptor Vertex; + + Vertex s(Vertex::LABEL_A); + Vertex t(Vertex::LABEL_B); + + float maxFlow = + boykov_kolmogorov_max_flow(graph, + capacityMap, + make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), + get(boost::edge_reverse, graph), + make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), + make_iterator_property_map(&groups[0], vertexIndexMap), + make_iterator_property_map(&distance_vec[0], vertexIndexMap), + vertexIndexMap, + s, + t); + Q_UNUSED(maxFlow); + + KisSequentialIterator dstIt(resultDevice, graph.rect()); + KisSequentialIterator mskIt(maskDevice, graph.rect()); + + const int pixelSize = resultDevice->pixelSize(); + + do { + KisLazyFillGraph::vertex_descriptor v(dstIt.x(), dstIt.y()); + long vertex_idx = get(boost::vertex_index, graph, v); + default_color_type label = groups[vertex_idx]; + + if (label == black_color) { + memcpy(dstIt.rawData(), color.data(), pixelSize); + *mskIt.rawData() = 10 + (int(label) << 4); + } + } while (dstIt.nextPixel() && mskIt.nextPixel()); +} + + QVector splitIntoConnectedComponents(KisPaintDeviceSP dev, + const QRect &boundingRect) +{ + QVector points; + const KoColorSpace *cs = dev->colorSpace(); + + const QRect rect = dev->exactBounds() & boundingRect; + if (rect.isEmpty()) return points; + + /** + * Please note that since we modify the device inside + * clearNonZeroComponent() call, we must use a *writable* + * iterator, for not ending up with a lazy copied old version of a + * device. + */ + KisSequentialIterator dstIt(dev, rect); + + do { + if (cs->opacityU8(dstIt.rawData()) > 0) { + const QPoint pt(dstIt.x(), dstIt.y()); + points << pt; + + KisScanlineFill fill(dev, pt, rect); + fill.clearNonZeroComponent(); + } + } while (dstIt.nextPixel()); + + return points; +} + + +KeyStroke::KeyStroke() +{ +} + +KeyStroke::KeyStroke(KisPaintDeviceSP _dev, const KoColor &_color) + : dev(_dev), color(_color) +{ +} + +} diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/lazybrush/kis_min_cut_worker.h copy from libs/image/kis_cached_paint_device.h copy to libs/image/lazybrush/kis_min_cut_worker.h --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/lazybrush/kis_min_cut_worker.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Dmitry Kazakov + * Copyright (c) 2016 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 @@ -16,33 +16,21 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KIS_CACHED_PAINT_DEVICE_H -#define __KIS_CACHED_PAINT_DEVICE_H +#ifndef __KIS_MIN_CUT_WORKER_H +#define __KIS_MIN_CUT_WORKER_H -#include "tiles3/kis_lockless_stack.h" +#include -class KisCachedPaintDevice +class KisMinCutWorker { public: - KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { - KisPaintDeviceSP device; - - if(!m_stack.pop(device)) { - device = new KisPaintDevice(prototype->colorSpace()); - } - - device->prepareClone(prototype); - return device; - } - - void putDevice(KisPaintDeviceSP device) { - device->clear(); - m_stack.push(device); - } + KisMinCutWorker(); + ~KisMinCutWorker(); private: - KisLocklessStack m_stack; + struct Private; + const QScopedPointer m_d; }; -#endif /* __KIS_CACHED_PAINT_DEVICE_H */ +#endif /* __KIS_MIN_CUT_WORKER_H */ diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/lazybrush/kis_min_cut_worker.cpp copy from libs/image/kis_cached_paint_device.h copy to libs/image/lazybrush/kis_min_cut_worker.cpp --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/lazybrush/kis_min_cut_worker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Dmitry Kazakov + * Copyright (c) 2016 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 @@ -16,33 +16,17 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KIS_CACHED_PAINT_DEVICE_H -#define __KIS_CACHED_PAINT_DEVICE_H +#include "kis_min_cut_worker.h" -#include "tiles3/kis_lockless_stack.h" - - -class KisCachedPaintDevice +struct KisMinCutWorker::Private { -public: - KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { - KisPaintDeviceSP device; - - if(!m_stack.pop(device)) { - device = new KisPaintDevice(prototype->colorSpace()); - } - - device->prepareClone(prototype); - return device; - } - - void putDevice(KisPaintDeviceSP device) { - device->clear(); - m_stack.push(device); - } - -private: - KisLocklessStack m_stack; }; -#endif /* __KIS_CACHED_PAINT_DEVICE_H */ +KisMinCutWorker::KisMinCutWorker() + : m_d(new Private) +{ +} + +KisMinCutWorker::~KisMinCutWorker() +{ +} diff --git a/libs/image/kis_cached_paint_device.h b/libs/image/lazybrush/kis_multiway_cut.h copy from libs/image/kis_cached_paint_device.h copy to libs/image/lazybrush/kis_multiway_cut.h --- a/libs/image/kis_cached_paint_device.h +++ b/libs/image/lazybrush/kis_multiway_cut.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Dmitry Kazakov + * Copyright (c) 2016 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 @@ -16,33 +16,34 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KIS_CACHED_PAINT_DEVICE_H -#define __KIS_CACHED_PAINT_DEVICE_H +#ifndef __KIS_MULTIWAY_CUT_H +#define __KIS_MULTIWAY_CUT_H -#include "tiles3/kis_lockless_stack.h" +#include +#include "kis_types.h" +#include "kritaimage_export.h" -class KisCachedPaintDevice +class KoColor; + +class KRITAIMAGE_EXPORT KisMultiwayCut { public: - KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { - KisPaintDeviceSP device; + KisMultiwayCut(KisPaintDeviceSP src, + KisPaintDeviceSP dst, + const QRect &boundingRect); + ~KisMultiwayCut(); - if(!m_stack.pop(device)) { - device = new KisPaintDevice(prototype->colorSpace()); - } + void addKeyStroke(KisPaintDeviceSP dev, const KoColor &color); - device->prepareClone(prototype); - return device; - } + void run(); - void putDevice(KisPaintDeviceSP device) { - device->clear(); - m_stack.push(device); - } + KisPaintDeviceSP srcDevice() const; + KisPaintDeviceSP dstDevice() const; private: - KisLocklessStack m_stack; + struct Private; + const QScopedPointer m_d; }; -#endif /* __KIS_CACHED_PAINT_DEVICE_H */ +#endif /* __KIS_MULTIWAY_CUT_H */ diff --git a/libs/image/lazybrush/kis_multiway_cut.cpp b/libs/image/lazybrush/kis_multiway_cut.cpp new file mode 100644 --- /dev/null +++ b/libs/image/lazybrush/kis_multiway_cut.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2016 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 "kis_multiway_cut.h" + +#include +#include +#include + +#include "kis_paint_device.h" +#include "kis_painter.h" +#include "kis_lazy_fill_tools.h" +#include "kis_sequential_iterator.h" +#include + + +using namespace KisLazyFillTools; + +struct KisMultiwayCut::Private +{ + KisPaintDeviceSP src; + KisPaintDeviceSP dst; + KisPaintDeviceSP mask; + QRect boundingRect; + + QVector keyStrokes; + + static void maskOutKeyStroke(KisPaintDeviceSP keyStrokeDevice, KisPaintDeviceSP mask, const QRect &boundingRect); +}; + +KisMultiwayCut::KisMultiwayCut(KisPaintDeviceSP src, + KisPaintDeviceSP dst, + const QRect &boundingRect) + : m_d(new Private) +{ + m_d->src = src; + m_d->dst = dst; + m_d->mask = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + m_d->boundingRect = boundingRect; +} + +KisMultiwayCut::~KisMultiwayCut() +{ +} + +void KisMultiwayCut::addKeyStroke(KisPaintDeviceSP dev, const KoColor &color) +{ + m_d->keyStrokes << KeyStroke(dev, color); +} + + +void KisMultiwayCut::Private::maskOutKeyStroke(KisPaintDeviceSP keyStrokeDevice, KisPaintDeviceSP mask, const QRect &boundingRect) +{ + KIS_ASSERT_RECOVER_RETURN(keyStrokeDevice->pixelSize() == 1); + KIS_ASSERT_RECOVER_RETURN(mask->pixelSize() == 1); + + QRegion region = + keyStrokeDevice->region() & + mask->exactBounds() & boundingRect; + + Q_FOREACH (const QRect &rc, region.rects()) { + KisSequentialIterator dstIt(keyStrokeDevice, rc); + KisSequentialConstIterator mskIt(mask, rc); + + do { + if (*mskIt.rawDataConst() > 0) { + *dstIt.rawData() = 0; + } + } while (dstIt.nextPixel() && mskIt.nextPixel()); + } +} + +void KisMultiwayCut::run() +{ + KisPaintDeviceSP other = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + + while (m_d->keyStrokes.size() > 1) { + KeyStroke current = m_d->keyStrokes.takeFirst(); + KisPainter gc(other); + + Q_FOREACH (const KeyStroke &s, m_d->keyStrokes) { + const QRect rc = s.dev->extent() & m_d->boundingRect; + gc.bitBlt(rc.topLeft(), s.dev, rc); + } + + KisLazyFillTools::cutOneWay(current.color, + m_d->src, + current.dev, + other, + m_d->dst, + m_d->mask, + m_d->boundingRect); + + other->clear(); + } + + // TODO: check if one can use the last cut for this purpose! + + if (m_d->keyStrokes.size() == 1) { + KeyStroke current = m_d->keyStrokes.takeLast(); + + m_d->maskOutKeyStroke(current.dev, m_d->mask, m_d->boundingRect); + + QVector points = + KisLazyFillTools::splitIntoConnectedComponents(current.dev, m_d->boundingRect); + + Q_FOREACH (const QPoint &pt, points) { + KisScanlineFill fill(m_d->mask, pt, m_d->boundingRect); + fill.fillColor(current.color, m_d->dst); + } + } +} + +KisPaintDeviceSP KisMultiwayCut::srcDevice() const +{ + return m_d->src; +} + +KisPaintDeviceSP KisMultiwayCut::dstDevice() const +{ + return m_d->dst; +} diff --git a/libs/image/tests/CMakeLists.txt b/libs/image/tests/CMakeLists.txt --- a/libs/image/tests/CMakeLists.txt +++ b/libs/image/tests/CMakeLists.txt @@ -688,3 +688,8 @@ kde4_add_unit_test(KisAslParserTest TESTNAME kritalibpsd-asl_parser_test ${kis_asl_parser_test_SRCS}) target_link_libraries(KisAslParserTest kritapsd kritapigment kritawidgetutils ${QT_QTXML_LIBRARY} Qt5::Test) +########### next target ############### + +set(kis_lazy_brush_test_SRCS kis_lazy_brush_test.cpp ) +kde4_add_unit_test(KisLazyBrushTest TESTNAME kritaimage-lazy_brush_test ${kis_lazy_brush_test_SRCS}) +target_link_libraries(KisLazyBrushTest kritaimage ${QT_QTTEST_LIBRARY}) \ No newline at end of file diff --git a/libs/image/tests/data/fill1_a.png b/libs/image/tests/data/fill1_a.png new file mode 100644 index 0000000000000000000000000000000000000000..a405589fec9a2b8b91289ad668371911eeaea40c GIT binary patch literal 1363 zc$}S->pK$)0LI5?GcC+y2op|3YS?i(F3()XFln|?xo#GjiM2H)U6zSsBITNpvF4Io zQXL1aW6kI!s?%lmWYUFBm?PSea`Z2p55M30Jnx71<696B9t4G$LI3~&6c-#o*vOo3( z@RD$Decha4Xx30 z9LbU71J@$l1|VxcVV)a(tg|uQ+UQGDI;oHll@U9d19BG#hIk{-E-3V!sj=xE1GC|& zL@krznneL~8WnL7@y_Q41FYIQHdZuAJk%CgmFdL~Plaol01utDEm$OS9Ygp9GY25v zHREJ^d4)@DYs`bu8N~|kJ*H%H<-UaTE5}H*)VL~wX@pSTJN1<*t+pU}Oh-Z}Z!smM z@(G@u8<})_zInpb2CM6S5iudQOl7n(vC}`hSnuV+8}#G(Ou=@Pa61Z;BXQS$jrE_@ zEo<}UWjiz#I=!za?$XdBwOEMyuUa6nm1r*Frj5i!TP-|PvIO7lN#cY#SnrhMcZKrz z@bs$D)huJ~a*C=|6WzX*higX?lKh%daeiJz(c?7`YKx7unkj&!*@sr!SMcEH)*i~t z%Tge92>myS`?ExV_~PDXs4{kh(l}GXO<%3y_BALfi#>O43Y&uVbfG=0bJf{OM?LRT zsq>Zlj7j5doyeij2Mc53%+}%pDY*b&Jngxx5Sf^XkShXdvYJ!zpQiXoS#!t}vTJgh z<`F{GA4}ve>D%0F*!Fyby47W75O^{6w}uvHBcUpk>4&o7T%N6^{rS{eJ7#Q&Y01eY zt!Ca5Kq!6W+FQd0&EV2uoo*Nf(?stY0w5|{xQgMH;K&$hvOn}nXiyxO4#$$9(^ z)}6a-4{FdQ_2IZsQpmZ)lgA7sDRynwKVKVGe@J`^Y9D<-hb$lbS3Zhoxi!0^BSpy& zb%&FEMvtpk83Ns#{ATy86Oaf4b%{8%3l1X}o`|PbQ5X)0Hw#{HRms`90Dq1gx zkR5twCsuRR&J#K8cx~^=3cDWqtIT0=$p{m*`8d?WHvcnOllO$(*!D8=Ji9)YM29Oh z`tN-`ad5#_@cXN)ggnLg<1uL7u$R_4%3obqwBCyBw3{9&T@=Mw?QktKek4Lmxr()V zgkHgYt2uh{7Hg~(XyHZ{<4h8qw?~Sg@=^a+3ZK(J`j>)DHg{f{XE z)7O>#0S6O5CtI;dwkA+Wvcxr_#5q4VH#M(>!MP|ku_QG`p**uBL&4qCHz2%`Pn>~) zImy$-F{I+w+ZmC;!j2+t=Y85#l^CbU9_0Pv`Tu|X#Z4k>ghaR}sOsE2Hhq0}bTDI0 zgT&eLRm-e4t1y56@^;_dgdW3V63J8}W*nT%`|y78<@+IFQQVadjL8nZx>?=|?7Mbd zW39@verUsWN;`LD`aj8uH{)ya`Ft2AA5h*}nhS(%vPS#;y23EuB}#FaM#QKyvT=bZ7U^tn{#EwC*6=b=J{`7Yf9DeEk+MT zKk!v>@8K}|?H1osd&f*&5Gdi`2e*LjnjTFw)=k?VDaY!OEdbKQ$SJA$&Q;EveG!-?89ZJ6T-G@yGywo?%dyb_ literal 0 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 VIpL}I{3#Id(a|tauTixP|35U^-?0Dy literal 0 Hc$@rYRdc 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 KFPe`;7yJ)9%Xz;5 literal 0 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 KtoVpq?EeE|G3_w` literal 0 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 literal 0 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 literal 0 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 Zt!Z6Jth0BZlR5Yw6@(6K+8uxXe*pjJ^DO`X literal 0 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 ZEOmomwX;z>J{b5%ML|QpLe?zg{{c`W-829I literal 0 Hc$@ + * Copyright (c) 2016 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 @@ -16,33 +16,26 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef __KIS_CACHED_PAINT_DEVICE_H -#define __KIS_CACHED_PAINT_DEVICE_H +#ifndef __KIS_LAZY_BRUSH_TEST_H +#define __KIS_LAZY_BRUSH_TEST_H -#include "tiles3/kis_lockless_stack.h" +#include - -class KisCachedPaintDevice +class KisLazyBrushTest : public QObject { -public: - KisPaintDeviceSP getDevice(KisPaintDeviceSP prototype) { - KisPaintDeviceSP device; - - if(!m_stack.pop(device)) { - device = new KisPaintDevice(prototype->colorSpace()); - } - - device->prepareClone(prototype); - return device; - } - - void putDevice(KisPaintDeviceSP device) { - device->clear(); - m_stack.push(device); - } - -private: - KisLocklessStack m_stack; + Q_OBJECT +private Q_SLOTS: + //void test(); + void testGraph(); + void testGraphMultilabel(); + void testGraphStandardIterators(); + void testGraphConcepts(); + void testCutOnGraph(); + void testCutOnGraphDevice(); + void testCutOnGraphDeviceMulti(); + void testLoG(); + + void testSplitIntoConnectedComponents(); }; -#endif /* __KIS_CACHED_PAINT_DEVICE_H */ +#endif /* __KIS_LAZY_BRUSH_TEST_H */ diff --git a/libs/image/tests/kis_lazy_brush_test.cpp b/libs/image/tests/kis_lazy_brush_test.cpp new file mode 100644 --- /dev/null +++ b/libs/image/tests/kis_lazy_brush_test.cpp @@ -0,0 +1,1306 @@ +/* + * Copyright (c) 2016 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 "kis_lazy_brush_test.h" + +#include +#include "kis_debug.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "lazybrush/kis_lazy_fill_graph.h" + +#if 0 + +int +doSomething() +{ + using namespace boost; + + typedef adjacency_list_traits < vecS, vecS, directedS > Traits; + typedef adjacency_list < vecS, vecS, directedS, + property < vertex_name_t, std::string, + property < vertex_index_t, long, + property < vertex_color_t, boost::default_color_type, + property < vertex_distance_t, long, + property < vertex_predecessor_t, Traits::edge_descriptor > > > > >, + + property < edge_capacity_t, long, + property < edge_residual_capacity_t, long, + property < edge_reverse_t, Traits::edge_descriptor > > > > Graph; + + Graph g; + property_map < Graph, edge_capacity_t >::type + capacity = get(edge_capacity, g); + property_map < Graph, edge_residual_capacity_t >::type + residual_capacity = get(edge_residual_capacity, g); + property_map < Graph, edge_reverse_t >::type rev = get(edge_reverse, g); + Traits::vertex_descriptor s, t; + read_dimacs_max_flow(g, capacity, rev, s, t); + + std::vector color(num_vertices(g)); + std::vector distance(num_vertices(g)); + long flow = boykov_kolmogorov_max_flow(g ,s, t); + + std::cout << "c The total flow:" << std::endl; + std::cout << "s " << flow << std::endl << std::endl; + + std::cout << "c flow values:" << std::endl; + graph_traits < Graph >::vertex_iterator u_iter, u_end; + graph_traits < Graph >::out_edge_iterator ei, e_end; + for (boost::tie(u_iter, u_end) = vertices(g); u_iter != u_end; ++u_iter) { + qDebug() << ppVar(get(vertex_color, g)[*u_iter]); + + + for (boost::tie(ei, e_end) = out_edges(*u_iter, g); ei != e_end; ++ei) { + if (capacity[*ei] > 0) { + std::cout << "f " << *u_iter << " " << target(*ei, g) << " " + << (capacity[*ei] - residual_capacity[*ei]) << std::endl; + } + } + } + + return EXIT_SUCCESS; +} + +#include +#include +#include + +template +class ImageToCapacityMap +{ + typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; + typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; + +public: + typedef EdgeDescriptor key_type; + typedef float value_type; + typedef const float& reference; + typedef boost::readable_property_map_tag category; + + ImageToCapacityMap(Graph &graph, const QImage &image) + : m_graph(graph), m_image(image) + { + m_bits = reinterpret_cast(m_image.constBits()); + m_minIntensity = std::numeric_limits::max(); + m_maxIntensity = std::numeric_limits::min(); + + int totalInt = 0; + const int numPixels = m_image.width() * m_image.height(); + m_intensities.resize(numPixels); + for (int i = 0; i < numPixels; i++) { + const int v = qGray(m_bits[i]); + m_intensities[i] = v; + qDebug() << ppVar(i) << ppVar(v); + totalInt += v; + + if (m_minIntensity > v) m_minIntensity = v; + if (m_maxIntensity < v) m_maxIntensity = v; + } + + qDebug() << ppVar(totalInt); + + m_pixelSize = 4; + m_rowStride = m_image.width() * m_pixelSize; + + qDebug() << ppVar(m_rowStride) << ppVar(m_image.size()) << ppVar(m_image.bytesPerLine()); + } + Graph &m_graph; + QImage m_image; + QVector m_intensities; + const QRgb *m_bits; + int m_rowStride; + int m_pixelSize; + int m_minIntensity; + int m_maxIntensity; +}; + +template +typename ImageToCapacityMap::value_type +get(const ImageToCapacityMap &map, + const typename ImageToCapacityMap::key_type &key) +{ + typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; + + VertexDescriptor src = source(key, map.m_graph); + VertexDescriptor tgt = target(key, map.m_graph); + const int x0 = src[0]; + const int y0 = src[1]; + const int x1 = tgt[0]; + const int y1 = tgt[1]; + + const int k = 2 * (map.m_image.width() + map.m_image.height()); + const int maxDimension = qMax(map.m_image.width(), map.m_image.height()); + + //if (x0 <= 5 && x1 <= 5 && y0 == 0 && y1 ==0) return 255; + //if (x0 >= 5 && x1 >= 5 && y0 == 8 && y1 ==8) return 255; + + //const QRgb *p0 = map.m_bits + y0 * map.m_rowStride + x0 * map.m_pixelSize; + //const QRgb *p1 = map.m_bits + y1 * map.m_rowStride + x1 * map.m_pixelSize; + //float value = 255.0 - qAbs(qGray(*p0) - qGray(*p1)); + + if ((!x0 && !y0) || (!x1 && !y1) || + (x0 == map.m_image.width() - 1 && y0 == map.m_image.height() - 1) || + (x1 == map.m_image.width() - 1 && y1 == map.m_image.height() - 1)) { + + qreal value = maxDimension * k; + + qDebug() << x0 << y0 << "->" << x1 << y1 << value; + + return value; + } + + const int i0 = map.m_intensities[x0 + y0 * map.m_image.width()]; + const int i1 = map.m_intensities[x1 + y1 * map.m_image.width()]; + + const int diff = qAbs(i0 - i1); + qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); + + float value = 1.0 + k * (1.0 - normDiff) + qMin(y0, y1); + + qDebug() << x0 << y0 << "->" << x1 << y1 << value;// << ppVar(normDiff); + + return value; +} + +template +ImageToCapacityMap +MakeImageToCapacityMap(Graph &graph, const QImage &image) { + return ImageToCapacityMap(graph, image); +} + +int doSomethingElse() +{ + + const unsigned int D = 2; + typedef boost::grid_graph Graph; + typedef boost::graph_traits::vertex_descriptor VertexDescriptor; + typedef boost::graph_traits::edge_descriptor EdgeDescriptor;//ADDED + typedef boost::graph_traits::vertices_size_type VertexIndex; + typedef boost::graph_traits::edges_size_type EdgeIndex; + + + QImage image(QSize(9,9), QImage::Format_ARGB32); + QPainter gc(&image); + gc.fillRect(image.rect(), Qt::white); + gc.fillRect(QRect(0,4,2,1), Qt::blue); + //gc.fillRect(QRect(0,5,2,1), Qt::blue); + gc.fillRect(QRect(6,4,3,1), Qt::blue); + gc.end(); + + image.save("graph_img.png"); + + boost::array lengths = { { image.width(), image.height() } }; + Graph graph(lengths, false); + + std::vector groups(num_vertices(graph), 0); + std::vector residual_capacity(num_edges(graph), 0); + auto capacityMap = MakeImageToCapacityMap(graph, image); + + float capSum = 0; + + BGL_FORALL_EDGES(e,graph,Graph) { + VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + //VertexIndex source_idx = get(boost::vertex_index,graph,src); + //VertexIndex target_idx = get(boost::vertex_index,graph,tgt); + //EdgeIndex edge_idx = get(boost::edge_index,graph,e); + + // qDebug() << "(" << src[0] << src[1] << ")" + // << "->" + // << "(" << tgt[0] << tgt[1] << ")" + // << get(capacityMap, e); + //capSum += get(capacityMap, e); + } + + //qDebug() << ppVar(capSum); + + BGL_FORALL_VERTICES(v,graph,Graph) + { + //qDebug() << ppVar(v[0]) << ppVar(v[1]); + +/* VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + VertexIndex source_idx = get(boost::vertex_index,graph,src); + VertexIndex target_idx = get(boost::vertex_index,graph,tgt); + EdgeIndex edge_idx = get(boost::edge_index,graph,e); + + capacity[edge_idx] = 255.0f - fabs(pixel_intensity[source_idx]-pixel_intensity[target_idx]); //you should change this to your "gradiant or intensity or something" + + reverse_edges[edge_idx]=edge(tgt,src,graph).first;//ADDED +*/ + } + + + const int t_index = image.width() * image.height() - 1; + VertexDescriptor s=vertex(0,graph), t=vertex(t_index,graph); + + + float maxFlow = + boykov_kolmogorov_max_flow(graph, + capacityMap, + make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), + get(boost::edge_reverse, graph), + make_iterator_property_map(&groups[0], get(boost::vertex_index, graph)), + get(boost::vertex_index, graph), + s, + t + ); + + qDebug() << ppVar(maxFlow); + + const int cell = 10; + const int half = cell / 2; + QImage result(QSize(cell * lengths[0], cell * lengths[1]), QImage::Format_ARGB32); + QPainter resultPainter(&result); + + BGL_FORALL_VERTICES(v, graph, Graph) { + const int x = v[0]; + const int y = v[1]; + + VertexIndex vertex_idx = get(boost::vertex_index, graph, v); + int label = groups[vertex_idx]; + + QColor color = + label == 0 ? Qt::blue : + label == 4 ? Qt::green : + label == 1 ? Qt::gray : + Qt::red; + + QRect rc(cell * x, cell * y, cell, cell); + resultPainter.fillRect(rc, color); + } + + BGL_FORALL_EDGES(e,graph,Graph) { + EdgeIndex egdeIndex = get(boost::edge_index, graph, e); + const int cap = residual_capacity[egdeIndex]; + + + QColor color(Qt::black); + if (cap != 0) { + const int fullCap = get(capacityMap, e); + const int gray = qreal(cap) / fullCap * 50.0; + color.setAlpha(gray); + } + + VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + + QPoint p0(half + cell * src[0], half + cell * src[1]); + QPoint p1(half + cell * tgt[0], half + cell * tgt[1]); + + resultPainter.setPen(QPen(color, 2)); + resultPainter.drawLine(p0, p1); + + // qDebug() << "(" << src[0] << src[1] << ")" + // << "->" + // << "(" << tgt[0] << tgt[1] << ")" + // << residual_capacity[egdeIndex]; + } + + result.save("result.png"); + + return 0; +} + +void KisLazyBrushTest::test() +{ + doSomethingElse(); +} + +#endif /*0*/ + +bool verifyNormalVertex(const KisLazyFillGraph::vertex_descriptor &v, int x, int y) +{ + bool result = v.type == KisLazyFillGraph::vertex_descriptor::NORMAL && + v.x == x && v.y == y; + + if (!result) { + qDebug() << ppVar(v) << ppVar(x) << ppVar(y); + } + + return result; +} + +bool verifyVertexIndex(KisLazyFillGraph &g, + KisLazyFillGraph::vertex_descriptor v, + KisLazyFillGraph::vertices_size_type index) +{ + bool result = true; + + const KisLazyFillGraph::vertices_size_type actualIndex = g.index_of(v); + const KisLazyFillGraph::vertex_descriptor actualVertex = g.vertex_at(index); + + if (index >= 0) { + result &= v == actualVertex; + } + + result &= index == actualIndex; + + if (!result) { + qDebug() << "Vertex failed:"; + qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; + qDebug() << index << "->" << actualVertex << "( expected:" << v << ")"; + } + + return result; +} + +bool verifyEdgeIndex(KisLazyFillGraph &g, + KisLazyFillGraph::edge_descriptor v, + KisLazyFillGraph::edges_size_type index) +{ + bool result = true; + + const KisLazyFillGraph::edges_size_type actualIndex = g.index_of(v); + const KisLazyFillGraph::edge_descriptor actualEdge = g.edge_at(index); + + if (index >= 0) { + result &= v == actualEdge; + } + + result &= index == actualIndex; + + if (!result) { + qDebug() << "Edge failed:"; + qDebug() << v << "->" << actualIndex << "( expected:" << index << ")"; + qDebug() << index << "->" << actualEdge << "( expected:" << v << ")"; + } + + return result; +} + +void KisLazyBrushTest::testGraph() +{ + QRect mainRect(10, 10, 100, 100); + QRect aLabelRect(30, 20, 20, 20); + QRect bLabelRect(60, 60, 20, 20); + + KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); + + const int numVertices = g.num_vertices(); + const int numEdges = g.num_edges(); + + qDebug() << ppVar(numVertices); + qDebug() << ppVar(numEdges); + + QCOMPARE(numVertices, 10002); + QCOMPARE(numEdges, 41200); + + for (int i = 0; i < numVertices; i++) { + KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); + int newVertexIndex = g.index_of(vertex); + QCOMPARE(newVertexIndex, i); + } + + for (int i = 0; i < numEdges; i++) { + KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); + int newEdgeIndex = g.index_of(edge); + QCOMPARE(newEdgeIndex, i); + } + + KisLazyFillGraph::vertex_descriptor v1(10, 10); + KisLazyFillGraph::vertex_descriptor v2(11, 10); + KisLazyFillGraph::vertex_descriptor v3(10, 11); + KisLazyFillGraph::vertex_descriptor v4(11, 11); + KisLazyFillGraph::vertex_descriptor v5(12, 10); + KisLazyFillGraph::vertex_descriptor v6(31, 21); + KisLazyFillGraph::vertex_descriptor v7(61, 61); + KisLazyFillGraph::vertex_descriptor v8(9, 10); + + KisLazyFillGraph::vertex_descriptor vA(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_A); + KisLazyFillGraph::vertex_descriptor vB(0, 0, KisLazyFillGraph::vertex_descriptor::LABEL_B); + + // Verify vertex index mapping + + QVERIFY(verifyVertexIndex(g, v1, 0)); + QVERIFY(verifyVertexIndex(g, v2, 1)); + QVERIFY(verifyVertexIndex(g, v3, 100)); + QVERIFY(verifyVertexIndex(g, v4, 101)); + QVERIFY(verifyVertexIndex(g, v5, 2)); + QVERIFY(verifyVertexIndex(g, v6, 1121)); + QVERIFY(verifyVertexIndex(g, v7, 5151)); + QVERIFY(verifyVertexIndex(g, v8, -1)); + + QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); + QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); + + // Verify edge index mapping + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40021)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 40421)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 40821)); + + + QCOMPARE(g.out_degree(v1), long(2)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); + + QCOMPARE(g.out_degree(v4), long(4)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); + + QCOMPARE(g.out_degree(v6), long(5)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); + QCOMPARE(g.out_edge_at(v6, 4).second, vA); + + QCOMPARE(g.out_degree(v7), long(5)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); + QCOMPARE(g.out_edge_at(v7, 4).second, vB); + + QCOMPARE(g.out_degree(vA), long(400)); + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); +} + +void KisLazyBrushTest::testGraphMultilabel() +{ + QRect mainRect(10, 10, 100, 100); + + QRect aLabelRect1(30, 20, 20, 20); + QRect aLabelRect2(70, 30, 20, 20); + + QRect bLabelRect1(60, 60, 20, 20); + QRect bLabelRect2(20, 80, 20, 20); + QRect bLabelRect3(60, 40, 20, 30); + + QRegion aLabelRegion; + aLabelRegion += aLabelRect1; + aLabelRegion += aLabelRect2; + + QRegion bLabelRegion; + bLabelRegion += bLabelRect1; + bLabelRegion += bLabelRect2; + bLabelRegion += bLabelRect3; + + KisLazyFillGraph g(mainRect, aLabelRegion, bLabelRegion); + + const int numVertices = g.num_vertices(); + const int numEdges = g.num_edges(); + + qDebug() << ppVar(numVertices); + qDebug() << ppVar(numEdges); + + QCOMPARE(numVertices, 10002); + QCOMPARE(numEdges, 43600); + + for (int i = 0; i < numVertices; i++) { + KisLazyFillGraph::vertex_descriptor vertex = g.vertex_at(i); + int newVertexIndex = g.index_of(vertex); + QCOMPARE(newVertexIndex, i); + } + + for (int i = 0; i < numEdges; i++) { + KisLazyFillGraph::edge_descriptor edge = g.edge_at(i); + int newEdgeIndex = g.index_of(edge); + if (i != newEdgeIndex) { + qDebug() << ppVar(edge); + } + QCOMPARE(newEdgeIndex, i); + } + + typedef KisLazyFillGraph::vertex_descriptor Vert; + + Vert v1(10, 10); + Vert v2(11, 10); + Vert v3(10, 11); + Vert v4(11, 11); + Vert v5(12, 10); + Vert v6(31, 21); + Vert v7(61, 61); + Vert v8(9, 10); + + Vert vA(0, 0, Vert::LABEL_A); + Vert vB(0, 0, Vert::LABEL_B); + + // Verify vertex index mapping + + QVERIFY(verifyVertexIndex(g, v1, 0)); + QVERIFY(verifyVertexIndex(g, v2, 1)); + QVERIFY(verifyVertexIndex(g, v3, 100)); + QVERIFY(verifyVertexIndex(g, v4, 101)); + QVERIFY(verifyVertexIndex(g, v5, 2)); + QVERIFY(verifyVertexIndex(g, v6, 1121)); + QVERIFY(verifyVertexIndex(g, v7, 5151)); + QVERIFY(verifyVertexIndex(g, v8, -1)); + + QVERIFY(verifyVertexIndex(g, vA, numVertices - 2)); + QVERIFY(verifyVertexIndex(g, vB, numVertices - 1)); + + // Verify edge index mapping + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v2), 0)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v4), 99)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v3), 19800)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v4), 19801)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v2, v1), 9900)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v3), 9999)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v3, v1), 29700)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v2), 29701)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vB), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v8), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v4), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v4, v1), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, v5), -1)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v6, vA), 39621)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, v6), 40421)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v1, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vA), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, vB), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, vA), -1)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(v7, vB), 41621)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, v7), 42821)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vA), 40000)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 30)), 40800)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 30), vB), -1)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 30)), -1)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vA), 40380)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vA, Vert(70, 49)), 41180)); + + QVERIFY(verifyEdgeIndex(g, std::make_pair(Vert(70, 49), vB), 41390)); + QVERIFY(verifyEdgeIndex(g, std::make_pair(vB, Vert(70, 49)), 42590)); + + QCOMPARE(g.out_degree(v1), long(2)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 0).second, 11, 10)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v1, 1).second, 10, 11)); + + QCOMPARE(g.out_degree(v4), long(4)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 0).second, 10, 11)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 1).second, 11, 10)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 2).second, 12, 11)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v4, 3).second, 11, 12)); + + QCOMPARE(g.out_degree(v6), long(5)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 0).second, 30, 21)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 1).second, 31, 20)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 2).second, 32, 21)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v6, 3).second, 31, 22)); + QCOMPARE(g.out_edge_at(v6, 4).second, vA); + + QCOMPARE(g.out_degree(v7), long(5)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 0).second, 60, 61)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 1).second, 61, 60)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 2).second, 62, 61)); + QVERIFY(verifyNormalVertex(g.out_edge_at(v7, 3).second, 61, 62)); + QCOMPARE(g.out_edge_at(v7, 4).second, vB); + + QCOMPARE(g.out_degree(vA), long(800)); + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 0).second, 30, 20)); + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 1).second, 31, 20)); + + // check second island + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 400).second, 70, 30)); + QVERIFY(verifyNormalVertex(g.out_edge_at(vA, 401).second, 71, 30)); +} + +void KisLazyBrushTest::testGraphStandardIterators() +{ + QRect mainRect(10, 10, 100, 100); + QRect aLabelRect(30, 20, 20, 20); + QRect bLabelRect(60, 60, 20, 20); + + KisLazyFillGraph g(mainRect, aLabelRect, bLabelRect); + + BGL_FORALL_VERTICES(v, g, KisLazyFillGraph) { + int index = g.index_of(v); + QVERIFY(index >= 0); + KisLazyFillGraph::vertex_descriptor newVertex = g.vertex_at(index); + QCOMPARE(newVertex, v); + } + + BGL_FORALL_EDGES(e, g, KisLazyFillGraph) { + int index = g.index_of(e); + QVERIFY(index >= 0); + KisLazyFillGraph::edge_descriptor newEdge = g.edge_at(index); + QCOMPARE(newEdge, e); + } +} + +void KisLazyBrushTest::testGraphConcepts() +{ + BOOST_CONCEPT_ASSERT(( VertexListGraphConcept )); //to have vertices(), num_vertices(), + BOOST_CONCEPT_ASSERT(( EdgeListGraphConcept )); //to have edges() + BOOST_CONCEPT_ASSERT(( IncidenceGraphConcept )); //to have source(), target() and out_edges() + BOOST_CONCEPT_ASSERT(( AdjacencyGraphConcept )); // to have adjacent_vertices(v, g) + BOOST_CONCEPT_ASSERT(( AdjacencyMatrixConcept )); // to have edge(u, v, g) +} + +template +class ComplexCapacityMap +{ + typedef ComplexCapacityMap type; + typedef typename boost::graph_traits::vertex_descriptor VertexDescriptor; + typedef typename boost::graph_traits::edge_descriptor EdgeDescriptor; + +public: + typedef EdgeDescriptor key_type; + typedef float value_type; + typedef const float& reference; + typedef boost::readable_property_map_tag category; + + ComplexCapacityMap(Graph &graph, + const QImage &mainImage, + const QImage &aLabelImage, + const QImage &bLabelImage) + : m_graph(graph), + m_mainImage(mainImage), + m_aLabelImage(aLabelImage), + m_bLabelImage(bLabelImage) + { + const QRgb *bits = reinterpret_cast(m_mainImage.constBits()); + m_minIntensity = std::numeric_limits::max(); + m_maxIntensity = std::numeric_limits::min(); + + const int numPixels = m_mainImage.width() * m_mainImage.height(); + m_intensities.resize(numPixels); + for (int i = 0; i < numPixels; i++) { + const int value = qGray(bits[i]); + m_intensities[i] = value; + + if (m_minIntensity > value) m_minIntensity = value; + if (m_maxIntensity < value) m_maxIntensity = value; + } + + m_pixelSize = 4; + m_rowStride = m_mainImage.width() * m_pixelSize; + } + + friend value_type get(const type &map, + const key_type &key) + { + VertexDescriptor src = source(key, map.m_graph); + VertexDescriptor dst = target(key, map.m_graph); + + bool srcLabelA = src.type == VertexDescriptor::LABEL_A; + bool srcLabelB = src.type == VertexDescriptor::LABEL_B; + bool dstLabelA = dst.type == VertexDescriptor::LABEL_A; + bool dstLabelB = dst.type == VertexDescriptor::LABEL_B; + + if (srcLabelA || srcLabelB) { + std::swap(src, dst); + std::swap(srcLabelA, dstLabelA); + std::swap(srcLabelB, dstLabelB); + } + + Q_ASSERT(!srcLabelA && !srcLabelB); + + const int k = 2 * (map.m_mainImage.width() + map.m_mainImage.height()); + //const int maxDimension = qMax(map.m_mainImage.width(), map.m_mainImage.height()); + + float value = 0.0; + + if (dstLabelA) { + const int xOffset = map.m_aLabelImage.offset().x(); + const int yOffset = map.m_aLabelImage.offset().y(); + + const int x = src.x - xOffset; + const int y = src.y - yOffset; + + //qDebug() << "Label A:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); + + QRgb pixel = map.m_aLabelImage.pixel(x, y); + const int i0 = qAlpha(pixel); + value = i0 / 255.0 * k; + + } else if (dstLabelB) { + const int xOffset = map.m_bLabelImage.offset().x(); + const int yOffset = map.m_bLabelImage.offset().y(); + + const int x = src.x - xOffset; + const int y = src.y - yOffset; + + //qDebug() << "Label B:" << ppVar(src.x) << ppVar(src.y) << ppVar(xOffset) << ppVar(yOffset); + + QRgb pixel = map.m_bLabelImage.pixel(x, y); + const int i0 = qAlpha(pixel); + value = i0 / 255.0 * k; + + } else { + const int xOffset = map.m_mainImage.offset().x(); + const int yOffset = map.m_mainImage.offset().y(); + + const int i0 = map.m_intensities[(src.x - xOffset) + (src.y - xOffset) * map.m_mainImage.width()]; + const int i1 = map.m_intensities[(dst.x - xOffset) + (dst.y - yOffset) * map.m_mainImage.width()]; + + const int diff = qAbs(i0 - i1); + qreal normDiff = qreal(diff) / (map.m_maxIntensity - map.m_minIntensity); + + value = 1.0 + k * (1.0 - normDiff); + } + + return value; + } +private: + Graph &m_graph; + QImage m_mainImage; + QImage m_aLabelImage; + QImage m_bLabelImage; + + QVector m_intensities; + int m_minIntensity; + int m_maxIntensity; + + int m_rowStride; + int m_pixelSize; +}; + +template +ComplexCapacityMap +MakeComplexCapacityMap(Graph &graph, + const QImage &mainImage, + const QImage &aLabelImage, + const QImage &bLabelImage) { + return ComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); +} + +void KisLazyBrushTest::testCutOnGraph() +{ + BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept, KisLazyFillGraph::edge_descriptor> )); + + + QRect mainRect(0, 0, 27, 27); + + const int scribbleASize = 18; + const int scribbleBSize = 19; + const int holeSize = 7; + const int obstacleVOffset = -5; + const int asymVOffset = 1; + + + const int obstacleSize = (mainRect.width() - holeSize) / 2; + QRect o1Rect(0, mainRect.height() / 2 + obstacleVOffset + asymVOffset, obstacleSize, 1); + QRect o2Rect(mainRect.width() - obstacleSize, mainRect.height() / 2 + obstacleVOffset - asymVOffset, obstacleSize, 1); + + QRect aLabelRect(0, 0, scribbleASize, 1); + QRect bLabelRect(3, mainRect.bottom(), scribbleBSize, 1); + + KisLazyFillGraph graph(mainRect, aLabelRect, bLabelRect); + + QImage mainImage(mainRect.size(), QImage::Format_ARGB32); + { + QPainter gc(&mainImage); + gc.fillRect(mainRect, Qt::white); + gc.fillRect(o1Rect, Qt::blue); + gc.fillRect(o2Rect, Qt::blue); + } + + QImage aLabelImage(aLabelRect.size(), QImage::Format_ARGB32); + { + QPainter gc(&aLabelImage); + gc.fillRect(QRect(QPoint(), aLabelRect.size()), Qt::red); + aLabelImage.setOffset(aLabelRect.topLeft()); + + qDebug() << ppVar(aLabelImage.offset()); + } + QImage bLabelImage(bLabelRect.size(), QImage::Format_ARGB32);; + { + QPainter gc(&bLabelImage); + gc.fillRect(QRect(QPoint(), bLabelRect.size()), Qt::red); + bLabelImage.setOffset(bLabelRect.topLeft()); + } + + + std::vector groups(num_vertices(graph), 0); + std::vector residual_capacity(num_edges(graph), 0); + auto capacityMap = MakeComplexCapacityMap(graph, mainImage, aLabelImage, bLabelImage); + + std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); + std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); + + auto vertexIndexMap = get(boost::vertex_index, graph); + + typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; + + VertexDescriptor s(0, 0, VertexDescriptor::LABEL_A); + VertexDescriptor t(0, 0, VertexDescriptor::LABEL_B); + + float maxFlow = + boykov_kolmogorov_max_flow(graph, + capacityMap, + make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), + get(boost::edge_reverse, graph), + make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), + make_iterator_property_map(&groups[0], vertexIndexMap), + make_iterator_property_map(&distance_vec[0], vertexIndexMap), + vertexIndexMap, + s, + t); + + qDebug() << ppVar(maxFlow); + + const int cell = 10; + const int half = cell / 2; + QImage result(cell * mainRect.size(), QImage::Format_ARGB32); + QPainter resultPainter(&result); + + BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { + long vertex_idx = get(boost::vertex_index, graph, v); + int label = groups[vertex_idx]; + + QColor color = + label == 0 ? Qt::blue : + label == 4 ? Qt::green : + label == 1 ? Qt::gray : + Qt::red; + + QRect rc(cell * v.x, cell * v.y, cell, cell); + resultPainter.fillRect(rc, color); + } + + BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { + long egdeIndex = get(boost::edge_index, graph, e); + const int cap = residual_capacity[egdeIndex]; + + + QColor color(Qt::black); + if (cap != 0) { + const int fullCap = get(capacityMap, e); + const int gray = qreal(cap) / fullCap * 50.0; + color.setAlpha(gray); + } + + VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + + QPoint p0(half + cell * src.x, half + cell * src.y); + QPoint p1(half + cell * tgt.x, half + cell * tgt.y); + + resultPainter.setPen(QPen(color, 2)); + resultPainter.drawLine(p0, p1); + + // qDebug() << "(" << src[0] << src[1] << ")" + // << "->" + // << "(" << tgt[0] << tgt[1] << ")" + // << residual_capacity[egdeIndex]; + } + + resultPainter.save(); + resultPainter.setTransform(QTransform::fromScale(cell, cell)); + resultPainter.setBrush(Qt::transparent); + resultPainter.setPen(QPen(Qt::yellow, 0)); + resultPainter.drawRect(o1Rect); + resultPainter.drawRect(o2Rect); + resultPainter.setPen(QPen(Qt::red, 0)); + resultPainter.drawRect(aLabelRect); + resultPainter.drawRect(bLabelRect); + resultPainter.restore(); + + result.save("result.png"); +} + +#include "lazybrush/kis_lazy_fill_capacity_map.h" +#include +#include "testutil.h" +#include + + +void writeColors(KisLazyFillGraph &graph, const std::vector &groups, KisPaintDeviceSP dst) +{ + KisSequentialIterator dstIt(dst, graph.rect()); + + KoColor blue(Qt::blue, dst->colorSpace()); + KoColor green(Qt::red, dst->colorSpace()); + KoColor red(Qt::red, dst->colorSpace()); + KoColor gray(Qt::gray, dst->colorSpace()); + const int pixelSize = dst->colorSpace()->pixelSize(); + + do { + KisLazyFillGraph::vertex_descriptor v(dstIt.x(), dstIt.y()); + long vertex_idx = get(boost::vertex_index, graph, v); + int label = groups[vertex_idx]; + + KoColor &color = + label == 0 ? blue : + label == 4 ? green : + label == 1 ? gray : + red; + + quint8 *dstPtr = dstIt.rawData(); + memcpy(dstPtr, color.data(), pixelSize); + } while (dstIt.nextPixel()); +} + +void writeStat(KisLazyFillGraph &graph, + const std::vector &groups, + const std::vector &residual_capacity, + KisLazyFillCapacityMap &capacityMap) +{ + + typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; + + const int cell = 10; + const int half = cell / 2; + QImage result(cell * graph.size(), QImage::Format_ARGB32); + QPainter resultPainter(&result); + + BGL_FORALL_VERTICES(v, graph, KisLazyFillGraph) { + if (v.type != VertexDescriptor::NORMAL) continue; + + long vertex_idx = get(boost::vertex_index, graph, v); + int label = groups[vertex_idx]; + + QColor color = + label == 0 ? Qt::blue : + label == 4 ? Qt::green : + label == 1 ? Qt::gray : + Qt::red; + + QRect rc(cell * v.x, cell * v.y, cell, cell); + resultPainter.fillRect(rc, color); + } + + BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { + long egdeIndex = get(boost::edge_index, graph, e); + const int cap = residual_capacity[egdeIndex]; + const int fullCap = get(capacityMap, e); + + + QColor color(Qt::red); + if (cap > 0 || fullCap == 0) continue; +/* + if (fullCap != 0) { + const int gray = fullCap / capacityMap.maxCapacity() * 50.0; + color.setAlpha(gray); + } else { + continue; + } +*/ + VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + + if (src.type != VertexDescriptor::NORMAL || + tgt.type != VertexDescriptor::NORMAL) { +/* + VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; + QPoint p0(half + cell * v.x, half + cell * v.y); + + resultPainter.setPen(QPen(color, 4)); + resultPainter.drawEllipse(p0, 0.5 * half, 0.5 * half); +*/ + } else { + QPoint p0(half + cell * src.x, half + cell * src.y); + QPoint p1(half + cell * tgt.x, half + cell * tgt.y); + + resultPainter.setPen(QPen(color, 4)); + resultPainter.drawLine(p0, p1); + } + } + + BGL_FORALL_EDGES(e,graph,KisLazyFillGraph) { + long egdeIndex = get(boost::edge_index, graph, e); + const int cap = residual_capacity[egdeIndex]; + + + QColor color(Qt::black); + if (cap != 0) { + const int fullCap = get(capacityMap, e); + const int gray = qreal(cap) / fullCap * 50.0; + color.setAlpha(gray); + } else { + continue; + } + + VertexDescriptor src = source(e,graph); + VertexDescriptor tgt = target(e,graph); + + if (src.type != VertexDescriptor::NORMAL || + tgt.type != VertexDescriptor::NORMAL) { + + VertexDescriptor &v = src.type != VertexDescriptor::NORMAL ? tgt : src; + QPoint p0(half + cell * v.x, half + cell * v.y); + + resultPainter.setPen(QPen(color, 2)); + resultPainter.drawEllipse(QPointF(p0), 0.5 * half, 0.5 * half); + + } else { + QPoint p0(half + cell * src.x, half + cell * src.y); + QPoint p1(half + cell * tgt.x, half + cell * tgt.y); + + resultPainter.setPen(QPen(color, 2)); + resultPainter.drawLine(p0, p1); + } + } + + resultPainter.save(); + resultPainter.setTransform(QTransform::fromScale(cell, cell)); + resultPainter.setBrush(Qt::transparent); + //resultPainter.setPen(QPen(Qt::yellow, 0)); + //resultPainter.drawRect(o1Rect); + //resultPainter.drawRect(o2Rect); + //resultPainter.setPen(QPen(Qt::red, 0)); + //resultPainter.drawRect(aLabelRect); + //resultPainter.drawRect(bLabelRect); + resultPainter.restore(); + + result.save("result.png"); +} + +#include "kis_paint_device_debug_utils.h" +#include "kis_gaussian_kernel.h" +#include "krita_utils.h" + +KisPaintDeviceSP loadTestImage(const QString &name, bool convertToAlpha) +{ + QImage image(TestUtil::fetchDataFileLazy(name)); + KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + dev->convertFromQImage(image, 0); + + if (convertToAlpha) { + dev = KisPainter::convertToAlphaAsAlpha(dev); + } + + return dev; +} + +#include "lazybrush/kis_lazy_fill_tools.h" +void KisLazyBrushTest::testCutOnGraphDevice() +{ + BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); + + KisPaintDeviceSP mainDev = loadTestImage("fill2_main.png", false); + KisPaintDeviceSP aLabelDev = loadTestImage("fill2_a.png", true); + KisPaintDeviceSP bLabelDev = loadTestImage("fill2_b.png", true); + + KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); + const QRect filterRect = filteredMainDev->exactBounds(); + // KisGaussianKernel::applyLoG(filteredMainDev, + // filterRect, + // 5, + // QBitArray(), 0); + + // KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); + + KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); + + KoColor color(Qt::red, mainDev->colorSpace()); + KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); + KisPaintDeviceSP maskDevice = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); + + maskDevice->fill(QRect(0,0,640,40), KoColor(Qt::gray, maskDevice->colorSpace())); + + KisLazyFillTools::cutOneWay(color, + filteredMainDev, + aLabelDev, + bLabelDev, + resultColoring, + maskDevice, + filterRect); + + KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); + KIS_DUMP_DEVICE_2(maskDevice, filterRect, "01mask", "dd"); + KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); + KIS_DUMP_DEVICE_2(aLabelDev, filterRect, "3aLabel", "dd"); + KIS_DUMP_DEVICE_2(bLabelDev, filterRect, "4bLabel", "dd"); + +#if 0 + KisLazyFillCapacityMap capacityMap(filteredMainDev, aLabelDev, bLabelDev); + KisLazyFillGraph &graph = capacityMap.graph(); + + std::vector groups(num_vertices(graph), 0); + std::vector residual_capacity(num_edges(graph), 0); + + std::vector::vertices_size_type> distance_vec(num_vertices(graph), 0); + std::vector::edge_descriptor> predecessor_vec(num_vertices(graph)); + + auto vertexIndexMap = get(boost::vertex_index, graph); + + typedef KisLazyFillGraph::vertex_descriptor VertexDescriptor; + + VertexDescriptor s(VertexDescriptor::LABEL_A); + VertexDescriptor t(VertexDescriptor::LABEL_B); + + float maxFlow = + boykov_kolmogorov_max_flow(graph, + capacityMap, + make_iterator_property_map(&residual_capacity[0], get(boost::edge_index, graph)), + get(boost::edge_reverse, graph), + make_iterator_property_map(&predecessor_vec[0], vertexIndexMap), + make_iterator_property_map(&groups[0], vertexIndexMap), + make_iterator_property_map(&distance_vec[0], vertexIndexMap), + vertexIndexMap, + s, + t); + + qDebug() << ppVar(maxFlow); + + KisPaintDeviceSP resultColoring = new KisPaintDevice(*mainDev); + writeColors(graph, groups, resultColoring); + + KIS_DUMP_DEVICE_2(resultColoring, graph.rect(), "0result", "dd"); + KIS_DUMP_DEVICE_2(mainDev, graph.rect(), "1main", "dd"); + KIS_DUMP_DEVICE_2(aLabelDev, graph.rect(), "3aLabel", "dd"); + KIS_DUMP_DEVICE_2(bLabelDev, graph.rect(), "4bLabel", "dd"); + + writeStat(graph, + groups, + residual_capacity, + capacityMap); +#endif +} + +#include "lazybrush/kis_multiway_cut.h" + +void KisLazyBrushTest::testCutOnGraphDeviceMulti() +{ + BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept )); + + KisPaintDeviceSP mainDev = loadTestImage("fill4_main.png", false); + KisPaintDeviceSP aLabelDev = loadTestImage("fill4_a.png", true); + KisPaintDeviceSP bLabelDev = loadTestImage("fill4_b.png", true); + KisPaintDeviceSP cLabelDev = loadTestImage("fill4_c.png", true); + KisPaintDeviceSP dLabelDev = loadTestImage("fill4_d.png", true); + KisPaintDeviceSP eLabelDev = loadTestImage("fill4_e.png", true); + + + KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); + const QRect filterRect = filteredMainDev->exactBounds(); + KisGaussianKernel::applyLoG(filteredMainDev, + filterRect, + 2, + QBitArray(), 0); + + KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, filterRect); + + + KisPaintDeviceSP resultColoring = new KisPaintDevice(mainDev->colorSpace()); + + KisMultiwayCut cut(filteredMainDev, resultColoring, filterRect); + + cut.addKeyStroke(aLabelDev, KoColor(Qt::red, mainDev->colorSpace())); + cut.addKeyStroke(bLabelDev, KoColor(Qt::green, mainDev->colorSpace())); + cut.addKeyStroke(cLabelDev, KoColor(Qt::blue, mainDev->colorSpace())); + cut.addKeyStroke(dLabelDev, KoColor(Qt::yellow, mainDev->colorSpace())); + cut.addKeyStroke(eLabelDev, KoColor(Qt::magenta, mainDev->colorSpace())); + + cut.run(); + + + KIS_DUMP_DEVICE_2(resultColoring, filterRect, "00result", "dd"); + KIS_DUMP_DEVICE_2(mainDev, filterRect, "1main", "dd"); + KIS_DUMP_DEVICE_2(filteredMainDev, filterRect, "2filtered", "dd"); +} + +void KisLazyBrushTest::testLoG() +{ + QImage mainImage(TestUtil::fetchDataFileLazy("fill1_main.png")); + QVERIFY(!mainImage.isNull()); + KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + mainDev->convertFromQImage(mainImage, 0); + QRect rect = mainDev->exactBounds(); + + // KisPaintDeviceSP mainDev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + // const QRect rect(0,0,10,10); + // const QRect fillRect(0,0,5,10); + // KoColor bg(Qt::white, mainDev->colorSpace()); + // KoColor fg(Qt::black, mainDev->colorSpace()); + // mainDev->fill(rect, bg); + // mainDev->fill(fillRect, fg); + + KisPaintDeviceSP filteredMainDev = KisPainter::convertToAlphaAsGray(mainDev); + KisGaussianKernel::applyLoG(filteredMainDev, + rect, + 4.0, + QBitArray(), 0); + + KisLazyFillTools::normalizeAndInvertAlpha8Device(filteredMainDev, rect); + + + + KIS_DUMP_DEVICE_2(mainDev, rect, "1main", "dd"); + KIS_DUMP_DEVICE_2(filteredMainDev, rect, "2filtered", "dd"); +} + +void KisLazyBrushTest::testSplitIntoConnectedComponents() +{ + const QRect rc1(10, 10, 10, 10); + const QRect rc2(30, 10, 10, 10); + const QRect boundingRect(0,0,100,100); + + KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + + dev->fill(rc1, KoColor(Qt::red, dev->colorSpace())); + dev->fill(rc2, KoColor(Qt::green, dev->colorSpace())); + + QCOMPARE(dev->exactBounds(), rc1 | rc2); + + QVector points = + KisLazyFillTools::splitIntoConnectedComponents(dev, boundingRect); + + qDebug() << ppVar(points); + + QCOMPARE(points.size(), 2); + QCOMPARE(points[0], QPoint(10,10)); + QCOMPARE(points[1], QPoint(30,10)); +} + +QTEST_MAIN(KisLazyBrushTest) diff --git a/libs/image/tests/kis_scanline_fill_test.h b/libs/image/tests/kis_scanline_fill_test.h --- a/libs/image/tests/kis_scanline_fill_test.h +++ b/libs/image/tests/kis_scanline_fill_test.h @@ -36,6 +36,9 @@ void testFillBackwardCollisionFull(); void testFillBackwardCollisionSanityCheck(); + void testClearNonZeroComponent(); + void testExternalFill(); + private: void testFillGeneral(const QVector &initialBackwardIntervals, const QVector &expectedResult, diff --git a/libs/image/tests/kis_scanline_fill_test.cpp b/libs/image/tests/kis_scanline_fill_test.cpp --- a/libs/image/tests/kis_scanline_fill_test.cpp +++ b/libs/image/tests/kis_scanline_fill_test.cpp @@ -247,4 +247,52 @@ #endif /* ENABLE_FILL_SANITY_CHECKS */ } +void KisScanlineFillTest::testClearNonZeroComponent() +{ + const QRect rc1(10, 10, 10, 10); + const QRect rc2(30, 10, 10, 10); + const QRect boundingRect(0,0,100,100); + + KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + + dev->fill(rc1, KoColor(Qt::red, dev->colorSpace())); + dev->fill(rc2, KoColor(Qt::green, dev->colorSpace())); + + QCOMPARE(dev->exactBounds(), rc1 | rc2); + + KisScanlineFill fill(dev, QPoint(10,10), boundingRect); + fill.clearNonZeroComponent(); + + QCOMPARE(dev->exactBounds(), rc2); +} + +void KisScanlineFillTest::testExternalFill() +{ + const QRect rc1(10, 10, 10, 10); + const QRect rc2(30, 10, 10, 10); + const QRect boundingRect(0,0,100,100); + + KisPaintDeviceSP dev = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + KisPaintDeviceSP other = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); + + dev->fill(rc1, KoColor(Qt::red, dev->colorSpace())); + dev->fill(rc2, KoColor(Qt::green, dev->colorSpace())); + + QCOMPARE(dev->exactBounds(), rc1 | rc2); + + KisScanlineFill fill(dev, QPoint(10,10), boundingRect); + fill.fillColor(KoColor(Qt::blue, dev->colorSpace()), other); + + QCOMPARE(dev->exactBounds(), rc1 | rc2); + QCOMPARE(other->exactBounds(), rc1); + + QColor c; + + dev->pixel(10, 10, &c); + QCOMPARE(c, QColor(Qt::red)); + + other->pixel(10, 10, &c); + QCOMPARE(c, QColor(Qt::blue)); +} + QTEST_MAIN(KisScanlineFillTest) diff --git a/libs/ui/kis_mask_manager.h b/libs/ui/kis_mask_manager.h --- a/libs/ui/kis_mask_manager.h +++ b/libs/ui/kis_mask_manager.h @@ -87,6 +87,7 @@ void createSelectionMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool avoidActiveNode); void createFilterMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool quiet, bool avoidActiveNode); + void createColorizeMask(KisNodeSP activeNode); void createTransformMask(KisNodeSP activeNode); void createTransparencyMask(KisNodeSP activeNode, KisPaintDeviceSP copyFrom, bool avoidActiveNode); diff --git a/libs/ui/kis_mask_manager.cc b/libs/ui/kis_mask_manager.cc --- a/libs/ui/kis_mask_manager.cc +++ b/libs/ui/kis_mask_manager.cc @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -234,6 +235,12 @@ } } +void KisMaskManager::createColorizeMask(KisNodeSP activeNode) +{ + KisMaskSP mask = new KisColorizeMask(); + createMaskCommon(mask, activeNode, 0, kundo2_i18n("Add Colorize Mask"), "KisColorizeMask", i18n("Colorize Mask"), true, false); +} + void KisMaskManager::createTransformMask(KisNodeSP activeNode) { KisTransformMaskSP mask = new KisTransformMask(); diff --git a/libs/ui/kis_node_manager.cpp b/libs/ui/kis_node_manager.cpp --- a/libs/ui/kis_node_manager.cpp +++ b/libs/ui/kis_node_manager.cpp @@ -300,6 +300,8 @@ NEW_LAYER_ACTION("add_new_filter_mask", "KisFilterMask"); + NEW_LAYER_ACTION("add_new_colorize_mask", "KisColorizeMask"); + NEW_LAYER_ACTION("add_new_transform_mask", "KisTransformMask"); NEW_LAYER_ACTION("add_new_selection_mask", "KisSelectionMask"); @@ -486,6 +488,8 @@ m_d->maskManager.createTransparencyMask(activeNode, copyFrom, false); } else if (nodeType == "KisFilterMask") { m_d->maskManager.createFilterMask(activeNode, copyFrom, quiet, false); + } else if (nodeType == "KisColorizeMask") { + m_d->maskManager.createColorizeMask(activeNode); } else if (nodeType == "KisTransformMask") { m_d->maskManager.createTransformMask(activeNode); } else if (nodeType == "KisSelectionMask") { diff --git a/libs/ui/tool/kis_resources_snapshot.h b/libs/ui/tool/kis_resources_snapshot.h --- a/libs/ui/tool/kis_resources_snapshot.h +++ b/libs/ui/tool/kis_resources_snapshot.h @@ -74,6 +74,7 @@ void setOpacity(qreal opacity); quint8 opacity() const; const KoCompositeOp* compositeOp() const; + QString compositeOpId() const; KoPattern* currentPattern() const; KoColor currentFgColor() const; diff --git a/libs/ui/tool/kis_resources_snapshot.cpp b/libs/ui/tool/kis_resources_snapshot.cpp --- a/libs/ui/tool/kis_resources_snapshot.cpp +++ b/libs/ui/tool/kis_resources_snapshot.cpp @@ -277,6 +277,11 @@ return m_d->compositeOp; } +QString KisResourcesSnapshot::compositeOpId() const +{ + return m_d->compositeOpId; +} + KoPattern* KisResourcesSnapshot::currentPattern() const { return m_d->currentPattern; diff --git a/libs/ui/tool/kis_tool.cc b/libs/ui/tool/kis_tool.cc --- a/libs/ui/tool/kis_tool.cc +++ b/libs/ui/tool/kis_tool.cc @@ -70,7 +70,6 @@ #include "kis_tool_utils.h" - struct Q_DECL_HIDDEN KisTool::Private { QCursor cursor; // the cursor that should be shown on tool activation. @@ -651,7 +650,10 @@ if (!node) { return false; } - if (!node->isEditable()) { + + bool nodeEditable = node->isEditable(); + + if (!nodeEditable) { KisCanvas2 * kiscanvas = static_cast(canvas()); QString message; if (!node->visible() && node->userLocked()) { @@ -665,7 +667,7 @@ } kiscanvas->viewManager()->showFloatingMessage(message, KisIconUtils::loadIcon("object-locked")); } - return node->isEditable(); + return nodeEditable; } bool KisTool::selectionEditable() diff --git a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp --- a/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp +++ b/libs/ui/tool/strokes/kis_painter_based_stroke_strategy.cpp @@ -19,6 +19,7 @@ #include "kis_painter_based_stroke_strategy.h" #include +#include #include #include "kis_painter.h" #include "kis_paint_device.h" @@ -189,8 +190,10 @@ if (indirect) { targetDevice = paintDevice->createCompositionSourceDevice(); targetDevice->setParentNode(node); + indirect->setCurrentColor(m_resources->currentFgColor()); indirect->setTemporaryTarget(targetDevice); - indirect->setTemporaryCompositeOp(m_resources->compositeOp()); + + indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(selection); @@ -227,7 +230,6 @@ void KisPainterBasedStrokeStrategy::finishStrokeCallback() { KisNodeSP node = m_resources->currentNode(); - KisLayerSP layer = dynamic_cast(node.data()); KisIndirectPaintingSupport *indirect = dynamic_cast(node.data()); @@ -245,16 +247,16 @@ undoAdapter = dumbUndoAdapter.data(); } - if(layer && indirect && indirect->hasTemporaryTarget()) { + if (indirect && indirect->hasTemporaryTarget()) { KUndo2MagicString transactionText = m_transaction->text(); m_transaction->end(); if(m_useMergeID){ - indirect->mergeToLayer(layer, + indirect->mergeToLayer(node, undoAdapter, transactionText,timedID(this->id())); } else{ - indirect->mergeToLayer(layer, + indirect->mergeToLayer(node, undoAdapter, transactionText); } @@ -304,9 +306,10 @@ dynamic_cast(node.data()); if(indirect) { + // todo: don't ask about paint device if (node->paintDevice() != m_targetDevice) { indirect->setTemporaryTarget(m_targetDevice); - indirect->setTemporaryCompositeOp(m_resources->compositeOp()); + indirect->setTemporaryCompositeOp(m_resources->compositeOpId()); indirect->setTemporaryOpacity(m_resources->opacity()); indirect->setTemporarySelection(m_activeSelection); } diff --git a/plugins/dockers/defaultdockers/kis_layer_box.cpp b/plugins/dockers/defaultdockers/kis_layer_box.cpp --- a/plugins/dockers/defaultdockers/kis_layer_box.cpp +++ b/plugins/dockers/defaultdockers/kis_layer_box.cpp @@ -350,6 +350,7 @@ m_newLayerMenu->addSeparator(); addActionToMenu(m_newLayerMenu, "add_new_transparency_mask"); addActionToMenu(m_newLayerMenu, "add_new_filter_mask"); + addActionToMenu(m_newLayerMenu, "add_new_colorize_mask"); addActionToMenu(m_newLayerMenu, "add_new_transform_mask"); addActionToMenu(m_newLayerMenu, "add_new_selection_mask"); } @@ -558,6 +559,7 @@ menu.addSeparator(); addActionToMenu(&menu, "add_new_transparency_mask"); addActionToMenu(&menu, "add_new_filter_mask"); + addActionToMenu(&menu, "add_new_colorize_mask"); addActionToMenu(&menu, "add_new_transform_mask"); addActionToMenu(&menu, "add_new_selection_mask"); menu.addSeparator(); diff --git a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp --- a/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp +++ b/plugins/tools/basictools/strokes/move_selection_stroke_strategy.cpp @@ -76,7 +76,7 @@ KisIndirectPaintingSupport *indirect = static_cast(m_paintLayer.data()); indirect->setTemporaryTarget(movedDevice); - indirect->setTemporaryCompositeOp(paintDevice->colorSpace()->compositeOp(COMPOSITE_OVER)); + indirect->setTemporaryCompositeOp(COMPOSITE_OVER); indirect->setTemporaryOpacity(OPACITY_OPAQUE_U8); m_initialDeviceOffset = QPoint(movedDevice->x(), movedDevice->y()); @@ -90,7 +90,7 @@ static_cast(m_paintLayer.data()); KisTransaction transaction(name(), m_paintLayer->paintDevice()); - indirect->mergeToLayer(m_paintLayer, (KisUndoAdapter*)0, KUndo2MagicString()); + indirect->mergeToLayer(m_paintLayer, (KisPostExecutionUndoAdapter*)0, KUndo2MagicString()); if (m_undoEnabled) { runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),