mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-14 04:19:12 +00:00
Fix interpolation between stroke points with line brush (fix #728)
Related to #245, and there are still problems with gaps using new dynamic angle parameters.
This commit is contained in:
parent
3b370c2ff5
commit
d1843fcf55
@ -16,8 +16,11 @@
|
||||
#include "app/tools/stroke.h"
|
||||
#include "app/tools/symmetry.h"
|
||||
#include "app/tools/tool_loop.h"
|
||||
#include "base/pi.h"
|
||||
#include "doc/algo.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace app {
|
||||
namespace tools {
|
||||
|
||||
@ -101,26 +104,60 @@ void Intertwine::doPointshapeHline(int x1, int y, int x2, ToolLoop* loop)
|
||||
}
|
||||
|
||||
// static
|
||||
void Intertwine::doPointshapeLine(int x1, int y1, int x2, int y2, ToolLoop* loop)
|
||||
void Intertwine::doPointshapeLineWithoutDynamics(int x1, int y1, int x2, int y2, ToolLoop* loop)
|
||||
{
|
||||
doc::AlgoLineWithAlgoPixel algo = getLineAlgo(loop);
|
||||
algo(x1, y1, x2, y2, (void*)loop, (AlgoPixel)doPointshapePoint);
|
||||
Stroke::Pt a(x1, y1);
|
||||
Stroke::Pt b(x2, y2);
|
||||
a.size = b.size = loop->getBrush()->size();
|
||||
a.angle = b.angle = loop->getBrush()->angle();
|
||||
doPointshapeLine(a, b, loop);
|
||||
}
|
||||
|
||||
void Intertwine::doPointshapeLine(const Stroke::Pt& a,
|
||||
const Stroke::Pt& b, ToolLoop* loop)
|
||||
{
|
||||
doc::AlgoLineWithAlgoPixel algo = getLineAlgo(loop, a, b);
|
||||
LineData lineData(loop, a, b);
|
||||
algo(a.x, a.y, b.x, b.y, (void*)&lineData, (AlgoPixel)doPointshapePointDynamics);
|
||||
}
|
||||
|
||||
// static
|
||||
doc::AlgoLineWithAlgoPixel Intertwine::getLineAlgo(ToolLoop* loop)
|
||||
doc::AlgoLineWithAlgoPixel Intertwine::getLineAlgo(ToolLoop* loop,
|
||||
const Stroke::Pt& a,
|
||||
const Stroke::Pt& b)
|
||||
{
|
||||
bool needsFixForLineBrush = false;
|
||||
if (loop->getBrush()->type() == kLineBrushType) {
|
||||
if ((a.angle != 0.0f || b.angle != 0.0f) &&
|
||||
(a.angle != b.angle)) {
|
||||
needsFixForLineBrush = true;
|
||||
}
|
||||
else {
|
||||
int angle = a.angle;
|
||||
int p = SGN(b.x - a.x);
|
||||
int q = SGN(a.y - b.y);
|
||||
float rF = std::cos(PI * angle / 180);
|
||||
float sF = std::sin(PI * angle / 180);
|
||||
int r = SGN(rF);
|
||||
int s = SGN(sF);
|
||||
needsFixForLineBrush = ((p == q && r != s) ||
|
||||
(p != q && r == s));
|
||||
}
|
||||
}
|
||||
|
||||
if (// When "Snap Angle" in being used or...
|
||||
(int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) ||
|
||||
// "Snap to Grid" is enabled
|
||||
(loop->getController()->canSnapToGrid() && loop->getSnapToGrid())) {
|
||||
// We prefer the perfect pixel lines that matches grid tiles
|
||||
return algo_line_perfect;
|
||||
return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush:
|
||||
algo_line_perfect);
|
||||
}
|
||||
else {
|
||||
// In other case we use the regular algorithm that is useful to
|
||||
// draw continuous lines/strokes.
|
||||
return algo_line_continuous;
|
||||
return (needsFixForLineBrush ? algo_line_continuous_with_fix_for_line_brush:
|
||||
algo_line_continuous);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,9 +48,14 @@ namespace app {
|
||||
static void doPointshapePoint(int x, int y, ToolLoop* loop);
|
||||
static void doPointshapePointDynamics(int x, int y, LineData* data);
|
||||
static void doPointshapeHline(int x1, int y, int x2, ToolLoop* loop);
|
||||
static void doPointshapeLine(int x1, int y1, int x2, int y2, ToolLoop* loop);
|
||||
// TODO We should remove this function and always use dynamics
|
||||
static void doPointshapeLineWithoutDynamics(int x1, int y1, int x2, int y2, ToolLoop* loop);
|
||||
static void doPointshapeLine(const Stroke::Pt& a,
|
||||
const Stroke::Pt& b, ToolLoop* loop);
|
||||
|
||||
static doc::AlgoLineWithAlgoPixel getLineAlgo(ToolLoop* loop);
|
||||
static doc::AlgoLineWithAlgoPixel getLineAlgo(ToolLoop* loop,
|
||||
const Stroke::Pt& a,
|
||||
const Stroke::Pt& b);
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
@ -115,8 +115,8 @@ public:
|
||||
}
|
||||
else {
|
||||
Stroke pts;
|
||||
doc::AlgoLineWithAlgoPixel lineAlgo = getLineAlgo(loop);
|
||||
for (int c=0; c+1<stroke.size(); ++c) {
|
||||
auto lineAlgo = getLineAlgo(loop, stroke[c], stroke[c+1]);
|
||||
LineData2 lineData(loop, stroke[c], stroke[c+1], pts);
|
||||
lineAlgo(stroke[c].x, stroke[c].y,
|
||||
stroke[c+1].x, stroke[c+1].y,
|
||||
@ -143,9 +143,7 @@ public:
|
||||
// contour tool, with brush type = kImageBrush with alpha content and
|
||||
// with not Pixel Perfect pencil mode.
|
||||
if (loop->getFilled() && !loop->getController()->isFreehand()) {
|
||||
doPointshapeLine(stroke[stroke.size()-1].x,
|
||||
stroke[stroke.size()-1].y,
|
||||
stroke[0].x, stroke[0].y, loop);
|
||||
doPointshapeLine(stroke[stroke.size()-1], stroke[0], loop);
|
||||
}
|
||||
}
|
||||
m_firstStroke = false;
|
||||
@ -191,6 +189,7 @@ public:
|
||||
}
|
||||
else if (stroke.size() >= 2) {
|
||||
for (int c=0; c+1<stroke.size(); ++c) {
|
||||
// TODO fix this with strokes and dynamics
|
||||
int x1 = stroke[c].x;
|
||||
int y1 = stroke[c].y;
|
||||
int x2 = stroke[c+1].x;
|
||||
@ -202,8 +201,8 @@ public:
|
||||
|
||||
const double angle = loop->getController()->getShapeAngle();
|
||||
if (ABS(angle) < 0.001) {
|
||||
doPointshapeLine(x1, y1, x2, y1, loop);
|
||||
doPointshapeLine(x1, y2, x2, y2, loop);
|
||||
doPointshapeLineWithoutDynamics(x1, y1, x2, y1, loop);
|
||||
doPointshapeLineWithoutDynamics(x1, y2, x2, y2, loop);
|
||||
|
||||
for (y=y1; y<=y2; y++) {
|
||||
doPointshapePoint(x1, y, loop);
|
||||
@ -214,11 +213,9 @@ public:
|
||||
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||
int n = p.size();
|
||||
for (int i=0; i+1<n; ++i) {
|
||||
doPointshapeLine(p[i].x, p[i].y,
|
||||
p[i+1].x, p[i+1].y, loop);
|
||||
doPointshapeLine(p[i], p[i+1], loop);
|
||||
}
|
||||
doPointshapeLine(p[n-1].x, p[n-1].y,
|
||||
p[0].x, p[0].y, loop);
|
||||
doPointshapeLine(p[n-1], p[0], loop);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -243,7 +240,7 @@ public:
|
||||
const double angle = loop->getController()->getShapeAngle();
|
||||
if (ABS(angle) < 0.001) {
|
||||
for (y=y1; y<=y2; y++)
|
||||
doPointshapeLine(x1, y, x2, y, loop);
|
||||
doPointshapeLineWithoutDynamics(x1, y, x2, y, loop);
|
||||
}
|
||||
else {
|
||||
Stroke p = rotateRectangle(x1, y1, x2, y2, angle);
|
||||
@ -398,25 +395,24 @@ public:
|
||||
|
||||
for (int c=0; c<stroke.size(); c += 4) {
|
||||
if (stroke.size()-c == 1) {
|
||||
doPointshapePoint(stroke[c].x, stroke[c].y, loop);
|
||||
doPointshapeStrokePt(stroke[c], loop);
|
||||
}
|
||||
else if (stroke.size()-c == 2) {
|
||||
doPointshapeLine(stroke[c].x, stroke[c].y,
|
||||
stroke[c+1].x, stroke[c+1].y, loop);
|
||||
doPointshapeLine(stroke[c], stroke[c+1], loop);
|
||||
}
|
||||
else if (stroke.size()-c == 3) {
|
||||
algo_spline(stroke[c ].x, stroke[c ].y,
|
||||
stroke[c+1].x, stroke[c+1].y,
|
||||
stroke[c+1].x, stroke[c+1].y,
|
||||
stroke[c+2].x, stroke[c+2].y, loop,
|
||||
(AlgoLine)doPointshapeLine);
|
||||
(AlgoLine)doPointshapeLineWithoutDynamics);
|
||||
}
|
||||
else {
|
||||
algo_spline(stroke[c ].x, stroke[c ].y,
|
||||
stroke[c+1].x, stroke[c+1].y,
|
||||
stroke[c+2].x, stroke[c+2].y,
|
||||
stroke[c+3].x, stroke[c+3].y, loop,
|
||||
(AlgoLine)doPointshapeLine);
|
||||
(AlgoLine)doPointshapeLineWithoutDynamics);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -465,8 +461,9 @@ public:
|
||||
}
|
||||
else {
|
||||
for (int c=0; c+1<stroke.size(); ++c) {
|
||||
auto lineAlgo = getLineAlgo(loop, stroke[c], stroke[c+1]);
|
||||
LineData2 lineData(loop, stroke[c], stroke[c+1], m_pts);
|
||||
algo_line_continuous(
|
||||
lineAlgo(
|
||||
stroke[c].x,
|
||||
stroke[c].y,
|
||||
stroke[c+1].x,
|
||||
@ -476,17 +473,27 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
for (int c=0; c<m_pts.size(); ++c) {
|
||||
// We ignore a pixel that is between other two pixels in the
|
||||
// corner of a L-like shape.
|
||||
if (c > 0 && c+1 < m_pts.size()
|
||||
&& (m_pts[c-1].x == m_pts[c].x || m_pts[c-1].y == m_pts[c].y)
|
||||
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
|
||||
&& m_pts[c-1].x != m_pts[c+1].x
|
||||
&& m_pts[c-1].y != m_pts[c+1].y) {
|
||||
m_pts.erase(c);
|
||||
// For line brush type, the pixel-perfect will create gaps so we
|
||||
// avoid removing points
|
||||
if (loop->getBrush()->type() != kLineBrushType ||
|
||||
(loop->getDynamics().angle == tools::DynamicSensor::Static &&
|
||||
(loop->getBrush()->angle() == 0.0f ||
|
||||
loop->getBrush()->angle() == 90.0f ||
|
||||
loop->getBrush()->angle() == 180.0f))) {
|
||||
for (int c=0; c<m_pts.size(); ++c) {
|
||||
// We ignore a pixel that is between other two pixels in the
|
||||
// corner of a L-like shape.
|
||||
if (c > 0 && c+1 < m_pts.size()
|
||||
&& (m_pts[c-1].x == m_pts[c].x || m_pts[c-1].y == m_pts[c].y)
|
||||
&& (m_pts[c+1].x == m_pts[c].x || m_pts[c+1].y == m_pts[c].y)
|
||||
&& m_pts[c-1].x != m_pts[c+1].x
|
||||
&& m_pts[c-1].y != m_pts[c+1].y) {
|
||||
m_pts.erase(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int c=0; c<m_pts.size(); ++c) {
|
||||
// We must ignore to print the first point of the line after
|
||||
// a joinStroke pass with a retained "Last" trace policy
|
||||
// (i.e. the user confirms draw a line while he is holding
|
||||
|
@ -101,6 +101,7 @@ protected:
|
||||
doc::color_t m_bgColor;
|
||||
doc::color_t m_primaryColor;
|
||||
doc::color_t m_secondaryColor;
|
||||
tools::DynamicsOptions m_dynamics;
|
||||
|
||||
public:
|
||||
ToolLoopBase(Editor* editor, Site site,
|
||||
@ -144,6 +145,11 @@ public:
|
||||
, m_primaryColor(button == tools::ToolLoop::Left ? m_fgColor: m_bgColor)
|
||||
, m_secondaryColor(button == tools::ToolLoop::Left ? m_bgColor: m_fgColor)
|
||||
{
|
||||
#ifdef ENABLE_UI // TODO add dynamics support when UI is not enabled
|
||||
if (m_controller->isFreehand())
|
||||
m_dynamics = App::instance()->contextBar()->getDynamics();
|
||||
#endif
|
||||
|
||||
if (m_tracePolicy == tools::TracePolicy::Accumulate ||
|
||||
m_tracePolicy == tools::TracePolicy::AccumulateUpdateLast) {
|
||||
tools::ToolBox* toolbox = App::instance()->toolBox();
|
||||
@ -164,9 +170,8 @@ public:
|
||||
}
|
||||
|
||||
// Use overlap trace policy for dynamic gradient
|
||||
auto dynamics = getDynamics();
|
||||
if (dynamics.isDynamic() &&
|
||||
dynamics.gradient != tools::DynamicSensor::Static &&
|
||||
if (m_dynamics.isDynamic() &&
|
||||
m_dynamics.gradient != tools::DynamicSensor::Static &&
|
||||
m_controller->isFreehand()) {
|
||||
// Use overlap trace policy to accumulate changes of colors
|
||||
// between stroke points.
|
||||
@ -376,11 +381,7 @@ public:
|
||||
}
|
||||
|
||||
tools::DynamicsOptions getDynamics() override {
|
||||
#ifdef ENABLE_UI // TODO add support when UI is not enabled
|
||||
return App::instance()->contextBar()->getDynamics();
|
||||
#else
|
||||
return tools::DynamicsOptions();
|
||||
#endif
|
||||
return m_dynamics;
|
||||
}
|
||||
|
||||
void onSliceRect(const gfx::Rect& bounds) override { }
|
||||
|
@ -65,6 +65,54 @@ void algo_line_perfect(int x1, int y1, int x2, int y2, void* data, AlgoPixel pro
|
||||
}
|
||||
}
|
||||
|
||||
// Special version of the perfect line algorithm specially done for
|
||||
// kLineBrushType so the whole line looks continuous without holes.
|
||||
//
|
||||
// TOOD in a future we should convert lines into scanlines and render
|
||||
// scanlines instead of drawing the brush on each pixel, that
|
||||
// would fix all cases
|
||||
void algo_line_perfect_with_fix_for_line_brush(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc)
|
||||
{
|
||||
bool yaxis;
|
||||
|
||||
if (ABS(y2-y1) > ABS(x2-x1)) {
|
||||
std::swap(x1, y1);
|
||||
std::swap(x2, y2);
|
||||
yaxis = true;
|
||||
}
|
||||
else
|
||||
yaxis = false;
|
||||
|
||||
const int w = ABS(x2-x1)+1;
|
||||
const int h = ABS(y2-y1)+1;
|
||||
const int dx = SGN(x2-x1);
|
||||
const int dy = SGN(y2-y1);
|
||||
|
||||
int e = 0;
|
||||
int y = y1;
|
||||
|
||||
x2 += dx;
|
||||
|
||||
for (int x=x1; x!=x2; x+=dx) {
|
||||
if (yaxis)
|
||||
proc(y, x, data);
|
||||
else
|
||||
proc(x, y, data);
|
||||
|
||||
e += h;
|
||||
if (e >= w) {
|
||||
y += dy;
|
||||
e -= w;
|
||||
if (x+dx != x2) {
|
||||
if (yaxis)
|
||||
proc(y, x, data);
|
||||
else
|
||||
proc(x, y, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Line code based on Alois Zingl work released under the
|
||||
// MIT license http://members.chello.at/easyfilter/bresenham.html
|
||||
void algo_line_continuous(int x0, int y0, int x1, int y1, void* data, AlgoPixel proc)
|
||||
@ -91,6 +139,38 @@ void algo_line_continuous(int x0, int y0, int x1, int y1, void* data, AlgoPixel
|
||||
}
|
||||
}
|
||||
|
||||
// Special version of the continuous line algorithm specially done for
|
||||
// kLineBrushType so the whole line looks continuous without holes.
|
||||
void algo_line_continuous_with_fix_for_line_brush(int x0, int y0, int x1, int y1, void* data, AlgoPixel proc)
|
||||
{
|
||||
int dx = ABS(x1-x0), sx = (x0 < x1 ? 1: -1);
|
||||
int dy = -ABS(y1-y0), sy = (y0 < y1 ? 1: -1);
|
||||
int err = dx+dy, e2; // error value e_xy
|
||||
bool x_changed;
|
||||
|
||||
for (;;) {
|
||||
x_changed = false;
|
||||
|
||||
proc(x0, y0, data);
|
||||
e2 = 2*err;
|
||||
if (e2 >= dy) { // e_xy+e_x > 0
|
||||
if (x0 == x1)
|
||||
break;
|
||||
err += dy;
|
||||
x0 += sx;
|
||||
x_changed = true;
|
||||
}
|
||||
if (e2 <= dx) { // e_xy+e_y < 0
|
||||
if (y0 == y1)
|
||||
break;
|
||||
err += dx;
|
||||
if (x_changed)
|
||||
proc(x0, y0, data);
|
||||
y0 += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ellipse code based on Alois Zingl work released under the MIT
|
||||
// license http://members.chello.at/easyfilter/bresenham.html
|
||||
//
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2020 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2018 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -26,6 +26,7 @@ namespace doc {
|
||||
//
|
||||
// Related to: https://github.com/aseprite/aseprite/issues/1395
|
||||
void algo_line_perfect(int x1, int y1, int x2, int y2, void* data, AlgoPixel proc);
|
||||
void algo_line_perfect_with_fix_for_line_brush(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
||||
|
||||
// Useful to create continuous lines (you can draw from one point to
|
||||
// another, and continue from that point to another in the same
|
||||
@ -35,6 +36,7 @@ namespace doc {
|
||||
// https://community.aseprite.org/t/1045
|
||||
// https://github.com/aseprite/aseprite/issues/1894
|
||||
void algo_line_continuous(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
||||
void algo_line_continuous_with_fix_for_line_brush(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
||||
|
||||
void algo_ellipse(int x1, int y1, int x2, int y2, void *data, AlgoPixel proc);
|
||||
void algo_ellipsefill(int x1, int y1, int x2, int y2, void *data, AlgoHLine proc);
|
||||
|
Loading…
x
Reference in New Issue
Block a user