Commit 389b3abd authored by Dmitry Kazakov's avatar Dmitry Kazakov

Implemented brush outline for the Liquify transform strategy

parent fdb29eb6
......@@ -92,6 +92,48 @@ void paintLine(PaintOp &op,
}
}
/**
* A special class containing the previous position of the cursor for
* the sake of painting the outline of the paint op. The main purpose
* of this class is to ensure that the saved point does not equal to
* the current one, which would cause a outline flicker. To echieve
* this the class stores two previosly requested points instead of the
* last one.
*/
class PositionHistory
{
public:
/**
* \return the previously used point, which is guaranteed not to
* be equal to \p pt and updates the history if needed
*/
QPointF pushThroughHistory(const QPointF &pt) {
QPointF result;
const qreal pointSwapThreshold = 7.0;
/**
* We check x *and* y separately, because events generated by
* a mouse device tend to come separately for x and y offsets.
* Efficienty generating the 'stairs' pattern.
*/
if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold &&
qAbs(pt.y() - m_second.y()) > pointSwapThreshold) {
result = m_second;
m_first = m_second;
m_second = pt;
} else {
result = m_first;
}
return result;
}
private:
QPointF m_first;
QPointF m_second;
};
}
#endif /* __KIS_PAINTOP_UTILS_H */
......@@ -18,8 +18,10 @@
#include "kis_algebra_2d.h"
#include <QPainterPath>
#include <kis_debug.h>
#define SANITY_CHECKS
namespace KisAlgebra2D {
......@@ -85,4 +87,20 @@ qreal angleBetweenVectors(const QPointF &v1, const QPointF &v2)
return a2 - a1;
}
QPainterPath smallArrow()
{
QPainterPath p;
p.moveTo(5, 2);
p.lineTo(-3, 8);
p.lineTo(-5, 5);
p.lineTo( 2, 0);
p.lineTo(-5,-5);
p.lineTo(-3,-8);
p.lineTo( 5,-2);
p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
return p;
}
}
......@@ -27,6 +27,8 @@
#include <kis_global.h>
#include <krita_export.h>
class QPainterPath;
namespace KisAlgebra2D {
template <class T>
......@@ -196,6 +198,8 @@ inline Point clampPoint(Point pt, const Rect &bounds)
return pt;
}
QPainterPath KRITAIMAGE_EXPORT smallArrow();
}
#endif /* __KIS_ALGEBRA_2D_H */
......@@ -25,6 +25,7 @@
#include "kis_paintop_utils.h"
#include "kis_coordinates_converter.h"
#include "kis_liquify_paintop.h"
#include "kis_paintop_utils.h"
struct KisLiquifyPaintHelper::Private
......@@ -43,6 +44,10 @@ struct KisLiquifyPaintHelper::Private
QScopedPointer<KisPaintingInformationBuilder> infoBuilder;
QTime strokeTime;
KisDistanceInformation previousDistanceInfo;
KisPaintOpUtils::PositionHistory lastOutlinePos;
void updatePreviousPaintInfo(const KisPaintInformation &info);
};
......@@ -55,6 +60,23 @@ KisLiquifyPaintHelper::~KisLiquifyPaintHelper()
{
}
void KisLiquifyPaintHelper::Private::updatePreviousPaintInfo(const KisPaintInformation &info)
{
previousDistanceInfo =
KisDistanceInformation(
lastOutlinePos.pushThroughHistory(info.pos()), 0);
previousPaintInfo = info;
}
QPainterPath KisLiquifyPaintHelper::brushOutline(const ToolTransformArgs::LiquifyProperties &props) const
{
KisPaintInformation::DistanceInformationRegistrar registrar =
m_d->previousPaintInfo.registerDistanceInformation(&m_d->previousDistanceInfo);
return KisLiquifyPaintop::brushOutline(props, m_d->previousPaintInfo);
}
void KisLiquifyPaintHelper::configurePaintOp(const ToolTransformArgs::LiquifyProperties &props,
KisLiquifyTransformWorker *worker)
{
......@@ -69,7 +91,7 @@ void KisLiquifyPaintHelper::startPaint(KoPointerEvent *event)
KisPaintInformation pi =
m_d->infoBuilder->startStroke(event, m_d->strokeTime.elapsed());
m_d->previousPaintInfo = pi;
m_d->updatePreviousPaintInfo(pi);
}
void KisLiquifyPaintHelper::continuePaint(KoPointerEvent *event)
......@@ -85,7 +107,7 @@ void KisLiquifyPaintHelper::continuePaint(KoPointerEvent *event)
&m_d->currentDistance,
false, false);
m_d->previousPaintInfo = pi;
m_d->updatePreviousPaintInfo(pi);
}
void KisLiquifyPaintHelper::endPaint(KoPointerEvent *event)
......@@ -101,5 +123,5 @@ void KisLiquifyPaintHelper::hoverPaint(KoPointerEvent *event)
QPointF imagePoint = m_d->converter->documentToImage(event->pos());
KisPaintInformation pi = m_d->infoBuilder->hover(imagePoint, event);
m_d->previousPaintInfo = pi;
m_d->updatePreviousPaintInfo(pi);
}
......@@ -44,6 +44,8 @@ public:
void hoverPaint(KoPointerEvent *event);
QPainterPath brushOutline(const ToolTransformArgs::LiquifyProperties &props) const;
private:
struct Private;
const QScopedPointer<Private> m_d;
......
......@@ -41,11 +41,70 @@ KisLiquifyPaintop::~KisLiquifyPaintop()
{
}
QPainterPath KisLiquifyPaintop::brushOutline(const ToolTransformArgs::LiquifyProperties &props,
const KisPaintInformation &info)
{
const qreal diameter = props.size();
const qreal reverseCoeff = props.reverseDirection() ? -1.0 : 1.0;
QPainterPath outline;
outline.addEllipse(-0.5 * diameter, -0.5 * diameter,
diameter, diameter);
switch (props.currentMode()) {
case ToolTransformArgs::LiquifyProperties::MOVE:
case ToolTransformArgs::LiquifyProperties::SCALE:
break;
case ToolTransformArgs::LiquifyProperties::ROTATE: {
QPainterPath p;
p.lineTo(-3.0, 4.0);
p.moveTo(0.0, 0.0);
p.lineTo(-3.0, -4.0);
QTransform S;
if (diameter < 15.0) {
const qreal scale = diameter / 15.0;
S = QTransform::fromScale(scale, scale);
}
QTransform R;
R.rotateRadians(-reverseCoeff * 0.5 * M_PI);
QTransform T = QTransform::fromTranslate(0.5 * diameter, 0.0);
p = (S * R * T).map(p);
outline.addPath(p);
break;
}
case ToolTransformArgs::LiquifyProperties::OFFSET: {
qreal normalAngle = info.drawingAngle() + reverseCoeff * 0.5 * M_PI;
QPainterPath p = KisAlgebra2D::smallArrow();
const qreal offset = qMax(0.8 * diameter, 15.0);
QTransform R;
R.rotateRadians(normalAngle);
QTransform T = QTransform::fromTranslate(offset, 0.0);
p = (T * R).map(p);
outline.addPath(p);
break;
}
case ToolTransformArgs::LiquifyProperties::UNDO:
break;
}
return outline;
}
KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi)
{
const qreal size = m_d->props.sizeHasPressure() ?
pi.pressure() * m_d->props.size():
m_d->props.size();
static const qreal sizeToSigmaCoeff = 1.0 / 3.0;
const qreal size = sizeToSigmaCoeff *
(m_d->props.sizeHasPressure() ?
pi.pressure() * m_d->props.size():
m_d->props.size());
const qreal spacing = m_d->props.spacing() * size;
......
......@@ -36,6 +36,9 @@ public:
KisSpacingInformation paintAt(const KisPaintInformation &pi);
static QPainterPath brushOutline(const ToolTransformArgs::LiquifyProperties &props,
const KisPaintInformation &info);
private:
struct Private;
const QScopedPointer<Private> m_d;
......
......@@ -87,6 +87,11 @@ KisLiquifyTransformStrategy::~KisLiquifyTransformStrategy()
{
}
QPainterPath KisLiquifyTransformStrategy::getCursorOutline() const
{
return m_d->helper.brushOutline(*m_d->currentArgs.liquifyProperties());
}
void KisLiquifyTransformStrategy::setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive)
{
Q_UNUSED(mousePos);
......@@ -95,7 +100,7 @@ void KisLiquifyTransformStrategy::setTransformFunction(const QPointF &mousePos,
QCursor KisLiquifyTransformStrategy::getCurrentCursor() const
{
return KisCursor::arrowCursor();
return Qt::BlankCursor;
}
void KisLiquifyTransformStrategy::paint(QPainter &gc)
......@@ -148,6 +153,11 @@ bool KisLiquifyTransformStrategy::endPrimaryAction(KoPointerEvent *event)
return true;
}
void KisLiquifyTransformStrategy::hoverPrimaryAction(KoPointerEvent *event)
{
m_d->helper.hoverPaint(event);
}
inline QPointF KisLiquifyTransformStrategy::Private::imageToThumb(const QPointF &pt, bool useFlakeOptimization)
{
return useFlakeOptimization ? converter->imageToDocument(converter->documentToFlake((pt))) : q->thumbToImageTransform().inverted().map(pt);
......
......@@ -46,16 +46,19 @@ public:
void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive);
void paint(QPainter &gc);
QCursor getCurrentCursor() const;
QPainterPath getCursorOutline() const;
void externalConfigChanged();
using KisTransformStrategyBase::beginPrimaryAction;
using KisTransformStrategyBase::continuePrimaryAction;
using KisTransformStrategyBase::endPrimaryAction;
using KisTransformStrategyBase::hoverPrimaryAction;
bool beginPrimaryAction(KoPointerEvent *event);
void continuePrimaryAction(KoPointerEvent *event, bool specialModifierActve);
bool endPrimaryAction(KoPointerEvent *event);
void hoverPrimaryAction(KoPointerEvent *event);
bool acceptsClicks() const;
......
......@@ -196,6 +196,16 @@ void KisToolTransform::paint(QPainter& gc, const KoViewConverter &converter)
gc.restore();
currentStrategy()->paint(gc);
if (!m_cursorOutline.isEmpty()) {
gc.save();
gc.setTransform(QTransform(), false);
QPainterPath mappedOutline = m_canvas->coordinatesConverter()->imageToWidgetTransform().map(m_cursorOutline);
paintToolOutline(&gc, mappedOutline);
gc.restore();
}
}
void KisToolTransform::setFunctionalCursor()
......@@ -207,6 +217,31 @@ void KisToolTransform::setFunctionalCursor()
}
}
void KisToolTransform::updateCursorOutline(const QPointF &imagePos)
{
QRect canvasUpdateRect;
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect = m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
m_cursorOutline = currentStrategy()->
getCursorOutline().translated(imagePos);
if (!m_cursorOutline.isEmpty()) {
canvasUpdateRect |=
m_canvas->coordinatesConverter()->
imageToDocument(m_cursorOutline.boundingRect()).toAlignedRect();
}
if (!canvasUpdateRect.isEmpty()) {
// grow rect a bit to follow interpolation fuzziness
canvasUpdateRect = kisGrowRect(canvasUpdateRect, 2);
m_canvas->updateCanvas(canvasUpdateRect);
}
}
void KisToolTransform::mousePressEvent(KoPointerEvent *event)
{
if (!PRESS_CONDITION_OM(event, KisTool::HOVER_MODE, Qt::LeftButton, Qt::ControlModifier | Qt::ShiftModifier)) {
......@@ -496,9 +531,12 @@ void KisToolTransform::mouseMoveEvent(KoPointerEvent *event)
{
QPointF mousePos = m_canvas->coordinatesConverter()->documentToImage(event->point);
updateCursorOutline(mousePos);
if (!MOVE_CONDITION(event, KisTool::PAINT_MODE)) {
currentStrategy()->setTransformFunction(mousePos, event->modifiers() & Qt::ControlModifier);
setFunctionalCursor();
currentStrategy()->hoverPrimaryAction(event);
KisTool::mouseMoveEvent(event);
return;
}
......
......@@ -208,6 +208,7 @@ private:
void outlineChanged();
// Sets the cursor according to mouse position (doesn't take shearing into account well yet)
void setFunctionalCursor();
void updateCursorOutline(const QPointF &imagePos);
// Sets m_function according to mouse position and modifier
void setTransformFunction(QPointF mousePos, Qt::KeyboardModifiers modifiers);
......@@ -274,6 +275,8 @@ private:
QScopedPointer<KisPerspectiveTransformStrategy> m_perspectiveStrategy;
KisTransformStrategyBase* currentStrategy() const;
QPainterPath m_cursorOutline;
private slots:
void slotTrackerChangedConfig();
void slotUiChangedConfig();
......
......@@ -44,6 +44,11 @@ KisTransformStrategyBase::~KisTransformStrategyBase()
{
}
QPainterPath KisTransformStrategyBase::getCursorOutline() const
{
return QPainterPath();
}
QImage KisTransformStrategyBase::originalImage() const
{
return m_d->originalImage;
......@@ -75,6 +80,11 @@ void KisTransformStrategyBase::continuePrimaryAction(KoPointerEvent *event, bool
continuePrimaryAction(m_d->converter->documentToImage(event->point), specialModifierActve);
}
void KisTransformStrategyBase::hoverPrimaryAction(KoPointerEvent *event)
{
hoverPrimaryAction(m_d->converter->documentToImage(event->point));
}
bool KisTransformStrategyBase::endPrimaryAction(KoPointerEvent *event)
{
Q_UNUSED(event);
......@@ -100,3 +110,8 @@ bool KisTransformStrategyBase::endPrimaryAction()
qFatal("Not implemented");
return false;
}
void KisTransformStrategyBase::hoverPrimaryAction(const QPointF &pt)
{
Q_UNUSED(pt);
}
......@@ -29,6 +29,7 @@ class QPainter;
class QCursor;
class KoPointerEvent;
class KisCoordinatesConverter;
class QPainterPath;
class KisTransformStrategyBase : public QObject
......@@ -49,18 +50,21 @@ public:
virtual void setTransformFunction(const QPointF &mousePos, bool perspectiveModifierActive) = 0;
virtual void paint(QPainter &gc) = 0;
virtual QCursor getCurrentCursor() const = 0;
virtual QPainterPath getCursorOutline() const;
virtual void externalConfigChanged() = 0;
virtual bool beginPrimaryAction(KoPointerEvent *event);
virtual void continuePrimaryAction(KoPointerEvent *event, bool specialModifierActve);
virtual bool endPrimaryAction(KoPointerEvent *event);
virtual void hoverPrimaryAction(KoPointerEvent *event);
protected:
virtual bool beginPrimaryAction(const QPointF &pt);
virtual void continuePrimaryAction(const QPointF &pt, bool specialModifierActve);
virtual bool endPrimaryAction();
virtual void hoverPrimaryAction(const QPointF &pt);
private:
struct Private;
......
......@@ -32,6 +32,8 @@
#include <kis_doc2.h>
#include <kis_image.h>
#include <kis_canvas_controller.h>
#include <kis_algebra_2d.h>
KisInfinityManager::KisInfinityManager(KisView2 *view, KisCanvas2 *canvas)
......@@ -137,16 +139,7 @@ void KisInfinityManager::drawDecoration(QPainter& gc, const QRectF& updateArea,
QColor color = cfg.canvasBorderColor();
gc.fillPath(m_decorationPath, color.darker(115));
QPainterPath p;
p.moveTo(5, 2);
p.lineTo(-3, 8);
p.lineTo(-5, 5);
p.lineTo( 2, 0);
p.lineTo(-5,-5);
p.lineTo(-3,-8);
p.lineTo( 5,-2);
p.arcTo(QRectF(3, -2, 4, 4), 90, -180);
QPainterPath p = KisAlgebra2D::smallArrow();
foreach (const QTransform &t, m_handleTransform) {
gc.fillPath(t.map(p), color);
......
......@@ -33,54 +33,13 @@
#include "kis_painter.h"
#include "kis_smoothing_options.h"
#include "kis_paintop_preset.h"
#include "kis_paintop_utils.h"
#include <math.h>
//#define DEBUG_BEZIER_CURVES
/**
* A special class containing the previous position of the cursor for
* the sake of painting the outline of the paint op. The main purposu
* of this class is to ensure that the saved point does not equal to
* the current one, which would cause a outline flicker. To echieve
* this the class stores two previosly requested points instead of the
* last one.
*/
class PositionHistory
{
public:
/**
* \return the previously used point, which is guaranteed not to
* be equal to \p pt and updates the history if needed
*/
QPointF pushThroughHistory(const QPointF &pt) {
QPointF result;
const qreal pointSwapThreshold = 7.0;
/**
* We check x *and* y separately, because events generated by
* a mouse device tend to come separately for x and y offsets.
* Efficienty generating the 'stairs' pattern.
*/
if (qAbs(pt.x() - m_second.x()) > pointSwapThreshold &&
qAbs(pt.y() - m_second.y()) > pointSwapThreshold) {
result = m_second;
m_first = m_second;
m_second = pt;
} else {
result = m_first;
}
return result;
}
private:
QPointF m_first;
QPointF m_second;
};
struct KisToolFreehandHelper::Private
{
KisPaintingInformationBuilder *infoBuilder;
......@@ -111,7 +70,7 @@ struct KisToolFreehandHelper::Private
QList<KisPaintInformation> history;
QList<qreal> distanceHistory;
PositionHistory lastOutlinePos;
KisPaintOpUtils::PositionHistory lastOutlinePos;
// Stabilizer data
QQueue<KisPaintInformation> stabilizerDeque;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment