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