1// Aseprite
2// Copyright (C) 2018-2020 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/tools/intertwine.h"
13
14#include "app/tools/controller.h"
15#include "app/tools/point_shape.h"
16#include "app/tools/stroke.h"
17#include "app/tools/symmetry.h"
18#include "app/tools/tool_loop.h"
19#include "base/pi.h"
20#include "doc/algo.h"
21#include "doc/layer.h"
22
23#include <cmath>
24
25namespace app {
26namespace tools {
27
28using namespace gfx;
29using namespace doc;
30
31Intertwine::LineData::LineData(ToolLoop* loop,
32 const Stroke::Pt& a,
33 const Stroke::Pt& b)
34 : loop(loop)
35 , a(a)
36 , b(b)
37 , pt(a)
38{
39 const int steps = std::max(std::abs(b.x - a.x),
40 std::abs(b.y - a.y))+1;
41 t = 0.0f;
42 step = 1.0f / steps;
43}
44
45void Intertwine::LineData::doStep(int x, int y)
46{
47 t += step;
48 const float ti = 1.0f-t;
49
50 pt.x = x;
51 pt.y = y;
52 pt.size = ti*a.size + t*b.size;
53 pt.angle = ti*a.angle + t*b.angle;
54 pt.gradient = ti*a.gradient + t*b.gradient;
55}
56
57gfx::Rect Intertwine::getStrokeBounds(ToolLoop* loop, const Stroke& stroke)
58{
59 return stroke.bounds();
60}
61
62void Intertwine::doTransformPoint(const Stroke::Pt& pt, ToolLoop* loop)
63{
64 loop->getPointShape()->transformPoint(loop, pt);
65}
66
67void Intertwine::doPointshapeStrokePt(const Stroke::Pt& pt, ToolLoop* loop)
68{
69 Symmetry* symmetry = loop->getSymmetry();
70 if (symmetry) {
71 // Convert the point to the sprite position so we can apply the
72 // symmetry transformation.
73 Stroke main_stroke;
74 main_stroke.addPoint(pt);
75
76 Strokes strokes;
77 symmetry->generateStrokes(main_stroke, strokes, loop);
78 for (const auto& stroke : strokes) {
79 // We call transformPoint() moving back each point to the cel
80 // origin.
81 doTransformPoint(stroke[0], loop);
82 }
83 }
84 else {
85 doTransformPoint(pt, loop);
86 }
87}
88
89// static
90void Intertwine::doPointshapePoint(int x, int y, ToolLoop* loop)
91{
92 Stroke::Pt pt(x, y);
93 pt.size = loop->getBrush()->size();
94 pt.angle = loop->getBrush()->angle();
95 loop->getIntertwine()->doPointshapeStrokePt(pt, loop);
96}
97
98// static
99void Intertwine::doPointshapePointDynamics(int x, int y, Intertwine::LineData* data)
100{
101 data->doStep(x, y);
102 data->loop->getIntertwine()->doPointshapeStrokePt(data->pt, data->loop);
103}
104
105// static
106void Intertwine::doPointshapeHline(int x1, int y, int x2, ToolLoop* loop)
107{
108 algo_line_perfect(x1, y, x2, y, loop, (AlgoPixel)doPointshapePoint);
109}
110
111// static
112void Intertwine::doPointshapeLineWithoutDynamics(int x1, int y1, int x2, int y2, ToolLoop* loop)
113{
114 Stroke::Pt a(x1, y1);
115 Stroke::Pt b(x2, y2);
116 a.size = b.size = loop->getBrush()->size();
117 a.angle = b.angle = loop->getBrush()->angle();
118 doPointshapeLine(a, b, loop);
119}
120
121void Intertwine::doPointshapeLine(const Stroke::Pt& a,
122 const Stroke::Pt& b, ToolLoop* loop)
123{
124 doc::AlgoLineWithAlgoPixel algo = getLineAlgo(loop, a, b);
125 LineData lineData(loop, a, b);
126 algo(a.x, a.y, b.x, b.y, (void*)&lineData, (AlgoPixel)doPointshapePointDynamics);
127}
128
129// static
130doc::AlgoLineWithAlgoPixel Intertwine::getLineAlgo(ToolLoop* loop,
131 const Stroke::Pt& a,
132 const Stroke::Pt& b)
133{
134 bool needsFixForLineBrush = false;
135 if ((loop->getBrush()->type() == kLineBrushType) &&
136 (a.size > 1.0 || b.size > 1.0)) {
137 if ((a.angle != 0.0f || b.angle != 0.0f) &&
138 (a.angle != b.angle)) {
139 needsFixForLineBrush = true;
140 }
141 else {
142 int angle = a.angle;
143 int p = SGN(b.x - a.x);
144 int q = SGN(a.y - b.y);
145 float rF = std::cos(PI * angle / 180);
146 float sF = std::sin(PI * angle / 180);
147 int r = SGN(rF);
148 int s = SGN(sF);
149 needsFixForLineBrush = ((p == q && r != s) ||
150 (p != q && r == s));
151 }
152 }
153
154 if (// When "Snap Angle" in being used or...
155 (int(loop->getModifiers()) & int(ToolLoopModifiers::kSquareAspect)) ||
156 // "Snap to Grid" is enabled
157 (loop->getController()->canSnapToGrid() && loop->getSnapToGrid())) {
158 // We prefer the perfect pixel lines that matches grid tiles
159 return (needsFixForLineBrush ? algo_line_perfect_with_fix_for_line_brush:
160 algo_line_perfect);
161 }
162 else {
163 // In other case we use the regular algorithm that is useful to
164 // draw continuous lines/strokes.
165 return (needsFixForLineBrush ? algo_line_continuous_with_fix_for_line_brush:
166 algo_line_continuous);
167 }
168}
169
170} // namespace tools
171} // namespace app
172