Commit 18db77f3 authored by Dmitry Kazakov's avatar Dmitry Kazakov

First ready-for-testing version of the Transform Mask functionality

1) Just create a Transform Mask and edit it with usual Transform Tool
2) All the transformation modes are supported.
2.1) Affine transforms (Free and Perspective) have dynamic preview. That
     is you can paint on a layer and the mask will be updated dynamically
2.1) The other types are updated "statically", that is after 3 sec after
     the last change in the original layer.

Question for testers: isn't this 3 sec delay too long? Probably we should
                      make it shorter?

3) The transformation is written into the mask after you click Apply button.
4) The quality of the affine preview and the final transform (after 3 sec
   delay) differs a bit. That is expected.
5) You can also apply a transform mask onto a Clone Layer, which will
   allow you painting in 3D-perspective dynamically.

Please test it in krita-chili-kazakov branch :)

CCMAIL:kimageshop@kde.org
parent 26db43e9
......@@ -132,6 +132,9 @@ set(kritaimage_LIB_SRCS
kis_fill_painter.cc
kis_filter_mask.cpp
kis_filter_strategy.cc
kis_transform_mask.cpp
kis_transform_mask_params_interface.cpp
kis_recalculate_transform_mask_job.cpp
kis_gradient_painter.cc
kis_iterator_ng.cpp
kis_async_merger.cpp
......
......@@ -208,7 +208,7 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
m_currentProjection,
walker.cropRect());
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect);
currentNode->updateProjection(applyRect, KisMergeWalker::convertPositionToFilthy(item.m_position));
continue;
}
......@@ -224,18 +224,18 @@ void KisAsyncMerger::startMerge(KisBaseRectsWalker &walker, bool notifyClones) {
if(item.m_position & KisMergeWalker::N_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY", currentNode, applyRect);
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect);
currentNode->updateProjection(applyRect, KisMergeWalker::convertPositionToFilthy(item.m_position));
}
else if(item.m_position & KisMergeWalker::N_ABOVE_FILTHY) {
DEBUG_NODE_ACTION("Updating", "N_ABOVE_FILTHY", currentNode, applyRect);
if(dependOnLowerNodes(currentNode)) {
currentNode->accept(originalVisitor);
currentNode->updateProjection(applyRect);
currentNode->updateProjection(applyRect, KisMergeWalker::convertPositionToFilthy(item.m_position));
}
}
else if(item.m_position & KisMergeWalker::N_FILTHY_PROJECTION) {
DEBUG_NODE_ACTION("Updating", "N_FILTHY_PROJECTION", currentNode, applyRect);
currentNode->updateProjection(applyRect);
currentNode->updateProjection(applyRect, KisMergeWalker::convertPositionToFilthy(item.m_position));
}
else /*if(item.m_position & KisMergeWalker::N_BELOW_FILTHY)*/ {
DEBUG_NODE_ACTION("Updating", "N_BELOW_FILTHY", currentNode, applyRect);
......
......@@ -58,7 +58,20 @@ public:
};
#define GRAPH_POSITION_MASK 0x07
#define POSITION_TO_FILTHY_MASK 0xF8
static inline KisNode::PositionToFilthy convertPositionToFilthy(NodePosition position) {
static const int positionToFilthyMask =
N_ABOVE_FILTHY |
N_FILTHY_PROJECTION |
N_FILTHY |
N_BELOW_FILTHY;
qint32 positionToFilthy = position & N_EXTRA ? N_FILTHY : position & positionToFilthyMask;
// We do not use N_FILTHY_ORIGINAL yet, so...
Q_ASSERT(positionToFilthy);
return static_cast<KisNode::PositionToFilthy>(positionToFilthy);
}
struct CloneNotification {
CloneNotification() {}
......@@ -189,13 +202,6 @@ protected:
virtual void startTrip(KisNodeSP startWith) = 0;
protected:
static inline KisNode::PositionToFilthy getPositionToFilthy(qint32 position) {
qint32 positionToFilthy = position & POSITION_TO_FILTHY_MASK;
// We do not use N_FILTHY_ORIGINAL yet, so...
Q_ASSERT(!(positionToFilthy & N_FILTHY_ORIGINAL));
return static_cast<KisNode::PositionToFilthy>(positionToFilthy);
}
static inline qint32 getGraphPosition(qint32 position) {
return position & GRAPH_POSITION_MASK;
......@@ -270,7 +276,7 @@ protected:
if(!isLayer(node)) return;
QRect currentChangeRect = node->changeRect(m_resultChangeRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
currentChangeRect = cropThisRect(currentChangeRect);
if(!m_changeRectVaries)
......@@ -279,7 +285,7 @@ protected:
m_resultChangeRect = currentChangeRect;
m_resultUncroppedChangeRect = node->changeRect(m_resultUncroppedChangeRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
registerCloneNotification(node, position);
}
......@@ -320,10 +326,10 @@ protected:
//else /* Why push empty rect? */;
m_resultAccessRect |= node->accessRect(m_lastNeedRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
m_lastNeedRect = node->needRect(m_lastNeedRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
m_childNeedRect = m_lastNeedRect;
}
......@@ -332,10 +338,10 @@ protected:
pushJob(node, position, m_lastNeedRect);
m_resultAccessRect |= node->accessRect(m_lastNeedRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
m_lastNeedRect = node->needRect(m_lastNeedRect,
getPositionToFilthy(position));
convertPositionToFilthy(position));
m_lastNeedRect = cropThisRect(m_lastNeedRect);
}
}
......
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_CACHED_PAINT_DEVICE_H
#define __KIS_CACHED_PAINT_DEVICE_H
#include "tiles3/kis_lockless_stack.h"
class KisCachedPaintDevice
{
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<KisPaintDeviceSP> m_stack;
};
#endif /* __KIS_CACHED_PAINT_DEVICE_H */
......@@ -69,8 +69,11 @@ void KisFilterMask::setFilter(KisFilterConfiguration * filterConfig)
QRect KisFilterMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc) const
const QRect & rc,
PositionToFilthy parentPos) const
{
Q_UNUSED(parentPos);
KisSafeFilterConfigurationSP filterConfig = filter();
Q_ASSERT(nodeProgressProxy());
......
......@@ -60,7 +60,8 @@ public:
QRect decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc) const;
const QRect & rc,
PositionToFilthy parentPos) const;
QRect changeRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
QRect needRect(const QRect &rect, PositionToFilthy pos = N_FILTHY) const;
......
......@@ -389,7 +389,8 @@ QRect KisLayer::masksNeedRect(const QList<KisEffectMaskSP> &masks,
QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
const KisPaintDeviceSP destination,
const QRect &requestedRect) const
const QRect &requestedRect,
PositionToFilthy pos) const
{
Q_ASSERT(source);
Q_ASSERT(destination);
......@@ -434,7 +435,11 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
}
foreach(const KisEffectMaskSP& mask, masks) {
mask->apply(destination, applyRects.pop());
const QRect maskApplyRect = applyRects.pop();
const QRect maskNeedRect =
applyRects.isEmpty() ? needRect : applyRects.top();
mask->apply(destination, maskApplyRect, maskNeedRect, pos);
}
Q_ASSERT(applyRects.isEmpty());
} else {
......@@ -445,10 +450,19 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
*/
KisPaintDeviceSP tempDevice = new KisPaintDevice(colorSpace());
tempDevice->prepareClone(source);
copyOriginalToProjection(source, tempDevice, needRect);
QRect maskApplyRect = applyRects.pop();
QRect maskNeedRect = needRect;
foreach(const KisEffectMaskSP& mask, masks) {
mask->apply(tempDevice, applyRects.pop());
mask->apply(tempDevice, maskApplyRect, maskNeedRect, pos);
if (!applyRects.isEmpty()) {
maskNeedRect = maskApplyRect;
maskApplyRect = applyRects.pop();
}
}
Q_ASSERT(applyRects.isEmpty());
......@@ -461,7 +475,7 @@ QRect KisLayer::applyMasks(const KisPaintDeviceSP source,
return changeRect;
}
QRect KisLayer::updateProjection(const QRect& rect)
QRect KisLayer::updateProjection(const QRect& rect, PositionToFilthy pos)
{
QRect updatedRect = rect;
KisPaintDeviceSP originalDevice = original();
......@@ -478,7 +492,7 @@ QRect KisLayer::updateProjection(const QRect& rect)
m_d->safeProjection.getDeviceLazy(originalDevice);
updatedRect = applyMasks(originalDevice, projection,
updatedRect);
updatedRect, pos);
}
}
......
......@@ -83,7 +83,7 @@ public:
* Ask the layer to assemble its data & apply all the effect masks
* to it.
*/
virtual QRect updateProjection(const QRect& rect);
virtual QRect updateProjection(const QRect& rect, PositionToFilthy pos);
virtual bool needProjection() const;
......@@ -275,7 +275,8 @@ protected:
QRect applyMasks(const KisPaintDeviceSP source,
const KisPaintDeviceSP destination,
const QRect &requestedRect) const;
const QRect &requestedRect,
PositionToFilthy pos) const;
private:
struct Private;
......
......@@ -38,35 +38,14 @@
#include "kis_image.h"
#include "kis_layer.h"
#include "tiles3/kis_lockless_stack.h"
#include "kis_cached_paint_device.h"
struct KisMask::Private {
class CachedPaintDevice {
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<KisPaintDeviceSP> m_stack;
};
struct KisMask::Private {
Private(KisMask *_q) : q(_q) {}
mutable KisSelectionSP selection;
CachedPaintDevice paintDeviceCache;
KisCachedPaintDevice paintDeviceCache;
KisMask *q;
/**
......@@ -231,30 +210,32 @@ void KisMask::select(const QRect & rc, quint8 selectedness)
QRect KisMask::decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc) const
const QRect & rc,
PositionToFilthy parentPos) const
{
Q_UNUSED(src);
Q_UNUSED(dst);
Q_UNUSED(parentPos);
Q_ASSERT_X(0, "KisMask::decorateRect", "Should be overridden by successors");
return rc;
}
void KisMask::apply(KisPaintDeviceSP projection, const QRect & rc) const
void KisMask::apply(KisPaintDeviceSP projection, const QRect &applyRect, const QRect &needRect, PositionToFilthy parentPos) const
{
if (selection()) {
m_d->selection->updateProjection(rc);
m_d->selection->updateProjection(applyRect);
if(!extent().intersects(rc))
if(!extent().intersects(applyRect))
return;
KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
QRect updatedRect = decorateRect(projection, cacheDevice, rc);
QRect updatedRect = decorateRect(projection, cacheDevice, applyRect, parentPos);
KisPainter gc(projection);
gc.setCompositeOp(compositeOp());
gc.setOpacity(opacity());
// masks don't have any compositioning
gc.setCompositeOp(COMPOSITE_COPY);
gc.setSelection(m_d->selection);
gc.bitBlt(updatedRect.topLeft(), cacheDevice, updatedRect);
......@@ -262,11 +243,11 @@ void KisMask::apply(KisPaintDeviceSP projection, const QRect & rc) const
} else {
KisPaintDeviceSP cacheDevice = m_d->paintDeviceCache.getDevice(projection);
cacheDevice->makeCloneFromRough(projection, rc);
projection->clear(rc);
// FIXME: how about opacity and compositeOp?
decorateRect(cacheDevice, projection, rc);
cacheDevice->makeCloneFromRough(projection, needRect);
projection->clear(needRect);
decorateRect(cacheDevice, projection, applyRect, parentPos);
m_d->paintDeviceCache.putDevice(cacheDevice);
}
......
......@@ -178,10 +178,11 @@ protected:
* Apply the effect the projection using the mask as a selection.
* Made public in KisEffectMask
*/
virtual void apply(KisPaintDeviceSP projection, const QRect & rc) const;
void apply(KisPaintDeviceSP projection, const QRect & applyRect, const QRect & needRect, PositionToFilthy parentPos) const;
virtual QRect decorateRect(KisPaintDeviceSP &src,
KisPaintDeviceSP &dst,
const QRect & rc) const;
const QRect & rc,
PositionToFilthy parentPos) const;
private:
......
......@@ -24,6 +24,12 @@
#include <QMatrix4x4>
#include <QTransform>
#include <QVector3D>
#include <QPolygonF>
#include <KoProgressUpdater.h>
#include <KoUpdater.h>
#include <KoColor.h>
#include <KoCompositeOpRegistry.h>
#include "kis_paint_device.h"
#include "kis_perspective_math.h"
......@@ -33,10 +39,8 @@
#include <kis_iterator_ng.h>
#include "krita_utils.h"
#include "kis_progress_update_helper.h"
#include "kis_painter.h"
#include <KoProgressUpdater.h>
#include <KoUpdater.h>
#include <KoColor.h>
KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress)
: m_dev(dev), m_progressUpdater(progress)
......@@ -60,21 +64,38 @@ KisPerspectiveTransformWorker::KisPerspectiveTransformWorker(KisPaintDeviceSP de
init(transform);
}
void KisPerspectiveTransformWorker::init(const QTransform &transform)
void KisPerspectiveTransformWorker::fillParams(const QRectF &srcRect,
const QRect &dstBaseClipRect,
QRegion *dstRegion,
QPolygonF *dstClipPolygon)
{
QPolygon bounds(m_dev->exactBounds());
QPolygon newBounds = transform.map(bounds);
QPolygonF bounds = srcRect;
QPolygonF newBounds = m_forwardTransform.map(bounds);
newBounds = newBounds.intersected(QRectF(dstBaseClipRect));
QPainterPath path;
path.addPolygon(newBounds);
*dstRegion = KritaUtils::splitPath(path);
*dstClipPolygon = newBounds;
}
void KisPerspectiveTransformWorker::init(const QTransform &transform)
{
m_isIdentity = transform.isIdentity();
if (!m_isIdentity && transform.isInvertible()) {
m_newTransform = transform.inverted();
m_forwardTransform = transform;
m_backwardTransform = transform.inverted();
if (m_dev) {
m_srcRect = m_dev->exactBounds();
newBounds = newBounds.intersected(m_dev->defaultBounds()->bounds());
QPainterPath path;
path.addPolygon(newBounds);
m_dstRegion = KritaUtils::splitPath(path);
QPolygonF dstClipPolygonUnused;
fillParams(m_srcRect,
m_dev->defaultBounds()->bounds(),
&m_dstRegion,
&dstClipPolygonUnused);
}
}
......@@ -82,11 +103,16 @@ KisPerspectiveTransformWorker::~KisPerspectiveTransformWorker()
{
}
void KisPerspectiveTransformWorker::setForwardTransform(const QTransform &transform)
{
init(transform);
}
void KisPerspectiveTransformWorker::run()
{
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, m_dstRegion.rectCount());
if (m_isIdentity) return;
KIS_ASSERT_RECOVER_RETURN(m_dev);
if (m_isIdentity) return;
KisPaintDeviceSP cloneDevice = new KisPaintDevice(*m_dev.data());
......@@ -94,7 +120,11 @@ void KisPerspectiveTransformWorker::run()
// shared with cloneDevice
m_dev->clear();
KisRandomSubAccessorSP srcAcc = cloneDevice->createRandomSubAccessor();
KIS_ASSERT_RECOVER_NOOP(!m_isIdentity);
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, m_dstRegion.rectCount());
KisRandomSubAccessorSP srcAcc = m_dev->createRandomSubAccessor();
KisRandomAccessorSP accessor = m_dev->createRandomAccessorNG(0, 0);
foreach(const QRect &rect, m_dstRegion.rects()) {
......@@ -102,19 +132,60 @@ void KisPerspectiveTransformWorker::run()
for (int x = rect.x(); x < rect.x() + rect.width(); ++x) {
QPointF dstPoint(x, y);
QPointF srcPoint = m_newTransform.map(dstPoint);
QPointF srcPoint = m_backwardTransform.map(dstPoint);
if (m_srcRect.contains(srcPoint)) {
accessor->moveTo(dstPoint.x(), dstPoint.y());
srcAcc->moveTo(srcPoint.x(), srcPoint.y());
srcAcc->sampledOldRawData(accessor->rawData());
}
}
}
progressHelper.step();
}
}
void KisPerspectiveTransformWorker::runPartialDst(KisPaintDeviceSP srcDev,
KisPaintDeviceSP dstDev,
const QRect &dstRect)
{
if (m_isIdentity) {
KisPainter gc(dstDev);
gc.setCompositeOp(COMPOSITE_COPY);
gc.bitBltOldData(dstRect.topLeft(), srcDev, dstRect);
return;
}
QRectF srcClipRect = srcDev->defaultBounds()->bounds();
KisProgressUpdateHelper progressHelper(m_progressUpdater, 100, dstRect.height());
KisRandomSubAccessorSP srcAcc = srcDev->createRandomSubAccessor();
KisRandomAccessorSP accessor = dstDev->createRandomAccessorNG(0, 0);
for (int y = dstRect.y(); y < dstRect.y() + dstRect.height(); ++y) {
for (int x = dstRect.x(); x < dstRect.x() + dstRect.width(); ++x) {
QPointF dstPoint(x, y);
QPointF srcPoint = m_backwardTransform.map(dstPoint);
if (srcClipRect.contains(srcPoint)) {
accessor->moveTo(dstPoint.x(), dstPoint.y());
srcAcc->moveTo(srcPoint.x(), srcPoint.y());
srcAcc->sampledOldRawData(accessor->rawData());
}
}
progressHelper.step();
}
}
#include "kis_perspectivetransform_worker.moc"
QTransform KisPerspectiveTransformWorker::forwardTransform() const
{
return m_forwardTransform;
}
QTransform KisPerspectiveTransformWorker::backwardTransform() const
{
return m_backwardTransform;
}
......@@ -30,11 +30,8 @@
#include <KoUpdater.h>
class KRITAIMAGE_EXPORT KisPerspectiveTransformWorker : public QObject
class KRITAIMAGE_EXPORT KisPerspectiveTransformWorker
{
Q_OBJECT
public:
KisPerspectiveTransformWorker(KisPaintDeviceSP dev, QPointF center, double aX, double aY, double distance, KoUpdaterPtr progress);
KisPerspectiveTransformWorker(KisPaintDeviceSP dev, const QTransform &transform, KoUpdaterPtr progress);
......@@ -42,16 +39,30 @@ public:
~KisPerspectiveTransformWorker();
void run();
void runPartialDst(KisPaintDeviceSP srcDev,
KisPaintDeviceSP dstDev,
const QRect &dstRect);
void setForwardTransform(const QTransform &transform);
QTransform forwardTransform() const;
QTransform backwardTransform() const;
private:
void init(const QTransform &transform);
void fillParams(const QRectF &srcRect,
const QRect &dstBaseClipRect,
QRegion *dstRegion,
QPolygonF *dstClipPolygon);
private:
KisPaintDeviceSP m_dev;
KoUpdaterPtr m_progressUpdater;
QRegion m_dstRegion;
QRectF m_srcRect;
QTransform m_newTransform;
QTransform m_backwardTransform;
QTransform m_forwardTransform;
bool m_isIdentity;
};
......
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kis_recalculate_transform_mask_job.h"
#include "kis_transform_mask.h"
KisRecalculateTransformMaskJob::KisRecalculateTransformMaskJob(KisTransformMaskSP mask)
: m_mask(mask)
{
}
bool KisRecalculateTransformMaskJob::overrides(const KisSpontaneousJob *_otherJob)
{
const KisRecalculateTransformMaskJob *otherJob =
dynamic_cast<const KisRecalculateTransformMaskJob*>(_otherJob);
return otherJob && otherJob->m_mask == m_mask;
}
void KisRecalculateTransformMaskJob::run()
{
m_mask->recaclulateStaticImage();
m_mask->setDirty();
}
/*
* Copyright (c) 2014 Dmitry Kazakov <dimula73@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __KIS_RECALCULATE_TRANSFORM_MASK_JOB_H
#define __KIS_RECALCULATE_TRANSFORM_MASK_JOB_H
#include "kis_types.h"
#include "kis_spontaneous_job.h"