Commit e30395b2 authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Added an ability to transform layers recursively with a Transform Tool

Now the transform tool supports transforming of a group of layers or
masks recursively in a single pass. You can use it for transforming a
set of layers grouped into a Group Layer or just transform a single
layer with all its masks (e.g. with transparency masks). This option
is enabled by default, but you can disable it by switching a small
button in the transform tool configuration docker. This button has a
icon of a small spider (the tool "crawls" through the layers :) ) [0].

It would be cool, if someone tested it a bit ;)

[0] - you can suggest any other icon for this, actually =)

BUG:297927,314176
CCMAIL:kimageshop@kde.org
parent da97c156
......@@ -30,23 +30,24 @@ KisProcessingVisitor::ProgressHelper::ProgressHelper(const KisNode *node)
if(progressProxy) {
m_progressUpdater = new KoProgressUpdater(progressProxy);
m_progressUpdater->start();
m_updater = m_progressUpdater->startSubtask();
m_progressUpdater->moveToThread(node->thread());
}
else {
m_progressUpdater = 0;
m_updater = 0;
}
}
KisProcessingVisitor::ProgressHelper::~ProgressHelper()
{
m_progressUpdater->deleteLater();
if (m_progressUpdater) {
m_progressUpdater->deleteLater();
}
}
KoUpdater* KisProcessingVisitor::ProgressHelper::updater() const
{
return m_updater;
QMutexLocker l(&m_progressMutex);
return m_progressUpdater ? m_progressUpdater->startSubtask() : 0;
}
......
......@@ -22,6 +22,8 @@
#include "krita_export.h"
#include "kis_shared.h"
#include <QMutex>
class KisNode;
class KoUpdater;
class KoProgressUpdater;
......@@ -57,7 +59,7 @@ public:
virtual void visit(KisTransparencyMask *mask, KisUndoAdapter *undoAdapter) = 0;
virtual void visit(KisSelectionMask *mask, KisUndoAdapter *undoAdapter) = 0;
protected:
public:
class ProgressHelper {
public:
ProgressHelper(const KisNode *node);
......@@ -66,7 +68,7 @@ protected:
KoUpdater* updater() const;
private:
KoProgressUpdater *m_progressUpdater;
KoUpdater *m_updater;
mutable QMutex m_progressMutex;
};
};
......
......@@ -25,5 +25,6 @@ install(TARGETS kritatooltransform DESTINATION ${PLUGIN_INSTALL_DIR})
install( FILES rotate_cursor.xpm
krita_tool_transform.png
krita_tool_transform_recursive.png
DESTINATION ${DATA_INSTALL_DIR}/krita/pics)
install( FILES kritatooltransform.desktop DESTINATION ${SERVICES_INSTALL_DIR})
......@@ -859,11 +859,11 @@ void KisToolTransform::mousePressEvent(KoPointerEvent *event)
KisImageWSP kisimage = image();
if (!currentNode() || !currentNode()->paintDevice())
if (!currentNode())
return;
setMode(KisTool::PAINT_MODE);
if (kisimage && currentNode()->paintDevice() && event->button() == Qt::LeftButton) {
if (kisimage && event->button() == Qt::LeftButton) {
QPointF mousePos = QPointF(event->point.x() * kisimage->xRes(), event->point.y() * kisimage->yRes());
if (!m_strokeId) {
startStroke(m_currentArgs.mode());
......@@ -1932,8 +1932,7 @@ void KisToolTransform::updateSelectionPath()
if (selection) {
selectionOutline = selection->outline();
} else {
KisPaintDeviceSP dev = currentNode()->paintDevice();
selectionOutline << dev->exactBounds();
selectionOutline << m_selectedPortionCache->exactBounds();
}
const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter();
......@@ -1946,32 +1945,16 @@ void KisToolTransform::updateSelectionPath()
}
}
void KisToolTransform::initThumbnailImage()
void KisToolTransform::initThumbnailImage(KisPaintDeviceSP previewDevice)
{
m_transform = QTransform();
m_origImg = QImage();
m_currImg = QImage();
KisPaintDeviceSP dev;
KisSelectionSP selection = currentSelection();
if (!currentNode() || !(dev = currentNode()->paintDevice())) {
return;
}
QRect srcRect(m_transaction.originalRect().toAlignedRect());
if (selection) {
m_selectedPortionCache = new KisPaintDevice(dev->colorSpace());
KisPainter gc(m_selectedPortionCache);
gc.setSelection(selection);
gc.bitBlt(srcRect.topLeft(), dev, srcRect);
} else {
m_selectedPortionCache = new KisPaintDevice(*dev);
}
m_selectedPortionCache = previewDevice;
const int maxSize = 2000;
QRect srcRect(m_transaction.originalRect().toAlignedRect());
int x, y, w, h;
srcRect.getRect(&x, &y, &w, &h);
......@@ -2039,7 +2022,7 @@ void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode)
KisPaintDeviceSP dev;
if (!currentNode() || !(dev = currentNode()->paintDevice())) {
if (!currentNode()) {
return;
}
......@@ -2058,25 +2041,25 @@ void KisToolTransform::startStroke(ToolTransformArgs::TransformMode mode)
return;
}
KisSelectionSP selection = currentSelection();
m_optWidget->setRecursiveOptionEnabled(false);
m_workRecursively = m_optWidget->workRecursively() ||
!currentNode()->paintDevice();
TransformStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode(), currentSelection(), image()->postExecutionUndoAdapter(), image()->undoAdapter());
KisPaintDeviceSP previewDevice = strategy->previewDevice();
QRectF originalRect;
originalRect = selection ?
selection->selectedExactRect() : dev->exactBounds();
KisSelectionSP selection = currentSelection();
QRect srcRect = selection ? selection->selectedExactRect() : previewDevice->exactBounds();
m_transaction = TransformTransactionProperties(originalRect, &m_currentArgs);
m_transaction = TransformTransactionProperties(srcRect, &m_currentArgs);
initThumbnailImage();
initThumbnailImage(previewDevice);
updateSelectionPath();
initTransformMode(mode);
KisStrokeStrategy *strategy = new TransformStrokeStrategy(currentNode(), currentSelection(), m_selectedPortionCache, image()->postExecutionUndoAdapter(), image()->undoAdapter());
m_strokeId = image()->startStroke(strategy);
image()->addJob(m_strokeId,
new TransformStrokeStrategy::ClearSelectionData());
clearDevices(currentNode(), m_workRecursively);
Q_ASSERT(m_changesTracker.isEmpty());
commitChanges();
......@@ -2087,15 +2070,13 @@ void KisToolTransform::endStroke()
if (!m_strokeId) return;
if (!m_currentArgs.isIdentity()) {
image()->addJob(m_strokeId,
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::PAINT_DEVICE,
m_currentArgs));
transformDevices(currentNode(), m_workRecursively);
image()->addJob(m_strokeId,
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::SELECTION,
m_currentArgs));
m_currentArgs,
currentNode()));
image()->endStroke(m_strokeId);
} else {
......@@ -2104,6 +2085,7 @@ void KisToolTransform::endStroke()
m_strokeId.clear();
m_changesTracker.reset();
m_optWidget->setRecursiveOptionEnabled(true);
}
void KisToolTransform::cancelStroke()
......@@ -2113,6 +2095,7 @@ void KisToolTransform::cancelStroke()
image()->cancelStroke(m_strokeId);
m_strokeId.clear();
m_changesTracker.reset();
m_optWidget->setRecursiveOptionEnabled(true);
}
void KisToolTransform::commitChanges()
......@@ -2128,6 +2111,39 @@ void KisToolTransform::slotTrackerChangedConfig()
updateOptionWidget();
}
void KisToolTransform::clearDevices(KisNodeSP node, bool recursive)
{
if (recursive) {
// simple tail-recursive iteration
KisNodeSP prevNode = node->lastChild();
while(prevNode) {
clearDevices(prevNode, recursive);
prevNode = prevNode->prevSibling();
}
}
image()->addJob(m_strokeId,
new TransformStrokeStrategy::ClearSelectionData(node));
}
void KisToolTransform::transformDevices(KisNodeSP node, bool recursive)
{
if (recursive) {
// simple tail-recursive iteration
KisNodeSP prevNode = node->lastChild();
while(prevNode) {
transformDevices(prevNode, recursive);
prevNode = prevNode->prevSibling();
}
}
image()->addJob(m_strokeId,
new TransformStrokeStrategy::TransformData(
TransformStrokeStrategy::TransformData::PAINT_DEVICE,
m_currentArgs,
node));
}
QWidget* KisToolTransform::createOptionWidget() {
m_optWidget = new KisToolTransformConfigWidget(&m_transaction, m_canvas, 0);
Q_CHECK_PTR(m_optWidget);
......
......@@ -97,6 +97,9 @@ protected:
void requestStrokeCancellation();
private:
void clearDevices(KisNodeSP node, bool recursive);
void transformDevices(KisNodeSP node, bool recursive);
void startStroke(ToolTransformArgs::TransformMode mode);
void endStroke();
void cancelStroke();
......@@ -271,13 +274,9 @@ private:
void initTransformMode(ToolTransformArgs::TransformMode mode);
void initThumbnailImage();
void initThumbnailImage(KisPaintDeviceSP previewDevice);
void updateSelectionPath();
void updateApplyResetAvailability();
void transformDevice(KisPaintDeviceSP device,
KoUpdaterPtr warpUpdater,
KoUpdaterPtr affineUpdater,
KoUpdaterPtr perspectiveUpdater);
private:
enum function {ROTATE = 0, MOVE, RIGHTSCALE, TOPRIGHTSCALE, TOPSCALE, TOPLEFTSCALE,
......@@ -311,6 +310,7 @@ private:
QImage m_currImg; // origImg transformed using m_transform
KisPaintDeviceSP m_selectedPortionCache;
KisStrokeId m_strokeId;
bool m_workRecursively;
QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations
......
......@@ -39,6 +39,7 @@ KisToolTransformConfigWidget::KisToolTransformConfigWidget(TransformTransactionP
{
setupUi(this);
showDecorationsBox->setIcon(koIcon("krita_tool_transform"));
chkWorkRecursively->setIcon(koIcon("krita_tool_transform_recursive.png"));
label_shearX->setPixmap(koIcon("shear_horizontal").pixmap(16, 16));
label_shearY->setPixmap(koIcon("shear_vertical").pixmap(16, 16));
......@@ -272,6 +273,16 @@ void KisToolTransformConfigWidget::resetRotationCenterButtons()
}
}
void KisToolTransformConfigWidget::setRecursiveOptionEnabled(bool value)
{
chkWorkRecursively->setEnabled(value);
}
bool KisToolTransformConfigWidget::workRecursively() const
{
return chkWorkRecursively->isChecked();;
}
void KisToolTransformConfigWidget::setTooBigLabelVisible(bool value)
{
tooBigLabelWidget->setVisible(value);
......
......@@ -39,6 +39,9 @@ public:
void setTooBigLabelVisible(bool value);
bool showDecorations() const;
bool workRecursively() const;
void setRecursiveOptionEnabled(bool value);
public slots:
void updateConfig(const ToolTransformArgs &config);
......
......@@ -32,26 +32,79 @@
#include <kis_warptransform_worker.h>
TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP node,
TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP rootNode,
KisSelectionSP selection,
KisPaintDeviceSP selectedPortionCache,
KisPostExecutionUndoAdapter *undoAdapter,
KisUndoAdapter *legacyUndoAdapter)
: KisStrokeStrategyUndoCommandBased(i18n("Transform Stroke"), false, undoAdapter),
m_node(node),
m_selection(selection),
m_selectedPortionCache(selectedPortionCache),
m_legacyUndoAdapter(legacyUndoAdapter),
m_progressUpdater(0)
m_legacyUndoAdapter(legacyUndoAdapter)
{
Q_ASSERT(m_node);
if (rootNode->childCount() || !rootNode->paintDevice()) {
m_previewDevice = createDeviceCache(rootNode->projection());
} else {
m_previewDevice = createDeviceCache(rootNode->paintDevice());
putDeviceCache(rootNode->paintDevice(), m_previewDevice);
}
Q_ASSERT(m_previewDevice);
}
TransformStrokeStrategy::~TransformStrokeStrategy()
{
if (m_progressUpdater) {
m_progressUpdater->deleteLater();
}
KisPaintDeviceSP TransformStrokeStrategy::previewDevice() const
{
return m_previewDevice;
}
KisPaintDeviceSP TransformStrokeStrategy::createDeviceCache(KisPaintDeviceSP dev)
{
KisPaintDeviceSP cache;
if (m_selection) {
QRect srcRect = m_selection->selectedExactRect();
cache = new KisPaintDevice(dev->colorSpace());
KisPainter gc(cache);
gc.setSelection(m_selection);
gc.bitBlt(srcRect.topLeft(), dev, srcRect);
} else {
cache = new KisPaintDevice(*dev);
}
return cache;
}
bool TransformStrokeStrategy::haveDeviceInCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
return m_devicesCacheHash.contains(src.data());
}
void TransformStrokeStrategy::putDeviceCache(KisPaintDeviceSP src, KisPaintDeviceSP cache)
{
QMutexLocker l(&m_devicesCacheMutex);
m_devicesCacheHash.insert(src.data(), cache);
}
KisPaintDeviceSP TransformStrokeStrategy::getDeviceCache(KisPaintDeviceSP src)
{
QMutexLocker l(&m_devicesCacheMutex);
KisPaintDeviceSP cache = m_devicesCacheHash.value(src.data());
if (!cache) {
qWarning() << "WARNING: Transform Stroke: the device is absent in cache!";
}
return cache;
}
bool TransformStrokeStrategy::checkBelongsToSelection(KisPaintDeviceSP device) const
{
return m_selection &&
(device == m_selection->pixelSelection().data() ||
device == m_selection->projection().data());
}
void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
......@@ -61,20 +114,25 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
if(td) {
if (td->destination == TransformData::PAINT_DEVICE) {
QRect oldExtent = m_node->extent();
QRect oldExtent = td->node->extent();
KisPaintDeviceSP device = td->node->paintDevice();
KisPaintDeviceSP device = m_node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
KisPaintDeviceSP cachedPortion = getDeviceCache(device);
Q_ASSERT(cachedPortion);
KisTransaction transaction("Transform Device", device);
KisTransaction transaction("Transform Device", device);
transformAndMergeDevice(td->config, m_selectedPortionCache,
device);
KisProcessingVisitor::ProgressHelper helper(td->node);
transformAndMergeDevice(td->config, cachedPortion,
device, &helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
m_node->setDirty(oldExtent | m_node->extent());
td->node->setDirty(oldExtent | td->node->extent());
}
} else if (m_selection) {
// FIXME: do it undoable
m_selection->flatten();
......@@ -82,8 +140,10 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
KisSelectionTransaction transaction("Transform Selection", m_legacyUndoAdapter, m_selection);
KisProcessingVisitor::ProgressHelper helper(td->node);
transformDevice(td->config,
m_selection->pixelSelection());
m_selection->pixelSelection(),
&helper);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
......@@ -92,17 +152,20 @@ void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
m_legacyUndoAdapter->emitSelectionChanged();
}
} else if (csd) {
clearSelection();
KisPaintDeviceSP device = csd->node->paintDevice();
if (device && !checkBelongsToSelection(device)) {
if (!haveDeviceInCache(device)) {
putDeviceCache(device, createDeviceCache(device));
}
clearSelection(device);
}
} else {
KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
}
}
void TransformStrokeStrategy::clearSelection()
void TransformStrokeStrategy::clearSelection(KisPaintDeviceSP device)
{
KisPaintDeviceSP device = m_node->paintDevice();
Q_ASSERT(device);
KisTransaction transaction("Clear Selection", device);
if (m_selection) {
device->clearSelection(m_selection);
......@@ -118,11 +181,12 @@ void TransformStrokeStrategy::clearSelection()
void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst)
KisPaintDeviceSP dst,
KisProcessingVisitor::ProgressHelper *helper)
{
KoUpdaterPtr mergeUpdater = src != dst ? fetchUpdater() : 0;
KoUpdaterPtr mergeUpdater = src != dst ? helper->updater() : 0;
transformDevice(config, src);
transformDevice(config, src, helper);
if (src != dst) {
QRect mergeRect = src->extent();
KisPainter painter(dst);
......@@ -132,24 +196,12 @@ void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &c
}
}
KoUpdaterPtr TransformStrokeStrategy::fetchUpdater()
{
QMutexLocker l(&m_progressMutex);
if (!m_progressUpdater) {
KisNodeProgressProxy *progressProxy = m_node->nodeProgressProxy();
m_progressUpdater = new KoProgressUpdater(progressProxy);
m_progressUpdater->moveToThread(m_node->thread());
}
return m_progressUpdater->startSubtask();
}
void TransformStrokeStrategy::transformDevice(const ToolTransformArgs &config,
KisPaintDeviceSP device)
KisPaintDeviceSP device,
KisProcessingVisitor::ProgressHelper *helper)
{
if (config.mode() == ToolTransformArgs::WARP) {
KoUpdaterPtr updater = fetchUpdater();
KoUpdaterPtr updater = helper->updater();
KisWarpTransformWorker worker(config.warpType(),
device,
......@@ -178,8 +230,8 @@ void TransformStrokeStrategy::transformDevice(const ToolTransformArgs &config,
QPointF translation = config.transformedCenter() - transformedCenter.toPointF();
KoUpdaterPtr updater1 = fetchUpdater();
KoUpdaterPtr updater2 = fetchUpdater();
KoUpdaterPtr updater1 = helper->updater();
KoUpdaterPtr updater2 = helper->updater();
KisTransformWorker transformWorker(device,
config.scaleX(), config.scaleY(),
......
......@@ -24,6 +24,7 @@
#include "kis_stroke_strategy_undo_command_based.h"
#include "kis_types.h"
#include "tool_transform_args.h"
#include <kis_processing_visitor.h>
class KisUndoAdapter;
......@@ -42,56 +43,71 @@ public:
};
public:
TransformData(Destination _destination, const ToolTransformArgs &_config)
TransformData(Destination _destination, const ToolTransformArgs &_config, KisNodeSP _node)
: KisStrokeJobData(CONCURRENT, NORMAL),
destination(_destination),
config(_config)
config(_config),
node(_node)
{
}
Destination destination;
ToolTransformArgs config;
KisNodeSP node;
};
class KDE_EXPORT ClearSelectionData : public KisStrokeJobData {
public:
ClearSelectionData()
: KisStrokeJobData(SEQUENTIAL, NORMAL)
ClearSelectionData(KisNodeSP _node)
: KisStrokeJobData(SEQUENTIAL, NORMAL),
node(_node)
{
}
KisNodeSP node;
};
public:
TransformStrokeStrategy(KisNodeSP node,
TransformStrokeStrategy(KisNodeSP rootNode,
KisSelectionSP selection,
KisPaintDeviceSP selectedPortionCache,
KisPostExecutionUndoAdapter *undoAdapter,
KisUndoAdapter *legacyUndoAdapter);
~TransformStrokeStrategy();
KisPaintDeviceSP previewDevice() const;
void doStrokeCallback(KisStrokeJobData *data);
private:
KoUpdaterPtr fetchUpdater();
KoUpdaterPtr fetchUpdater(KisNodeSP node);
void transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst);
KisPaintDeviceSP dst,
KisProcessingVisitor::ProgressHelper *helper);
void transformDevice(const ToolTransformArgs &config,