1 /*
2 Drawpile - a collaborative drawing program.
3
4 Copyright (C) 2018-2019 Calle Laakkonen
5
6 Drawpile is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Drawpile is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Drawpile. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "classicbrushstate.h"
21 #include "core/layerstack.h"
22 #include "core/brushmask.h"
23 #include "core/layer.h"
24
25 namespace brushes {
26
ClassicBrushState()27 ClassicBrushState::ClassicBrushState()
28 : m_contextId(0), m_layerId(0),
29 m_length(0), m_smudgeDistance(0), m_pendown(false),
30 m_lastDab(nullptr), m_lastDabX(0), m_lastDabY(0)
31 {
32 }
33
setBrush(const ClassicBrush & brush)34 void ClassicBrushState::setBrush(const ClassicBrush &brush)
35 {
36 m_brush = brush;
37
38 QColor c = m_brush.color();
39 if(brush.incremental() || brush.smudge1() > 0.0) {
40 // Note: when smudging is used, we must use incremental mode
41 // because we currently do not sample colors from sublayers.
42 c.setAlpha(0);
43
44 } else {
45 // If brush alpha is nonzero, indirect drawing mode
46 // is used and the alpha is used as the overall transparency
47 // of the entire stroke.
48 // TODO this doesn't work right. We should use alpha-darken mode
49 // and set the opacity range properly
50 c.setAlphaF(m_brush.opacity1());
51
52 m_brush.setOpacity(1.0);
53 if(brush.useOpacityPressure())
54 m_brush.setOpacity2(0.0);
55 }
56 m_brush.setColor(c);
57 m_smudgedColor = c;
58
59 if(m_pendown)
60 qWarning("Brush changed mid-stroke!");
61 }
62
addOffset(int x,int y)63 void ClassicBrushState::addOffset(int x, int y)
64 {
65 if(m_pendown) {
66 m_lastPoint += QPointF(x, y);
67 }
68 }
69
strokeTo(const paintcore::Point & to,const paintcore::Layer * sourceLayer)70 void ClassicBrushState::strokeTo(const paintcore::Point &to, const paintcore::Layer *sourceLayer)
71 {
72 if(m_pendown) {
73 // Stroke in progress: draw a line
74 qreal dx = to.x() - m_lastPoint.x();
75 qreal dy = to.y() - m_lastPoint.y();
76 const qreal dist = hypot(dx, dy);
77 dx = dx / dist;
78 dy = dy / dist;
79 const qreal dp = (to.pressure() - m_lastPoint.pressure()) / dist;
80
81 const qreal spacing0 = qMax(1.0, m_brush.spacingDist(m_lastPoint.pressure()));
82 qreal i;
83 if(m_length>=spacing0)
84 i = 0;
85 else if(m_length==0)
86 i = spacing0;
87 else
88 i = m_length;
89
90 paintcore::Point p(m_lastPoint.x() + dx*i, m_lastPoint.y() + dy*i, qBound(0.0, m_lastPoint.pressure() + dp*i, 1.0));
91
92 while(i<=dist) {
93 const qreal spacing = qMax(1.0, m_brush.spacingDist(p.pressure()));
94 const qreal smudge = m_brush.smudge(p.pressure());
95
96 if(++m_smudgeDistance > m_brush.resmudge() && smudge>0 && sourceLayer) {
97 const QColor sampled = sourceLayer->colorAt(p.x(), p.y(), qRound(m_brush.size(p.pressure())));
98
99 if(sampled.isValid()) {
100 const qreal a = sampled.alphaF() * smudge;
101
102 m_smudgedColor = QColor::fromRgbF(
103 m_smudgedColor.redF() * (1-a) + sampled.redF() * a,
104 m_smudgedColor.greenF() * (1-a) + sampled.greenF() * a,
105 m_smudgedColor.blueF() * (1-a) + sampled.blueF() * a,
106 0
107 );
108 }
109
110 m_smudgeDistance = 0;
111 }
112
113 if(m_smudgedColor.isValid())
114 addDab(p, m_smudgedColor.rgba());
115
116 p.rx() += dx * spacing;
117 p.ry() += dy * spacing;
118 p.setPressure(qBound(0.0, p.pressure() + dp * spacing, 1.0));
119 i += spacing;
120 }
121 m_length = i-dist;
122
123 } else {
124 // Start a new stroke
125 m_pendown = true;
126 if(m_brush.isColorPickMode() && sourceLayer && m_brush.blendingMode() != paintcore::BlendMode::MODE_ERASE) {
127 m_smudgedColor = sourceLayer->colorAt(to.x(), to.y(), qRound(m_brush.size(to.pressure())));
128 }
129
130 if(m_smudgedColor.isValid())
131 addDab(to, m_smudgedColor.rgba());
132 }
133
134 m_lastPoint = to;
135 }
136
addDab(const paintcore::Point & point,quint32 color)137 void ClassicBrushState::addDab(const paintcore::Point &point, quint32 color)
138 {
139 const int x = point.x() * 4;
140 const int y = point.y() * 4;
141 const int opacity = m_brush.opacity(point.pressure()) * 255;
142
143 if(opacity == 0)
144 return;
145
146 if(!m_lastDab
147 || m_lastDab->color() != color
148 || qAbs(x - m_lastDabX) > protocol::ClassicBrushDab::MAX_XY_DELTA
149 || qAbs(y - m_lastDabY) > protocol::ClassicBrushDab::MAX_XY_DELTA
150 || m_lastDab->dabs().size() >= protocol::DrawDabsClassic::MAX_DABS
151 ) {
152 m_lastDab = new protocol::DrawDabsClassic(
153 m_contextId,
154 m_layerId,
155 x,
156 y,
157 color,
158 m_brush.blendingMode()
159 );
160 m_dabs << protocol::MessagePtr(m_lastDab);
161 m_lastDabX = x;
162 m_lastDabY = y;
163 }
164
165 m_lastDab->dabs() << protocol::ClassicBrushDab {
166 static_cast<decltype(protocol::ClassicBrushDab::x)>(x - m_lastDabX),
167 static_cast<decltype(protocol::ClassicBrushDab::y)>(y - m_lastDabY),
168 static_cast<decltype(protocol::ClassicBrushDab::size)>(m_brush.size(point.pressure()) * 256),
169 static_cast<decltype(protocol::ClassicBrushDab::hardness)>(m_brush.hardness(point.pressure()) * 255),
170 static_cast<decltype(protocol::ClassicBrushDab::opacity)>(opacity)
171 };
172
173 m_lastDabX = x;
174 m_lastDabY = y;
175 }
176
endStroke()177 void ClassicBrushState::endStroke()
178 {
179 m_pendown = false;
180 m_length = 0;
181 m_smudgeDistance = 0;
182 m_smudgedColor = m_brush.color();
183 }
184
185 }
186