Commit 0e3a6bea authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Ported Transform Tool to strokes

There is one regression: the actions inside a single transform stroke
(that is until you applied the transformation) cannot be undone yet.

The undo of the transformation as a whole works perfectly.

BUG:251749,313294,310559,297929
CCBUG:297927
parent 2b5d7c11
......@@ -21,12 +21,11 @@
#include "kis_selection.h"
#include "kis_pixel_selection.h"
#include "kis_image.h"
#include "kis_undo_adapter.h"
KisSelectionTransactionData::KisSelectionTransactionData(const QString& name, KisImageWSP image, KisSelectionSP selection, KUndo2Command* parent) :
KisSelectionTransactionData::KisSelectionTransactionData(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent) :
KisTransactionData(name, selection->getOrCreatePixelSelection().data(), parent)
, m_image(image)
, m_undoAdapter(undoAdapter)
, m_selection(selection)
{
}
......@@ -38,17 +37,15 @@ KisSelectionTransactionData::~KisSelectionTransactionData()
void KisSelectionTransactionData::redo()
{
KisTransactionData::redo();
m_selection->setDirty(m_image->bounds());
m_selection->updateProjection();
m_image->undoAdapter()->emitSelectionChanged();
m_undoAdapter->emitSelectionChanged();
}
void KisSelectionTransactionData::undo()
{
KisTransactionData::undo();
m_selection->setDirty(m_image->bounds());
m_selection->updateProjection();
m_image->undoAdapter()->emitSelectionChanged();
m_undoAdapter->emitSelectionChanged();
}
......@@ -24,6 +24,8 @@
#include "kis_selection.h"
class KisUndoAdapter;
/**
* KisSelectionTransactionData records changes to the selection for the undo stack. There
* are two selections in Krita: the global selection and the per-layer selection mask.
......@@ -36,7 +38,7 @@ class KRITAIMAGE_EXPORT KisSelectionTransactionData : public KisTransactionData
{
public:
KisSelectionTransactionData(const QString& name, KisImageWSP image, KisSelectionSP selection, KUndo2Command* parent = 0);
KisSelectionTransactionData(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent = 0);
virtual ~KisSelectionTransactionData();
public:
......@@ -44,7 +46,7 @@ public:
void undo();
private:
KisImageWSP m_image;
KisUndoAdapter *m_undoAdapter;
KisSelectionSP m_selection;
};
......
......@@ -127,9 +127,9 @@ public:
class KisSelectionTransaction : public KisTransaction
{
public:
KisSelectionTransaction(const QString& name, KisImageWSP image, KisSelectionSP selection, KUndo2Command* parent = 0)
KisSelectionTransaction(const QString& name, KisUndoAdapter *undoAdapter, KisSelectionSP selection, KUndo2Command* parent = 0)
{
m_transactionData = new KisSelectionTransactionData(name, image, selection, parent);
m_transactionData = new KisSelectionTransactionData(name, undoAdapter, selection, parent);
}
};
......
......@@ -167,7 +167,7 @@ private:
void transformMask(KisMask* mask) {
KisSelectionSP selection = mask->selection();
if(selection->hasPixelSelection() && !m_scaleOnlyShapes) {
KisSelectionTransaction transaction(QString(), m_image, selection);
KisSelectionTransaction transaction(QString(), m_image->undoAdapter(), selection);
KisPaintDeviceSP dev = selection->getOrCreatePixelSelection().data();
KisTransformWorker tw(dev, m_sx, m_sy, 0.0, 0.0, 0.0, 0.0, m_angle, m_tx, m_ty, m_progress, m_filter);
......
......@@ -207,7 +207,7 @@ void ImageSize::slotSelectionScale()
QPointer<KoUpdater> u = pu->startSubtask();
if (dlgSize->exec() == QDialog::Accepted) {
KisSelectionTransaction transaction(i18n("Scale Selection"), image, selection);
KisSelectionTransaction transaction(i18n("Scale Selection"), image->undoAdapter(), selection);
qint32 w = dlgSize->width();
qint32 h = dlgSize->height();
......
......@@ -18,6 +18,7 @@
#include "move_selection_stroke_strategy.h"
#include <klocale.h>
#include <KoColorSpace.h>
#include "kis_image.h"
#include "kis_paint_layer.h"
......@@ -30,7 +31,7 @@ MoveSelectionStrokeStrategy::MoveSelectionStrokeStrategy(KisPaintLayerSP paintLa
KisSelectionSP selection,
KisUpdatesFacade *updatesFacade,
KisPostExecutionUndoAdapter *undoAdapter)
: KisStrokeStrategyUndoCommandBased("Move Selection Stroke", false, undoAdapter),
: KisStrokeStrategyUndoCommandBased(i18n("Move Selection Stroke"), false, undoAdapter),
m_paintLayer(paintLayer),
m_selection(selection),
m_updatesFacade(updatesFacade),
......
......@@ -18,6 +18,7 @@
#include "move_stroke_strategy.h"
#include <klocale.h>
#include "kis_image_interfaces.h"
#include "kis_node.h"
#include "commands_new/kis_update_command.h"
......@@ -28,7 +29,7 @@ MoveStrokeStrategy::MoveStrokeStrategy(KisNodeSP node,
KisUpdatesFacade *updatesFacade,
KisPostExecutionUndoAdapter *undoAdapter,
KisUndoAdapter *legacyUndoAdapter)
: KisStrokeStrategyUndoCommandBased("Move stroke", false, undoAdapter),
: KisStrokeStrategyUndoCommandBased(i18n("Move Stroke"), false, undoAdapter),
m_node(node),
m_updatesFacade(updatesFacade),
m_legacyUndoAdapter(legacyUndoAdapter)
......
......@@ -6,6 +6,7 @@ set(kritatooltransform_PART_SRCS
tool_transform_commands.cc
kis_tool_transform.cc
kis_tool_transform_config_widget.cpp
strokes/transform_stroke_strategy.cpp
)
......
......@@ -66,7 +66,7 @@ class KisCanvas2;
* until the user click that button is only a preview.
*/
class KisToolTransform : public KisTool, KisCommandHistoryListener
class KisToolTransform : public KisTool
{
Q_OBJECT
......@@ -83,18 +83,22 @@ public:
virtual void keyPressEvent(QKeyEvent *event);
virtual void keyReleaseEvent(QKeyEvent *event);
virtual void resourceChanged(int key, const QVariant& res);
public:
void paint(QPainter& gc, const KoViewConverter &converter);
void notifyCommandAdded(const KUndo2Command *);
void notifyCommandExecuted(const KUndo2Command *);
public slots:
virtual void activate(ToolActivation toolActivation, const QSet<KoShape*> &shapes);
virtual void deactivate();
protected:
void requestStrokeEnd();
void requestStrokeCancellation();
private:
void startStroke(ToolTransformArgs::TransformMode mode);
void endStroke();
void cancelStroke();
private:
// Used in dichotomic search (see below)
typedef enum {NONE, XCOORD, YCOORD} DICHO_DROP;
......@@ -138,11 +142,11 @@ private:
if (!m_currentArgs.aX() && !m_currentArgs.aY())
return QPointF(x, y);
QVector3D t = QVector3D(x, y, z) - m_cameraPos;
QVector3D t = QVector3D(x, y, z) - m_currentArgs.cameraPos();
if (t.z() == 0.0)
return QPointF(0,0);
return QPointF((t.x() - m_eyePos.x()) * m_eyePos.z() / t.z(), (t.y() - m_eyePos.y()) * m_eyePos.z() / t.z());
return QPointF((t.x() - m_currentArgs.eyePos().x()) * m_currentArgs.eyePos().z() / t.z(), (t.y() - m_currentArgs.eyePos().y()) * m_currentArgs.eyePos().z() / t.z());
}
// The perspective is only invertible if the plane into which the returned point should be is given
QVector3D invperspective(double x, double y, QVector3D plan) {
......@@ -151,8 +155,8 @@ private:
// y = (yinv - cy)*ez / (zinv - cz)
// a*xinv + b*yinv + c*zinv = 0
// where :
// (cx, cy, cz) = (m_cameraPos.x() + m_eyePos.x(), m_cameraPos.y() + m_eyePos.y(), m_cameraPos.z())
// ez = m_eyePos
// (cx, cy, cz) = (m_currentArgs.cameraPos().z().x() + m_currentArgs.eyePos().x(), m_currentArgs.cameraPos().z().y() + m_currentArgs.eyePos().y(), m_currentArgs.cameraPos().z().z())
// ez = m_currentArgs.eyePos()
// (a, b, c) = plan
if (!m_currentArgs.aX() && !m_currentArgs.aY())
return QVector3D(x, y, 0);
......@@ -160,15 +164,15 @@ private:
double a = plan.x();
double b = plan.y();
double c = plan.z();
double denom = a*x + b*y + c*m_eyePos.z();
double denom = a*x + b*y + c*m_currentArgs.eyePos().z();
if (!denom)
return QVector3D(0, 0, 0);
double cx = (m_cameraPos.x() - m_eyePos.x());
double cy = (m_cameraPos.y() - m_eyePos.y());
double cz = m_cameraPos.z();
double ez = m_eyePos.z();
double cx = (m_currentArgs.cameraPos().x() - m_currentArgs.eyePos().x());
double cy = (m_currentArgs.cameraPos().y() - m_currentArgs.eyePos().y());
double cz = m_currentArgs.cameraPos().z();
double ez = m_currentArgs.eyePos().z();
double acx = a * cx;
double bcy = b * cy;
double ccz = c * cz;
......@@ -255,19 +259,13 @@ private:
double dichotomyScaleY(QVector3D v1, QVector3D v2, DICHO_DROP flag, double desired, double b, double precision, double maxIterations1, double maxIterations2);
// If p is inside r, p is returned, otherwise the returned point is the intersection of the line given by vector p, and the rectangle
inline QPointF clipInRect(QPointF p, QRectF r);
void initFreeTransform();
// Sets the default control points as a grid of density pointsPerLine. If pointsPerLine < 0, m_defaultPointsPerLine is used for density instead
void setDefaultWarpPoints(int pointsPerLine = -1);
void initWarpTransform();
// Saves the original selection, paintDevice, image previews, and initializes the transformation depending on the mode given in argument
void initTransform(ToolTransformArgs::TransformMode mode);
// Only commits the changes made on the preview to the undo stack
void transform();
// Applies the current transformation to the original paint device and commits it to the undo stack
void applyTransform();
// Updated the widget according to m_currentArgs
void updateOptionWidget();
void initTransformMode(ToolTransformArgs::TransformMode mode);
void initThumbnailImage();
void updateSelectionPath();
void updateApplyResetAvailability();
......@@ -300,11 +298,14 @@ private:
QImage m_origImg; // image of the pixels in selection bound rect
QTransform m_transform; // transformation to apply on origImg
QTransform m_thumbToImageTransform;
QTransform m_handlesTransform;
QTransform m_paintingTransform;
QPointF m_paintingOffset;
QImage m_currImg; // origImg transformed using m_transform
KisPaintDeviceSP m_selectedPortionCache;
KisStrokeId m_strokeId;
QPainterPath m_selectionPath; // original (unscaled) selection outline, used for painting decorations
......@@ -356,7 +357,6 @@ private:
QPointF m_clickPoint; //position of the mouse when click occurred
// 'Free-transform'-related :
QVector3D m_cameraPos, m_eyePos;
QVector3D m_currentPlane, m_clickPlane; // vector (a, b, c) represents the vect plane a*x + b*y + c*z = 0
double m_cosaZ; // cos of currentArgs.aZ()
double m_sinaZ;
......
......@@ -199,8 +199,8 @@ void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config)
scaleYBox->setValue(config.scaleY() * 100.);
shearXBox->setValue(config.shearX());
shearYBox->setValue(config.shearY());
translateXBox->setValue(config.translate().x());
translateYBox->setValue(config.translate().y());
translateXBox->setValue(config.transformedCenter().x());
translateYBox->setValue(config.transformedCenter().y());
aXBox->setValue(radianToDegree(config.aX()));
aYBox->setValue(radianToDegree(config.aY()));
aZBox->setValue(radianToDegree(config.aZ()));
......@@ -418,7 +418,7 @@ void KisToolTransformConfigWidget::slotSetTranslateX(double value)
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setTranslate(QPointF(value, config->translate().y()));
config->setTransformedCenter(QPointF(value, config->transformedCenter().y()));
notifyConfigChanged();
}
......@@ -427,7 +427,7 @@ void KisToolTransformConfigWidget::slotSetTranslateY(double value)
if (m_uiSlotsBlocked) return;
ToolTransformArgs *config = m_transaction->currentConfig();
config->setTranslate(QPointF(config->translate().x(), value));
config->setTransformedCenter(QPointF(config->transformedCenter().x(), value));
notifyConfigChanged();
}
......
......@@ -35,6 +35,7 @@ public:
void setApplyResetDisabled(bool disabled);
void resetRotationCenterButtons();
void setDefaultWarpPoints(int pointsPerLine = -1);
public slots:
void updateConfig(const ToolTransformArgs &config);
......@@ -92,7 +93,6 @@ private:
void blockUiSlots();
void unblockUiSlots();
void setDefaultWarpPoints(int pointsPerLine);
void activateCustomWarpPoints(bool enabled);
void updateLockPointsButtonCaption();
......
/*
* Copyright (c) 2013 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 "transform_stroke_strategy.h"
#include <QMutexLocker>
#include <KoProgressUpdater.h>
#include "kis_node_progress_proxy.h"
#include <klocale.h>
#include <kis_node.h>
#include <kis_transaction.h>
#include <kis_painter.h>
#include <kis_transform_worker.h>
#include <kis_perspectivetransform_worker.h>
#include <kis_warptransform_worker.h>
TransformStrokeStrategy::TransformStrokeStrategy(KisNodeSP node,
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)
{
Q_ASSERT(m_node);
}
TransformStrokeStrategy::~TransformStrokeStrategy()
{
if (m_progressUpdater) {
m_progressUpdater->deleteLater();
}
}
void TransformStrokeStrategy::doStrokeCallback(KisStrokeJobData *data)
{
TransformData *td = dynamic_cast<TransformData*>(data);
ClearSelectionData *csd = dynamic_cast<ClearSelectionData*>(data);
if(td) {
if (td->destination == TransformData::PAINT_DEVICE) {
QRect oldExtent = m_node->extent();
KisPaintDeviceSP device = m_node->paintDevice();
KisTransaction transaction("Transform Device", device);
transformAndMergeDevice(td->config, m_selectedPortionCache,
device);
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
m_node->setDirty(oldExtent | m_node->extent());
} else if (m_selection) {
// FIXME: do it undoable
m_selection->flatten();
Q_ASSERT(m_selection->pixelSelection());
KisSelectionTransaction transaction("Transform Selection", m_legacyUndoAdapter, m_selection);
transformDevice(td->config,
m_selection->pixelSelection());
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::CONCURRENT,
KisStrokeJobData::NORMAL);
m_legacyUndoAdapter->emitSelectionChanged();
}
} else if (csd) {
clearSelection();
} else {
KisStrokeStrategyUndoCommandBased::doStrokeCallback(data);
}
}
void TransformStrokeStrategy::clearSelection()
{
KisPaintDeviceSP device = m_node->paintDevice();
Q_ASSERT(device);
KisTransaction transaction("Clear Selection", device);
if (m_selection) {
device->clearSelection(m_selection);
} else {
QRect oldExtent = device->extent();
device->clear();
device->setDirty(oldExtent);
}
runAndSaveCommand(KUndo2CommandSP(transaction.endAndTake()),
KisStrokeJobData::SEQUENTIAL,
KisStrokeJobData::NORMAL);
}
void TransformStrokeStrategy::transformAndMergeDevice(const ToolTransformArgs &config,
KisPaintDeviceSP src,
KisPaintDeviceSP dst)
{
KoUpdaterPtr mergeUpdater = src != dst ? fetchUpdater() : 0;
transformDevice(config, src);
if (src != dst) {
QRect mergeRect = src->extent();
KisPainter painter(dst);
painter.setProgress(mergeUpdater);
painter.bitBlt(mergeRect.topLeft(), src, mergeRect);
painter.end();
}
}
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)
{
if (config.mode() == ToolTransformArgs::WARP) {
KoUpdaterPtr updater = fetchUpdater();
KisWarpTransformWorker worker(config.warpType(),
device,
config.origPoints(),
config.transfPoints(),
config.alpha(),
updater);
worker.run();
} else {
QVector3D transformedCenter;
{
KisTransformWorker t(0,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
0, // set X and Y translation
0, // to null for calculation
0,
config.filter());
transformedCenter = QVector3D(t.transform().map(config.originalCenter()));
}
QPointF translation = config.transformedCenter() - transformedCenter.toPointF();
KoUpdaterPtr updater1 = fetchUpdater();
KoUpdaterPtr updater2 = fetchUpdater();
KisTransformWorker transformWorker(device,
config.scaleX(), config.scaleY(),
config.shearX(), config.shearY(),
config.originalCenter().x(),
config.originalCenter().y(),
config.aZ(),
(int)(translation.x()),
(int)(translation.y()),
updater1,
config.filter());
transformWorker.run();
KisPerspectiveTransformWorker perspectiveWorker(device,
config.transformedCenter(),
config.aX(),
config.aY(),
config.cameraPos().z(),
updater2);
perspectiveWorker.run();
}
}
/*
* Copyright (c) 2013 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 __TRANSFORM_STROKE_STRATEGY_H
#define __TRANSFORM_STROKE_STRATEGY_H
#include <QMutex>
#include "kis_stroke_strategy_undo_command_based.h"
#include "kis_types.h"
#include "tool_transform_args.h"
class KisUndoAdapter;
class KisPostExecutionUndoAdapter;
class KDE_EXPORT TransformStrokeStrategy : public KisStrokeStrategyUndoCommandBased
{
public:
class KDE_EXPORT TransformData : public KisStrokeJobData {
public:
enum Destination {
PAINT_DEVICE,
SELECTION,
};
public:
TransformData(Destination _destination, const ToolTransformArgs &_config)
: KisStrokeJobData(CONCURRENT, NORMAL),
destination(_destination),
config(_config)
{
}
Destination destination;
ToolTransformArgs config;
};
class KDE_EXPORT ClearSelectionData : public KisStrokeJobData {
public:
ClearSelectionData()
: KisStrokeJobData(SEQUENTIAL, NORMAL)
{
}
};
public:
TransformStrokeStrategy(KisNodeSP node,
KisSelectionSP selection,
KisPaintDeviceSP selectedPortionCache,
KisPostExecutionUndoAdapter *undoAdapter,