Commit 9c919cde authored by Dmitry Kazakov's avatar Dmitry Kazakov

Split the transform tool into several classes

Now the tool is split into three classes:

o KisToolTransform
o KisWarpTransformStrategy
o KisFreeTransformStrategy

They handle the transformation modes separately.

This patch also ports the handwritten gradient descent methods into
GNU Scientific Library.
parent 7e2450a4
...@@ -293,6 +293,14 @@ if (USEOPENGL) ...@@ -293,6 +293,14 @@ if (USEOPENGL)
macro_log_feature(HAVE_OPENGL "OpenGL" "OpenGL support" "" FALSE "" "Required by parts of Krita and optionally by flake") macro_log_feature(HAVE_OPENGL "OpenGL" "OpenGL support" "" FALSE "" "Required by parts of Krita and optionally by flake")
endif(USEOPENGL) endif(USEOPENGL)
##
## Test for GNU Scientific Library
##
macro_optional_find_package(GSL)
macro_log_feature(GSL_FOUND "GSL" "GNU Scientific Library" "http://www.gnu.org/software/gsl"
FALSE "1.7" "Required by Krita's Transform tool and Sheets' solver plugin")
macro_bool_to_01(GSL_FOUND HAVE_GSL)
configure_file(config-gsl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-gsl.h )
## ##
## Test for kdepimlibs ## Test for kdepimlibs
......
/* config-gsl.h. Generated by cmake from config-gsl.h.cmake */
/* Define if you have GSL, GNU Scientific Library */
#cmakedefine HAVE_GSL 1
...@@ -139,5 +139,9 @@ inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) { ...@@ -139,5 +139,9 @@ inline qreal kisDistance(const QPointF &pt1, const QPointF &pt2) {
return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y())); return std::sqrt(pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y()));
} }
inline qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2) {
return pow2(pt1.x() - pt2.x()) + pow2(pt1.y() - pt2.y());
}
#endif // KISGLOBAL_H_ #endif // KISGLOBAL_H_
set(kritatooltransform_PART_SRCS set(kritatooltransform_PART_SRCS
tool_transform.cc tool_transform.cc
tool_transform_args.cc tool_transform_args.cc
tool_transform_changes_tracker.cpp tool_transform_changes_tracker.cpp
kis_tool_transform.cc kis_tool_transform.cc
kis_tool_transform_config_widget.cpp kis_tool_transform_config_widget.cpp
kis_warp_transform_strategy.cpp
kis_free_transform_strategy.cpp
kis_free_transform_strategy_gsl_helpers.cpp
kis_transform_utils.cpp
strokes/transform_stroke_strategy.cpp strokes/transform_stroke_strategy.cpp
) )
...@@ -14,9 +16,11 @@ kde4_add_ui_files(kritatooltransform_PART_SRCS wdg_tool_transform.ui) ...@@ -14,9 +16,11 @@ kde4_add_ui_files(kritatooltransform_PART_SRCS wdg_tool_transform.ui)
kde4_add_plugin(kritatooltransform ${kritatooltransform_PART_SRCS}) kde4_add_plugin(kritatooltransform ${kritatooltransform_PART_SRCS})
if (NOT GSL_FOUND)
message (WARNING "KRITA WARNING! No GNU Scientific Library was found! Krita's Transform Tool will not be able to scale the image with handles. Please install GSL library.")
endif (NOT GSL_FOUND)
target_link_libraries(kritatooltransform kritaui ${GSL_LIBRARIES} ${GSL_CBLAS_LIBRARIES})
target_link_libraries(kritatooltransform kritaui)
install(TARGETS kritatooltransform DESTINATION ${PLUGIN_INSTALL_DIR}) install(TARGETS kritatooltransform DESTINATION ${PLUGIN_INSTALL_DIR})
......
/*
* 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_FREE_TRANSFORM_STRATEGY_H
#define __KIS_FREE_TRANSFORM_STRATEGY_H
#include <QObject>
#include <QScopedPointer>
class QPointF;
class QPainter;
class KisCoordinatesConverter;
class ToolTransformArgs;
class QTransform;
class TransformTransactionProperties;
class QCursor;
class QImage;
class KisFreeTransformStrategy : public QObject
{
Q_OBJECT
public:
KisFreeTransformStrategy(const KisCoordinatesConverter *converter,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction,
QTransform &transform);
~KisFreeTransformStrategy();
void setTransformFunction(const QPointF &mousePos, bool usePerspective);
void paint(QPainter &gc);
QCursor getCurrentCursor() const;
void setThumbnailImage(const QImage &image, QTransform thumbToImageTransform);
void externalConfigChanged();
bool beginPrimaryAction(const QPointF &pt);
void continuePrimaryAction(const QPointF &pt, bool specialModifierActve);
bool endPrimaryAction();
void recalculateTransformationsWORKAROUND();
enum StrokeFunction {
ROTATE = 0,
MOVE,
RIGHTSCALE,
TOPRIGHTSCALE,
TOPSCALE,
TOPLEFTSCALE,
LEFTSCALE,
BOTTOMLEFTSCALE,
BOTTOMSCALE,
BOTTOMRIGHTSCALE,
BOTTOMSHEAR,
RIGHTSHEAR,
TOPSHEAR,
LEFTSHEAR,
MOVECENTER,
PERSPECTIVE
};
StrokeFunction functionWORKAROUND() const;
signals:
void requestCanvasUpdate();
void requestResetRotationCenterButtons();
void requestShowImageTooBig(bool value);
private:
class Private;
const QScopedPointer<Private> m_d;
};
#endif /* __KIS_FREE_TRANSFORM_STRATEGY_H */
/*
* 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_FREE_TRANSFORM_STRATEGY_GSL_HELPERS_H
#define __KIS_FREE_TRANSFORM_STRATEGY_GSL_HELPERS_H
#include <QPointF>
class ToolTransformArgs;
namespace GSL
{
struct ScaleResult1D {
ScaleResult1D() : scale(1.0) {}
QPointF transformedCenter;
qreal scale;
};
ScaleResult1D calculateScaleX(const ToolTransformArgs &args,
const QPointF &staticPointSrc,
const QPointF &staticPointDst,
const QPointF &movingPointSrc,
qreal viewDistance);
ScaleResult1D calculateScaleY(const ToolTransformArgs &args,
const QPointF &staticPointSrc,
const QPointF &staticPointDst,
const QPointF &movingPointSrc,
qreal viewDistance);
struct ScaleResult2D {
ScaleResult2D() : scaleX(1.0), scaleY(1.0) {}
QPointF transformedCenter;
qreal scaleX;
qreal scaleY;
};
ScaleResult2D calculateScale2D(const ToolTransformArgs &args,
const QPointF &staticPointSrc,
const QPointF &staticPointDst,
const QPointF &movingPointSrc,
const QPointF &movingPointDst);
}
#endif /* __KIS_FREE_TRANSFORM_STRATEGY_GSL_HELPERS_H */
...@@ -230,7 +230,7 @@ void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config) ...@@ -230,7 +230,7 @@ void KisToolTransformConfigWidget::updateConfig(const ToolTransformArgs &config)
alphaBox->setValue(config.alpha()); alphaBox->setValue(config.alpha());
if (config.defaultPoints()) { if (config.defaultPoints()) {
densityBox->setValue(config.pointsPerLine()); densityBox->setValue(config.numPoints());
} }
cmbWarpType->setCurrentIndex((int)config.warpType()); cmbWarpType->setCurrentIndex((int)config.warpType());
...@@ -549,7 +549,6 @@ void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine) ...@@ -549,7 +549,6 @@ void KisToolTransformConfigWidget::setDefaultWarpPoints(int pointsPerLine)
config->setDefaultPoints(true); config->setDefaultPoints(true);
config->setPoints(origPoints, transfPoints); config->setPoints(origPoints, transfPoints);
config->setPointsPerLine(pointsPerLine);
notifyConfigChanged(); notifyConfigChanged();
} }
......
/*
* 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_transform_utils.h"
#include <cmath>
#include <QTransform>
#include "tool_transform_args.h"
const int KisTransformUtils::rotationHandleVisualRadius = 12;
const int KisTransformUtils::rotationHandleRadius = 24;
const int KisTransformUtils::handleVisualRadius = 12;
const int KisTransformUtils::handleRadius = 24;
QTransform KisTransformUtils::imageToFlakeTransform(const KisCoordinatesConverter *converter)
{
return converter->imageToDocumentTransform() * converter->documentToFlakeTransform();
}
qreal KisTransformUtils::effectiveHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(handleRadius, handleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter)
{
QPointF handleRadiusPt = flakeToImage(converter, QPointF(rotationHandleRadius, rotationHandleRadius));
return (handleRadiusPt.x() > handleRadiusPt.y()) ? handleRadiusPt.x() : handleRadiusPt.y();
}
qreal KisTransformUtils::scaleFromAffineMatrix(const QTransform &t) {
return std::sqrt(t.m11() * t.m11() + t.m22() * t.m22() + t.m12() * t.m12() + t.m21() * t.m21());
}
QPointF KisTransformUtils::clipInRect(QPointF p, QRectF r)
{
QPointF center = r.center();
QPointF t = p - center;
r.translate(- center);
if (t.y() != 0) {
if (t.x() != 0) {
double slope = t.y() / t.x();
if (t.x() < r.left()) {
t.setY(r.left() * slope);
t.setX(r.left());
}
else if (t.x() > r.right()) {
t.setY(r.right() * slope);
t.setX(r.right());
}
if (t.y() < r.top()) {
t.setX(r.top() / slope);
t.setY(r.top());
}
else if (t.y() > r.bottom()) {
t.setX(r.bottom() / slope);
t.setY(r.bottom());
}
}
else {
if (t.y() < r.top())
t.setY(r.top());
else if (t.y() > r.bottom())
t.setY(r.bottom());
}
}
else {
if (t.x() < r.left())
t.setX(r.left());
else if (t.x() > r.right())
t.setX(r.right());
}
t += center;
return t;
}
KisTransformUtils::MatricesPack::MatricesPack(const ToolTransformArgs &args)
{
TS = QTransform::fromTranslate(-args.originalCenter().x(), -args.originalCenter().y());
SC = QTransform::fromScale(args.scaleX(), args.scaleY());
S.shear(0, args.shearY()); S.shear(args.shearX(), 0);
P.rotate(180. * args.aX() / M_PI, QVector3D(1, 0, 0));
P.rotate(180. * args.aY() / M_PI, QVector3D(0, 1, 0));
P.rotate(180. * args.aZ() / M_PI, QVector3D(0, 0, 1));
projectedP = P.toTransform(args.cameraPos().z());
QPointF translation = args.transformedCenter();
T = QTransform::fromTranslate(translation.x(), translation.y());
}
QTransform KisTransformUtils::MatricesPack::finalTransform() const
{
return TS * SC * S * projectedP * T;
}
/*
* 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_TRANSFORM_UTILS_H
#define __KIS_TRANSFORM_UTILS_H
#include <QtGlobal>
#include "kis_coordinates_converter.h"
#include <QTransform>
#include <QMatrix4x4>
class ToolTransformArgs;
class KisTransformUtils
{
public:
static const int rotationHandleVisualRadius;
static const int handleVisualRadius;
static const int handleRadius;
static const int rotationHandleRadius;
template <class T>
static T flakeToImage(const KisCoordinatesConverter *converter, T object) {
return converter->documentToImage(converter->flakeToDocument(object));
}
static QTransform imageToFlakeTransform(const KisCoordinatesConverter *converter);
static qreal effectiveHandleGrabRadius(const KisCoordinatesConverter *converter);
static qreal effectiveRotationHandleGrabRadius(const KisCoordinatesConverter *converter);
static qreal scaleFromAffineMatrix(const QTransform &t);
static QPointF clipInRect(QPointF p, QRectF r);
struct MatricesPack
{
MatricesPack(const ToolTransformArgs &args);
QTransform TS;
QTransform SC;
QTransform S;
QMatrix4x4 P;
QTransform projectedP;
QTransform T;
// the final transformation looks like
// transform = TS * SC * S * projectedP * T
QTransform finalTransform() const;
};
};
#endif /* __KIS_TRANSFORM_UTILS_H */
/*
* 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_warp_transform_strategy.h"
#include <QPointF>
#include <QPainter>
#include "kis_coordinates_converter.h"
#include "tool_transform_args.h"
#include "transform_transaction_properties.h"
#include "krita_utils.h"
#include "kis_cursor.h"
#include "kis_transform_utils.h"
struct KisWarpTransformStrategy::Private
{
Private(const KisCoordinatesConverter *_converter,
ToolTransformArgs &_currentArgs,
TransformTransactionProperties &_transaction)
: converter(_converter),
currentArgs(_currentArgs),
transaction(_transaction)
{
}
const KisCoordinatesConverter *converter;
bool cursorOverPoint;
int pointIndexUnderCursor;
//////
ToolTransformArgs &currentArgs;
//////
TransformTransactionProperties &transaction;
QTransform thumbToImageTransform;
QImage originalImage;
QTransform paintingTransform;
QPointF paintingOffset;
QImage transformedImage;
QTransform handlesTransform;
static const int rotationCenterVisualRadius = 12;
static const int handleVisualRadius = 12;
static const int handleRadius = 24;
void recalculateTransformations();
inline QPointF imageToThumb(const QPointF &pt, bool useFlakeOptimization);
};
KisWarpTransformStrategy::KisWarpTransformStrategy(const KisCoordinatesConverter *converter,
ToolTransformArgs &currentArgs,
TransformTransactionProperties &transaction)
: m_d(new Private(converter, currentArgs, transaction))
{
}
KisWarpTransformStrategy::~KisWarpTransformStrategy()
{
}
void KisWarpTransformStrategy::setTransformFunction(const QPointF &mousePos)
{
double handleRadiusSq = pow2(KisTransformUtils::effectiveHandleGrabRadius(m_d->converter));
m_d->cursorOverPoint = false;
m_d->pointIndexUnderCursor = -1;
const QVector<QPointF> &points = m_d->currentArgs.transfPoints();
for (int i = 0; i < points.size(); ++i) {
if (kisSquareDistance(mousePos, points[i]) <= handleRadiusSq) {
m_d->cursorOverPoint = true;
m_d->pointIndexUnderCursor = i;
break;
}
}
}
QCursor KisWarpTransformStrategy::getCurrentCursor() const
{
if (m_d->cursorOverPoint) {
return KisCursor::pointingHandCursor();
} else {
return KisCursor::arrowCursor();
}
}
void KisWarpTransformStrategy::paint(QPainter &gc)
{
// Draw handles
int numPoints = m_d->currentArgs.origPoints().size();
gc.save();
gc.setOpacity(0.9);
gc.setTransform(m_d->paintingTransform, true);
gc.drawImage(m_d->paintingOffset, m_d->transformedImage);
gc.restore();
gc.save();
gc.setTransform(m_d->handlesTransform, true);
// draw connecting lines
{
QPen antsPen;
QPen outlinePen;
KritaUtils::initAntsPen(&antsPen, &outlinePen);
gc.setOpacity(0.5);
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawLine(m_d->currentArgs.transfPoints()[i], m_d->currentArgs.origPoints()[i]);
gc.setPen(antsPen);
gc.drawLine(m_d->currentArgs.transfPoints()[i], m_d->currentArgs.origPoints()[i]);
}
}
// draw handles themselves
{
QPen mainPen(Qt::black);
QPen outlinePen(Qt::white);
qreal handlesExtraScale = KisTransformUtils::scaleFromAffineMatrix(m_d->handlesTransform);
qreal dstIn = 8 / handlesExtraScale;
qreal dstOut = 10 / handlesExtraScale;
qreal srcIn = 6 / handlesExtraScale;
qreal srcOut = 6 / handlesExtraScale;
QRectF handleRect1(-0.5 * dstIn, -0.5 * dstIn, dstIn, dstIn);
QRectF handleRect2(-0.5 * dstOut, -0.5 * dstOut, dstOut, dstOut);
gc.setOpacity(1.0);
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawEllipse(handleRect2.translated(m_d->currentArgs.transfPoints()[i]));
gc.setPen(mainPen);
gc.drawEllipse(handleRect1.translated(m_d->currentArgs.transfPoints()[i]));
}
QPainterPath inLine;
inLine.moveTo(-0.5 * srcIn, 0);
inLine.lineTo( 0.5 * srcIn, 0);
inLine.moveTo( 0, -0.5 * srcIn);
inLine.lineTo( 0, 0.5 * srcIn);
QPainterPath outLine;
outLine.moveTo(-0.5 * srcOut, -0.5 * srcOut);
outLine.lineTo( 0.5 * srcOut, -0.5 * srcOut);
outLine.lineTo( 0.5 * srcOut, 0.5 * srcOut);
outLine.lineTo(-0.5 * srcOut, 0.5 * srcOut);
outLine.lineTo(-0.5 * srcOut, -0.5 * srcOut);
gc.setOpacity(0.5);
for (int i = 0; i < numPoints; ++i) {
gc.setPen(outlinePen);
gc.drawPath(outLine.translated(m_d->currentArgs.origPoints()[i]));
gc.setPen(mainPen);
gc.drawPath(inLine.translated(m_d->currentArgs.origPoints()[i]));
}
}
gc.restore();
}
void KisWarpTransformStrategy::externalConfigChanged()
{
m_d->recalculateTransformations();
}
bool KisWarpTransformStrategy::beginPrimaryAction(const QPointF &pt)
{
if (m_d->cursorOverPoint) return true;
bool hasSomethingToDrag = m_d->transaction.editWarpPoints();
if (hasSomethingToDrag) {
m_d->currentArgs.refOriginalPoints().append(pt);
m_d->currentArgs.refTransformedPoints().append(pt);
m_d->cursorOverPoint = true;
m_d->pointIndexUnderCursor = m_d->currentArgs.origPoints().size() - 1;
m_d->recalculateTransformations();
setTransformFunction(pt);
emit requestCanvasUpdate();
}
return hasSomethingToDrag;
}
void KisWarpTransformStrategy::continuePrimaryAction(const QPointF &pt)
{
// toplevel code switches to HOVER mode if nothing is selected
KIS_ASSERT_RECOVER_RETURN(m_d->pointIndexUnderCursor >= 0);
if (m_d->transaction.editWarpPoints()) {
QPointF newPos = KisTransformUtils::clipInRect(pt, m_d->transaction.originalRect());
m_d->currentArgs.origPoint(m_d->pointIndexUnderCursor) = newPos;
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = newPos;
}
else {
m_d->currentArgs.transfPoint(m_d->pointIndexUnderCursor) = pt;
}
m_d->recalculateTransformations();
emit requestCanvasUpdate();
}
bool KisWarpTransformStrategy::endPrimaryAction()
{