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