Commit 45d5b0dd authored by Dmitry Kazakov's avatar Dmitry Kazakov
Browse files

Implemented anisotropic spacing for the Krita brushes

Now if you change the 'ratio' option the brush, the horizontal
and vertical spacing will be relative to the width and height of the
brush correspondingly.

This effect is achieved by used equation of a ellipse. When the
cumulative 'distance' value grows out of the ellipse defined by the
spacing values, a new dab is painted.

BUG:276465
parent 1bd83f16
......@@ -60,6 +60,7 @@ endif(HAVE_VC)
set(kritaimage_LIB_SRCS
${libkritatile_SRCS}
kis_distance_information.cpp
kis_painter.cc
kis_progress_updater.cpp
brushengine/kis_paint_information.cc
......
......@@ -151,40 +151,25 @@ KisDistanceInformation KisPaintOp::paintLine(const KisPaintInformation &pi1,
const KisPaintInformation &pi2,
const KisDistanceInformation& savedDist)
{
KisVector2D end = toKisVector2D(pi2.pos());
KisVector2D start = toKisVector2D(pi1.pos());
KisVector2D dragVec = end - start;
Q_ASSERT(savedDist.distance >= 0);
qreal endDist = dragVec.norm();
qreal currentDist = savedDist.distance;
dragVec.normalize();
KisVector2D step(0, 0);
qreal sp = savedDist.spacing;
while (currentDist < endDist) {
QPointF p = toQPointF(start + currentDist * dragVec);
qreal t = currentDist / endDist;
KisVector2D movement;
if(sp > 0.01) {
movement = sp * dragVec;
} else {
movement = 0.01 * dragVec;
}
sp = paintAt(KisPaintInformation::mix(p, t, pi1, pi2, movement));
currentDist += sp;
KisDistanceInformation currentDistance = savedDist;
QPointF start = pi1.pos();
QPointF end = pi2.pos();
QVector2D dragVecNorm = QVector2D(end - start).normalized();
KisPaintInformation pi = pi1;
QPointF pt = start;
qreal t = 0.0;
while ((t = currentDistance.getNextPointPosition(pt, end)) >= 0.0) {
qreal spacingApproximation = currentDistance.spacing().scalarApprox();
KisVector2D movement = toKisVector2D((dragVecNorm * spacingApproximation).toPointF());
pt = pt + t * (end - pt);
pi = KisPaintInformation::mix(pt, t, pi, pi2, movement);
currentDistance.setSpacing(paintAt(pi));
}
QRect r(pi1.pos().toPoint(), pi2.pos().toPoint());
d->painter->addDirtyRect(r.normalized());
return KisDistanceInformation(currentDist - endDist, sp);
return currentDistance;
}
KisPainter* KisPaintOp::painter() const
......
......@@ -57,7 +57,7 @@ public:
* The distance between two calls of the paintAt is always specified by spacing;
* xSpacing and ySpacing is 1.0 by default, negative values causes infinite loops (it is checked by Q_ASSERT)
*/
virtual qreal paintAt(const KisPaintInformation& info) = 0;
virtual KisSpacingInformation paintAt(const KisPaintInformation& info) = 0;
/**
* Draw a line between pos1 and pos2 using the currently set brush and color.
......
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* 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 "kis_distance_information.h"
#include "kis_debug.h"
#include <QtCore/qmath.h>
inline qreal pow2(qreal x) {
return x * x;
}
template<>
inline QPointF qAbs(const QPointF &pt) {
return QPointF(qAbs(pt.x()), qAbs(pt.y()));
}
qreal KisDistanceInformation::getNextPointPosition(const QPointF &start,
const QPointF &end)
{
return m_spacing.isIsotropic() ?
getNextPointPositionIsotropic(start, end) :
getNextPointPositionAnisotropic(start, end);
}
qreal KisDistanceInformation::getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end)
{
qreal distance = m_distance.x();
qreal spacing = m_spacing.spacing().x();
if (distance >= spacing) {
return 0.0;
}
if (start == end) {
return -1;
}
qreal dragVecLength = QVector2D(end - start).length();
qreal nextPointDistance = spacing - distance;
qreal t;
if (nextPointDistance <= dragVecLength) {
t = nextPointDistance / dragVecLength;
m_distance = QPointF();
} else {
t = -1;
m_distance.rx() += dragVecLength;
}
return t;
}
qreal KisDistanceInformation::getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end)
{
if (m_spacing.spacing().isNull()) {
return 0.0;
}
if (start == end) {
return -1;
}
qreal a_rev = 1.0 / qMax(0.5, m_spacing.spacing().x());
qreal b_rev = 1.0 / qMax(0.5, m_spacing.spacing().y());
qreal x = m_distance.x();
qreal y = m_distance.y();
qreal dx = qAbs(end.x() - start.x());
qreal dy = qAbs(end.y() - start.y());
qreal alpha = pow2(dx * a_rev) + pow2(dy * b_rev);
qreal beta = x * dx * a_rev * a_rev + y * dy * b_rev * b_rev;
qreal gamma = pow2(x * a_rev) + pow2(y * b_rev) - 1;
qreal D_4 = pow2(beta) - alpha * gamma;
qreal t = -1.0;
if (D_4 >= 0) {
qreal k = (-beta + qSqrt(D_4)) / alpha;
if (k >= 0.0 && k <= 1.0) {
t = k;
m_distance = QPointF();
} else {
m_distance += qAbs(end - start);
}
} else {
qWarning() << "BUG: No solution for elliptical spacing equation has been found. This shouldn't have happened.";
}
return t;
}
/*
* Copyright (c) 2010 Cyrille Berger <cberger@cberger.net>
* 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
......@@ -19,30 +20,90 @@
#ifndef _KIS_DISTANCE_INFORMATION_H_
#define _KIS_DISTANCE_INFORMATION_H_
#include <QPointF>
#include <QVector2D>
/**
* This structure containg information about the desired spacing
* requested by the paintAt call
*/
class KisSpacingInformation {
public:
KisSpacingInformation()
: m_isIsotropic(true)
{
}
KisSpacingInformation(qreal isotropicSpacing)
: m_spacing(isotropicSpacing, isotropicSpacing),
m_isIsotropic(true)
{
}
KisSpacingInformation(const QPointF &anisotropicSpacing)
: m_spacing(anisotropicSpacing),
m_isIsotropic(anisotropicSpacing.x() == anisotropicSpacing.y())
{
}
inline QPointF spacing() const {
return m_spacing;
}
inline bool isIsotropic() const {
return m_isIsotropic;
}
inline qreal scalarApprox() const {
return m_isIsotropic ? m_spacing.x() : QVector2D(m_spacing).length();
}
private:
QPointF m_spacing;
bool m_isIsotropic;
};
/**
* This function is used as return of paintLine to contains information that need
* to be passed for the next call.
* This structure is used as return value of paintLine to contain
* information that is needed to be passed for the next call.
*/
struct KisDistanceInformation {
KisDistanceInformation()
: distance(0)
, spacing(0)
{}
{
}
KisDistanceInformation(double _distance, double _spacing)
: distance(_distance)
, spacing(_spacing)
{}
/* KisDistanceInformation(const QPointF &distance, const KisSpacingInformation &spacing)
: m_distance(distance)
, m_spacing(spacing)
{
}*/
void clear()
KisDistanceInformation(qreal isotropicDistance, qreal isotropicSpacing)
: m_distance(isotropicDistance, isotropicDistance)
, m_spacing(isotropicSpacing)
{
distance = 0;
spacing = 0;
}
double distance;
double spacing;
qreal getNextPointPosition(const QPointF &start,
const QPointF &end);
inline const KisSpacingInformation& spacing() const {
return m_spacing;
}
inline void setSpacing(const KisSpacingInformation &spacing) {
m_spacing = spacing;
}
private:
qreal getNextPointPositionIsotropic(const QPointF &start,
const QPointF &end);
qreal getNextPointPositionAnisotropic(const QPointF &start,
const QPointF &end);
private:
QPointF m_distance;
KisSpacingInformation m_spacing;
};
#endif
......@@ -1037,7 +1037,7 @@ void KisPainter::paintEllipse(const qreal x,
paintEllipse(QRectF(x, y, w, h));
}
qreal KisPainter::paintAt(const KisPaintInformation& pi)
KisSpacingInformation KisPainter::paintAt(const KisPaintInformation& pi)
{
if (!d->paintOp || !d->paintOp->canPaint()) return 0.0;
return d->paintOp->paintAt(pi);
......
......@@ -492,7 +492,7 @@ public:
void paintPolygon(const vQPointF& points);
/** Draw a spot at pos using the currently set paint op, brush and color */
qreal paintAt(const KisPaintInformation &pos);
KisSpacingInformation paintAt(const KisPaintInformation &pos);
/**
* Stroke the given QPainterPath.
......
......@@ -127,7 +127,8 @@ void KisRecordedPathPaintAction::playPaint(const KisPlayInfo&, KisPainter* paint
switch(slice.type)
{
case Private::BezierCurveSlice::Point:
savedDist = KisDistanceInformation(0, painter->paintAt(slice.point1));
savedDist = KisDistanceInformation();
painter->paintAt(slice.point1);
break;
case Private::BezierCurveSlice::Line:
savedDist = painter->paintLine(slice.point1, slice.point2, savedDist);
......
......@@ -31,7 +31,7 @@ public:
: KisPaintOp(gc) {
}
qreal paintAt(const KisPaintInformation&) {
KisSpacingInformation paintAt(const KisPaintInformation&) {
return 0.0;
}
qreal spacing(qreal&, qreal&, qreal, qreal) const {
......
......@@ -58,7 +58,7 @@ KisChalkPaintOp::~KisChalkPaintOp()
delete m_chalkBrush;
}
qreal KisChalkPaintOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisChalkPaintOp::paintAt(const KisPaintInformation& info)
{
if (!painter()) return 1.0;
......
......@@ -36,7 +36,7 @@ public:
KisChalkPaintOp(const KisChalkPaintOpSettings *settings, KisPainter * painter, KisImageWSP image);
virtual ~KisChalkPaintOp();
qreal paintAt(const KisPaintInformation& info);
KisSpacingInformation paintAt(const KisPaintInformation& info);
private:
KisPaintDeviceSP m_dab;
......
......@@ -106,7 +106,7 @@ inline void KisColorSmudgeOp::getTopLeftAligned(const QPointF &pos, const QPoint
splitCoordinate(topLeft.y(), y, &yFraction);
}
qreal KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
{
KisBrushSP brush = m_brush;
......@@ -138,12 +138,13 @@ qreal KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
// Save the hot spot point for the next iteration
m_lastPaintPos = scatteredPos;
qreal coveredDistance = m_spacingOption.isChecked() ?
spacing(m_spacingOption.apply(info)) : spacing(scale);
KisSpacingInformation spacingInfo =
effectiveSpacing(m_maskBounds.width(), m_maskBounds.height(),
m_spacingOption, info);
if (m_firstRun) {
m_firstRun = false;
return coveredDistance;
return spacingInfo;
}
......@@ -224,5 +225,5 @@ qreal KisColorSmudgeOp::paintAt(const KisPaintInformation& info)
painter()->setOpacity(oldOpacity);
painter()->setCompositeOp(oldModeId);
return coveredDistance;
return spacingInfo;
}
......@@ -47,7 +47,7 @@ public:
KisColorSmudgeOp(const KisBrushBasedPaintOpSettings* settings, KisPainter* painter, KisImageWSP image);
virtual ~KisColorSmudgeOp();
qreal paintAt(const KisPaintInformation& info);
KisSpacingInformation paintAt(const KisPaintInformation& info);
private:
void updateMask(const KisPaintInformation& info, double scale, double rotation);
......
......@@ -46,7 +46,7 @@ KisCurvePaintOp::~KisCurvePaintOp()
delete m_painter;
}
qreal KisCurvePaintOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisCurvePaintOp::paintAt(const KisPaintInformation& info)
{
Q_UNUSED(info);
return 1.0;
......
......@@ -37,7 +37,7 @@ public:
KisCurvePaintOp(const KisCurvePaintOpSettings *settings, KisPainter * painter, KisImageWSP image);
virtual ~KisCurvePaintOp();
qreal paintAt(const KisPaintInformation& info);
KisSpacingInformation paintAt(const KisPaintInformation& info);
KisDistanceInformation paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, const KisDistanceInformation& savedDist);
private:
......
......@@ -97,7 +97,7 @@ KisBrushOp::~KisBrushOp()
delete m_hsvTransformation;
}
qreal KisBrushOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisBrushOp::paintAt(const KisPaintInformation& info)
{
if (!painter()->device()) return 1.0;
......@@ -164,10 +164,9 @@ qreal KisBrushOp::paintAt(const KisPaintInformation& info)
painter()->setOpacity(origOpacity);
painter()->setFlow(origFlow);
if (m_spacingOption.isChecked())
return spacing(m_spacingOption.apply(info));
return spacing(scale);
return effectiveSpacing(dab->bounds().width(),
dab->bounds().height(),
m_spacingOption, info);
}
KisDistanceInformation KisBrushOp::paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, const KisDistanceInformation& savedDist)
......@@ -187,7 +186,7 @@ KisDistanceInformation KisBrushOp::paintLine(const KisPaintInformation& pi1, con
QRect rc = m_lineCacheDevice->extent();
painter()->bitBlt(rc.x(), rc.y(), m_lineCacheDevice, rc.x(), rc.y(), rc.width(), rc.height());
return KisDistanceInformation(0.0, 0.0);
return KisDistanceInformation();
}
return KisPaintOp::paintLine(pi1, pi2, savedDist);
}
......@@ -52,10 +52,10 @@ class KisBrushOp : public KisBrushBasedPaintOp
public:
KisBrushOp(const KisBrushBasedPaintOpSettings *settings, KisPainter * painter, KisImageWSP image);
virtual ~KisBrushOp();
~KisBrushOp();
qreal paintAt(const KisPaintInformation& info);
virtual KisDistanceInformation paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, const KisDistanceInformation& savedDist = KisDistanceInformation());
KisSpacingInformation paintAt(const KisPaintInformation& info);
KisDistanceInformation paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, const KisDistanceInformation& savedDist = KisDistanceInformation());
private:
KisColorSource *m_colorSource;
......
......@@ -110,7 +110,7 @@ qreal KisDuplicateOp::minimizeEnergy(const qreal* m, qreal* sol, int w, int h)
#define CLAMP(x,l,u) ((x)<(l)?(l):((x)>(u)?(u):(x)))
qreal KisDuplicateOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisDuplicateOp::paintAt(const KisPaintInformation& info)
{
if (!painter()) return 1.0;
......@@ -295,5 +295,5 @@ qreal KisDuplicateOp::paintAt(const KisPaintInformation& info)
painter()->renderMirrorMaskSafe(dstRect, m_srcdev, 0, 0, dab,
!m_dabCache->needSeparateOriginal());
return spacing(scale);
return effectiveSpacing(dstRect.width(), dstRect.height());
}
......@@ -46,10 +46,9 @@ class KisDuplicateOp : public KisBrushBasedPaintOp
public:
KisDuplicateOp(const KisDuplicateOpSettings *settings, KisPainter * painter);
~KisDuplicateOp();
virtual ~KisDuplicateOp();
qreal paintAt(const KisPaintInformation& info);
KisSpacingInformation paintAt(const KisPaintInformation& info);
private:
......
......@@ -85,7 +85,7 @@ KisDeformPaintOp::~KisDeformPaintOp()
{
}
qreal KisDeformPaintOp::paintAt(const KisPaintInformation& info)
KisSpacingInformation KisDeformPaintOp::paintAt(const KisPaintInformation& info)
{
if (!painter()) return m_spacing;
if (!m_dev) return m_spacing;
......
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