1 /*
2    Drawpile - a collaborative drawing program.
3 
4    Copyright (C) 2006-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 "brushpreview.h"
21 
22 #ifndef DESIGNER_PLUGIN
23 #include "core/point.h"
24 #include "core/layerstack.h"
25 #include "core/layerstackpixmapcacheobserver.h"
26 #include "core/layer.h"
27 #include "core/floodfill.h"
28 #include "brushes/shapes.h"
29 #include "brushes/brushengine.h"
30 #include "brushes/brushpainter.h"
31 #include "utils/icon.h"
32 #endif
33 
34 #include <QPaintEvent>
35 #include <QPainter>
36 #include <QEvent>
37 
38 namespace widgets {
39 
BrushPreview(QWidget * parent,Qt::WindowFlags f)40 BrushPreview::BrushPreview(QWidget *parent, Qt::WindowFlags f)
41 	: QFrame(parent,f)
42 {
43 	setAttribute(Qt::WA_NoSystemBackground);
44 	setMinimumSize(32,32);
45 }
46 
~BrushPreview()47 BrushPreview::~BrushPreview() {
48 #ifndef DESIGNER_PLUGIN
49 	delete m_previewCache;
50 	delete m_preview;
51 #endif
52 }
53 
setBrush(const brushes::ClassicBrush & brush)54 void BrushPreview::setBrush(const brushes::ClassicBrush &brush)
55 {
56 	m_brush = brush;
57 	m_needupdate = true;
58 	update();
59 }
60 
setPreviewShape(PreviewShape shape)61 void BrushPreview::setPreviewShape(PreviewShape shape)
62 {
63 	if(m_shape != shape) {
64 		m_shape = shape;
65 		m_needupdate = true;
66 		update();
67 	}
68 }
69 
setFloodFillTolerance(int tolerance)70 void BrushPreview::setFloodFillTolerance(int tolerance)
71 {
72 	if(m_fillTolerance != tolerance) {
73 		m_fillTolerance = tolerance;
74 		m_needupdate = true;
75 		update();
76 	}
77 }
78 
setFloodFillExpansion(int expansion)79 void BrushPreview::setFloodFillExpansion(int expansion)
80 {
81 	if(m_fillExpansion != expansion) {
82 		m_fillExpansion = expansion;
83 		m_needupdate = true;
84 		update();
85 	}
86 }
87 
setUnderFill(bool underfill)88 void BrushPreview::setUnderFill(bool underfill)
89 {
90 	if(m_underFill != underfill) {
91 		m_underFill = underfill;
92 		m_needupdate = true;
93 		update();
94 	}
95 }
96 
resizeEvent(QResizeEvent *)97 void BrushPreview::resizeEvent(QResizeEvent *)
98 {
99 	m_needupdate = true;
100 }
101 
changeEvent(QEvent *)102 void BrushPreview::changeEvent(QEvent *)
103 {
104 	m_needupdate = true;
105 	update();
106 }
107 
paintEvent(QPaintEvent * event)108 void BrushPreview::paintEvent(QPaintEvent *event)
109 {
110 #ifndef DESIGNER_PLUGIN
111 	if(m_needupdate)
112 		updatePreview();
113 
114 	QPainter painter(this);
115 	painter.drawPixmap(event->rect(), m_previewCache->getPixmap(event->rect()), event->rect());
116 #endif
117 }
118 
updatePreview()119 void BrushPreview::updatePreview()
120 {
121 #ifndef DESIGNER_PLUGIN
122 	if(!m_preview) {
123 		m_preview = new paintcore::LayerStack;
124 		m_previewCache = new paintcore::LayerStackPixmapCacheObserver(this);
125 		m_previewCache->attachToLayerStack(m_preview);
126 	}
127 
128 	auto layerstack = m_preview->editor(0);
129 
130 	if(m_preview->width() == 0) {
131 		const QSize size = contentsRect().size();
132 		layerstack.resize(0, size.width(), size.height(), 0);
133 		layerstack.createLayer(0, 0, QColor(0,0,0), false, false, QString());
134 
135 	} else if(m_preview->width() != contentsRect().width() || m_preview->height() != contentsRect().height()) {
136 		layerstack.resize(0, contentsRect().width() - m_preview->width(), contentsRect().height() - m_preview->height(), 0);
137 	}
138 
139 	const QRectF previewRect {
140 		m_preview->width()/8.0,
141 		m_preview->height()/4.0,
142 		m_preview->width()-m_preview->width()/4.0,
143 		m_preview->height()-m_preview->height()/2.0
144 	};
145 
146 	paintcore::PointVector pointvector;
147 
148 	switch(m_shape) {
149 	case Stroke: pointvector = brushes::shapes::sampleStroke(previewRect); break;
150 	case Line:
151 		pointvector
152 			<< paintcore::Point(previewRect.left(), previewRect.top(), 1.0)
153 			<< paintcore::Point(previewRect.right(), previewRect.bottom(), 1.0);
154 		break;
155 	case Rectangle: pointvector = brushes::shapes::rectangle(previewRect); break;
156 	case Ellipse: pointvector = brushes::shapes::ellipse(previewRect); break;
157 	case FloodFill:
158 	case FloodErase: pointvector = brushes::shapes::sampleBlob(previewRect); break;
159 	}
160 
161 	QColor bgColor = icon::isDark(m_brush.color()) ? QColor(250, 250, 250) : QColor(32, 32, 32);
162 	QColor layerColor = Qt::transparent;
163 	enum class LayerFill {
164 		Solid,
165 		RainbowBars,
166 		RainbowDabs
167 	};
168 	auto fgStyle = m_brush.smudge1()>0 ? LayerFill::RainbowBars : LayerFill::Solid;
169 
170 	brushes::ClassicBrush brush = m_brush;
171 
172 	if(brush.blendingMode() == paintcore::BlendMode::MODE_ERASE) {
173 		layerColor = bgColor;
174 		bgColor = Qt::transparent;
175 
176 	} else if(brush.blendingMode() == paintcore::BlendMode::MODE_COLORERASE) {
177 		// Color-erase mode: use fg color as background
178 		bgColor = Qt::transparent;
179 		layerColor = brushColor();
180 
181 	} else if(!paintcore::findBlendMode(brush.blendingMode()).flags.testFlag(paintcore::BlendMode::IncrOpacity)) {
182 		fgStyle = LayerFill::RainbowDabs;
183 		bgColor = Qt::transparent;
184 	}
185 
186 	if(m_shape == FloodFill) {
187 		brush.setColor(bgColor);
188 		bgColor = Qt::transparent;
189 
190 	} else if(m_shape == FloodErase) {
191 		layerColor = brush.color();
192 		brush.setColor(bgColor);
193 		bgColor = Qt::transparent;
194 	}
195 
196 	auto layer = layerstack.getEditableLayerByIndex(0);
197 	layerstack.setBackground(paintcore::Tile(bgColor));
198 	layer.putTile(0, 0, 99999, paintcore::Tile(layerColor));
199 
200 	if(fgStyle == LayerFill::Solid) {
201 
202 		// When using the "behind" mode, draw something in the foreground to show off the effect
203 		if(brush.blendingMode() == paintcore::BlendMode::MODE_BEHIND) {
204 			const int w = layer->width();
205 			const int h = layer->height();
206 			const int b = qMax(2, w / 20);
207 			layer.fillRect(QRect{w/4*1-b/2, 0, b, h}, Qt::black, paintcore::BlendMode::MODE_NORMAL);
208 			layer.fillRect(QRect{w/4*2-b/2, 0, b, h}, Qt::gray, paintcore::BlendMode::MODE_NORMAL);
209 			layer.fillRect(QRect{w/4*3-b/2, 0, b, h}, Qt::white, paintcore::BlendMode::MODE_NORMAL);
210 		}
211 
212 	} else if(fgStyle == LayerFill::RainbowDabs) {
213 		const int w = layer->width();
214 		const int h = layer->height();
215 		const uint8_t d = qBound(10, h*2/3, 255);
216 		const int x0 = d, x1 = w - d;
217 		const int step = d * 70 / 100;
218 		const int huestep = 359 / ((x1-x0) / step);
219 		int hue = 0;
220 		for(int x=x0;x<x1;x+=step, hue+=huestep) {
221 			protocol::DrawDabsPixel dab(
222 				protocol::DabShape::Round,
223 				1,
224 				layer->id(),
225 				x, h/2,
226 				QColor::fromHsv(hue, 160, 220).rgb() & 0x00ffffff,
227 				paintcore::BlendMode::MODE_NORMAL,
228 				protocol::PixelBrushDabVector { protocol::PixelBrushDab {0, 0, d, 255} }
229 			);
230 			brushes::drawBrushDabsDirect(dab, layer);
231 		}
232 
233 	} else if(fgStyle == LayerFill::RainbowBars) {
234 		const int w = layer->width();
235 		const int h = layer->height();
236 		const int bars = 5;
237 		const int bw = w/bars;
238 
239 		for(int i=0;i<bars;++i) {
240 			layer.fillRect(QRect{i*bw, 0, bw, h}, QColor::fromHsv(i*(359/bars), 160, 220), paintcore::BlendMode::MODE_NORMAL);
241 		}
242 	}
243 
244 	// Draw the brush preview shape
245 	{
246 		brushes::BrushEngine brushengine;
247 		brushengine.setBrush(1, 1, brush);
248 
249 		for(int i=0;i<pointvector.size();++i)
250 			brushengine.strokeTo(pointvector[i], layer.layer());
251 		brushengine.endStroke();
252 
253 		const auto dabs = brushengine.takeDabs();
254 		for(int i=0;i<dabs.size();++i)
255 			brushes::drawBrushDabsDirect(*dabs.at(i), layer);
256 
257 		layer.mergeSublayer(1);
258 	}
259 
260 	// Do the flood fill
261 	// In flood fill mode, the shape drawn with the brush creates
262 	// a closed area that will be filled here.
263 	if(m_shape == FloodFill || m_shape == FloodErase) {
264 		paintcore::FillResult fr = paintcore::floodfill(
265 			m_preview,
266 			previewRect.center().toPoint(),
267 			m_shape == FloodFill ? brushColor() : QColor(),
268 			m_fillTolerance,
269 			0,
270 			false,
271 			360000);
272 
273 		if(m_fillExpansion>0)
274 			fr = paintcore::expandFill(fr, m_fillExpansion, brushColor());
275 		if(!fr.image.isNull())
276 			layer.putImage(fr.x, fr.y, fr.image, m_shape == FloodFill ? (m_underFill ? paintcore::BlendMode::MODE_BEHIND : paintcore::BlendMode::MODE_NORMAL) : paintcore::BlendMode::MODE_ERASE);
277 	}
278 
279 	m_needupdate=false;
280 #endif
281 }
282 
mouseDoubleClickEvent(QMouseEvent *)283 void BrushPreview::mouseDoubleClickEvent(QMouseEvent*)
284 {
285 	emit requestColorChange();
286 }
287 
288 }
289 
290