Krita Source Code Documentation
Loading...
Searching...
No Matches
hairy_brush.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008-2010 Lukáš Tvrdý <lukast.dev@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7#include "hairy_brush.h"
8
9#include <KoColor.h>
10#include <KoColorSpace.h>
13
14#include <QVariant>
15#include <QVector>
16
17#include <kis_types.h>
21
22#include <kis_global.h>
23
24#include <cmath>
25#include <ctime>
26
27
29{
30 m_counter = 0;
31 m_lastAngle = 0.0;
32 m_oldPressure = 1.0f;
33
34 m_saturationId = -1;
35}
36
38{
39 delete m_transfo;
40 qDeleteAll(m_bristles.begin(), m_bristles.end());
41 m_bristles.clear();
42}
43
44
57
59{
60 int width = dab->bounds().width();
61 int height = dab->bounds().height();
62
63 int centerX = width * 0.5;
64 int centerY = height * 0.5;
65
66 // make mask
67 Bristle * bristle = nullptr;
68 qreal alpha;
69
70 quint8 * dabPointer = dab->data();
71 quint8 pixelSize = dab->pixelSize();
72 const KoColorSpace * cs = dab->colorSpace();
73 KoColor bristleColor(cs);
74
75 KisRandomSource randomSource(0);
76
77 for (int y = 0; y < height; y++) {
78 for (int x = 0; x < width; x++) {
79 alpha = cs->opacityF(dabPointer);
80 if (alpha != 0.0) {
81 if (density == 1.0 || randomSource.generateNormalized() <= density) {
82 memcpy(bristleColor.data(), dabPointer, pixelSize);
83
84 bristle = new Bristle(x - centerX, y - centerY, alpha); // using value from image as length of bristle
85 bristle->setColor(bristleColor);
86
87 m_bristles.append(bristle);
88 }
89 }
90 dabPointer += pixelSize;
91 }
92 }
93}
94
95
96void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation)
97{
98 m_counter++;
99
100 qreal x1 = pi1.pos().x();
101 qreal y1 = pi1.pos().y();
102
103 qreal x2 = pi2.pos().x();
104 qreal y2 = pi2.pos().y();
105
106 qreal dx = x2 - x1;
107 qreal dy = y2 - y1;
108
109 // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to
110 // not computing the drag vector properly in paintBezierLine when smoothing is used
111 //qreal angle = atan2(dy, dx);
112 qreal angle = rotation;
113
114 qreal mousePressure = 1.0;
115 if (m_properties->useMousePressure) { // want pressure from mouse movement
116 qreal distance = sqrt(dx * dx + dy * dy);
117 mousePressure = (1.0 - computeMousePressure(distance));
118 scale *= mousePressure;
119 }
120 // this pressure controls shear and ink depletion
121 qreal pressure = mousePressure * (pi2.pressure() * 2);
122
123 Bristle *bristle = 0;
124 KoColor bristleColor(dab->colorSpace());
125
127
128 m_dab = dab;
129
130 // initialization block
131 if (firstStroke()) {
132 initAndCache();
133 }
134
135 /*If this is first time the brush touches the canvas and
136 we are using soak ink while ink depletion is enabled...*/
139 if (layer) {
140 colorifyBristles(layer, pi1.pos());
141 }
142 else {
143 dbgKrita << "Can't soak the ink from the layer";
144 }
145 }
146
147 KisRandomSourceSP randomSource = pi2.randomSource();
148
149 qreal fx1, fy1, fx2, fy2;
150 qreal randomX, randomY;
151 qreal shear;
152
153 float inkDepletion = 0.0;
154 int inkDepletionSize = m_properties->inkDepletionCurve.size();
155 int bristleCount = m_bristles.size();
156 int bristlePathSize;
157 qreal threshold = 1.0 - pi2.pressure();
158 for (int i = 0; i < bristleCount; i++) {
159
160 if (!m_bristles.at(i)->enabled()) continue;
161 bristle = m_bristles[i];
162
163 randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
164 randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor;
165
166 shear = pressure * m_properties->shearFactor;
167
168 m_transform.reset();
169 m_transform.rotateRadians(-angle);
170 m_transform.scale(scale, scale);
171 m_transform.translate(randomX, randomY);
172 m_transform.shear(shear, shear);
173
175 // transform start dab
176 m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1);
177 // transform end dab
178 m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
179 }
180 else {
181 // continue the path of the bristle from the previous position
182 fx1 = bristle->prevX();
183 fy1 = bristle->prevY();
184 m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2);
185 }
186 // remember the end point
187 bristle->setPrevX(fx2);
188 bristle->setPrevY(fy2);
189
190 // all coords relative to device position
191 fx1 += x1;
192 fy1 += y1;
193
194 fx2 += x2;
195 fy2 += y2;
196
197 if (m_properties->threshold && (bristle->length() < threshold)) continue;
198 // paint between first and last dab
199 const QVector<QPointF> bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0);
200 bristlePathSize = m_trajectory.size();
201
202 // avoid overlapping bristle caps with antialias on
203 if (m_properties->antialias) {
204 bristlePathSize -= 1;
205 }
206
207 memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize);
208 for (int i = 0; i < bristlePathSize ; i++) {
209
211 inkDepletion = fetchInkDepletion(bristle, inkDepletionSize);
212
213 if (m_properties->useSaturation && m_transfo != 0) {
214 saturationDepletion(bristle, bristleColor, pressure, inkDepletion);
215 }
216
218 opacityDepletion(bristle, bristleColor, pressure, inkDepletion);
219 }
220
221 }
222 else {
223 if (bristleColor.opacityU8() != 0) {
224 bristleColor.setOpacity(bristle->length());
225 }
226 }
227
228 addBristleInk(bristle, bristlePath.at(i), bristleColor);
229 bristle->setInkAmount(1.0 - inkDepletion);
230 bristle->upIncrement();
231 }
232
233 }
234 m_dab = nullptr;
235 m_dabAccessor = nullptr;
236}
237
238
239inline qreal HairyBrush::fetchInkDepletion(Bristle* bristle, int inkDepletionSize)
240{
241 if (bristle->counter() >= inkDepletionSize - 1) {
242 return m_properties->inkDepletionCurve[inkDepletionSize - 1];
243 } else {
244 return m_properties->inkDepletionCurve[bristle->counter()];
245 }
246}
247
248
249void HairyBrush::saturationDepletion(Bristle * bristle, KoColor &bristleColor, qreal pressure, qreal inkDepletion)
250{
251 qreal saturation;
253 // new weighted way (experiment)
254 saturation = (
255 (pressure * m_properties->pressureWeight) +
256 (bristle->length() * m_properties->bristleLengthWeight) +
258 ((1.0 - inkDepletion) * m_properties->inkDepletionWeight)) - 1.0;
259 }
260 else {
261 // old way of computing saturation
262 saturation = (
263 pressure *
264 bristle->length() *
265 bristle->inkAmount() *
266 (1.0 - inkDepletion)) - 1.0;
267
268 }
272 m_transfo->setParameter(3, 1);//sets the type to
273 m_transfo->setParameter(4, false);//sets the colorize to none.
274 m_transfo->transform(bristleColor.data(), bristleColor.data() , 1);
275}
276
277void HairyBrush::opacityDepletion(Bristle* bristle, KoColor& bristleColor, qreal pressure, qreal inkDepletion)
278{
279 qreal opacity = OPACITY_OPAQUE_F;
281 opacity = pressure * m_properties->pressureWeight +
284 (1.0 - inkDepletion) * m_properties->inkDepletionWeight;
285 }
286 else {
287 opacity =
288 bristle->length() *
289 bristle->inkAmount();
290 }
291
292 opacity = kisBoundFast(0.0, opacity, 1.0);
293 bristleColor.setOpacity(opacity);
294}
295
296inline void HairyBrush::addBristleInk(Bristle *bristle,const QPointF &pos, const KoColor &color)
297{
298 Q_UNUSED(bristle);
299 if (m_properties->antialias) {
301 paintParticle(pos, color);
302 } else {
303 paintParticle(pos, color, 1.0);
304 }
305 }
306 else {
307 int ix = qRound(pos.x());
308 int iy = qRound(pos.y());
310 plotPixel(ix, iy, color);
311 }
312 else {
313 darkenPixel(ix, iy, color);
314 }
315 }
316}
317
318void HairyBrush::paintParticle(QPointF pos, const KoColor& color, qreal weight)
319{
320 // opacity top left, right, bottom left, right
321 quint8 opacity = color.opacityU8();
322 opacity *= weight;
323
324 int ipx = int (pos.x());
325 int ipy = int (pos.y());
326 qreal fx = qAbs(pos.x() - ipx);
327 qreal fy = qAbs(pos.y() - ipy);
328
329 quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
330 quint8 btr = qRound((fx) * (1.0 - fy) * opacity);
331 quint8 bbl = qRound((1.0 - fx) * (fy) * opacity);
332 quint8 bbr = qRound((fx) * (fy) * opacity);
333
334 const KoColorSpace * cs = m_dab->colorSpace();
335
336 m_dabAccessor->moveTo(ipx , ipy);
337 btl = quint8(kisBoundFast<quint16>(OPACITY_TRANSPARENT_U8, btl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
338 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
339 cs->setOpacity(m_dabAccessor->rawData(), btl, 1);
340
341 m_dabAccessor->moveTo(ipx + 1, ipy);
342 btr = quint8(kisBoundFast<quint16>(OPACITY_TRANSPARENT_U8, btr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
343 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
344 cs->setOpacity(m_dabAccessor->rawData(), btr, 1);
345
346 m_dabAccessor->moveTo(ipx, ipy + 1);
347 bbl = quint8(kisBoundFast<quint16>(OPACITY_TRANSPARENT_U8, bbl + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
348 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
349 cs->setOpacity(m_dabAccessor->rawData(), bbl, 1);
350
351 m_dabAccessor->moveTo(ipx + 1, ipy + 1);
352 bbr = quint8(kisBoundFast<quint16>(OPACITY_TRANSPARENT_U8, bbr + cs->opacityU8(m_dabAccessor->rawData()), OPACITY_OPAQUE_U8));
353 memcpy(m_dabAccessor->rawData(), color.data(), cs->pixelSize());
354 cs->setOpacity(m_dabAccessor->rawData(), bbr, 1);
355}
356
357void HairyBrush::paintParticle(QPointF pos, const KoColor& color)
358{
359 // opacity top left, right, bottom left, right
360 memcpy(m_color.data(), color.data(), m_pixelSize);
361 quint8 opacity = color.opacityU8();
362
363 int ipx = int (pos.x());
364 int ipy = int (pos.y());
365 qreal fx = qAbs(pos.x() - ipx);
366 qreal fy = qAbs(pos.y() - ipy);
367
368 quint8 btl = qRound((1.0 - fx) * (1.0 - fy) * opacity);
369 quint8 btr = qRound((fx) * (1.0 - fy) * opacity);
370 quint8 bbl = qRound((1.0 - fx) * (fy) * opacity);
371 quint8 bbr = qRound((fx) * (fy) * opacity);
372
373 m_color.setOpacity(btl);
374 plotPixel(ipx , ipy, m_color);
375
376 m_color.setOpacity(btr);
377 plotPixel(ipx + 1 , ipy, m_color);
378
379 m_color.setOpacity(bbl);
380 plotPixel(ipx , ipy + 1, m_color);
381
382 m_color.setOpacity(bbr);
383 plotPixel(ipx + 1 , ipy + 1, m_color);
384}
385
386
387inline void HairyBrush::plotPixel(int wx, int wy, const KoColor &color)
388{
389 m_dabAccessor->moveTo(wx, wy);
391}
392
393inline void HairyBrush::darkenPixel(int wx, int wy, const KoColor &color)
394{
395 m_dabAccessor->moveTo(wx, wy);
397 memcpy(m_dabAccessor->rawData(), color.data(), m_pixelSize);
398 }
399}
400
402{
403 static const double scale = 20.0;
404 static const double minPressure = 0.02;
405
406 double oldPressure = m_oldPressure;
407
408 double factor = 1.0 - distance / scale;
409 if (factor < 0.0) factor = 0.0;
410
411 double result = ((4.0 * oldPressure) + minPressure + factor) / 5.0;
412
413 m_oldPressure = result;
414 return result;
415}
416
417
419{
420 KoColor bristleColor(m_dab->colorSpace());
421 KisCrossDeviceColorSamplerInt colorSampler(source, bristleColor);
422
423 Bristle *b = 0;
424 int size = m_bristles.size();
425 for (int i = 0; i < size; i++) {
426 b = m_bristles[i];
427 int x = qRound(b->x() + point.x());
428 int y = qRound(b->y() + point.y());
429
430 colorSampler.sampleOldColor(x, y, bristleColor.data());
431 b->setColor(bristleColor);
432 }
433
434}
435
436
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
const quint8 OPACITY_TRANSPARENT_U8
const qreal OPACITY_OPAQUE_F
const quint8 OPACITY_OPAQUE_U8
const QString COMPOSITE_OVER
qreal distance(const QPointF &p1, const QPointF &p2)
int counter() const
Definition bristle.h:45
float length() const
Definition bristle.h:37
float inkAmount() const
Definition bristle.h:53
void setColor(const KoColor &color)
Definition bristle.cpp:49
bool firstStroke() const
QHash< QString, QVariant > m_params
void paintParticle(QPointF pos, const KoColor &color, qreal weight)
paint wu particle by copying the color and setup just the opacity, weight is complementary to opacity...
double m_oldPressure
const KoCompositeOp * m_compositeOp
double m_lastAngle
QTransform m_transform
QVector< Bristle * > m_bristles
void plotPixel(int wx, int wy, const KoColor &color)
composite single pixel to dab
KisPaintDeviceSP m_dab
void initAndCache()
void opacityDepletion(Bristle *bristle, KoColor &bristleColor, qreal pressure, qreal inkDepletion)
simulate running out of ink through opacity decreasing
const KisHairyProperties * m_properties
void saturationDepletion(Bristle *bristle, KoColor &bristleColor, qreal pressure, qreal inkDepletion)
simulate running out of saturation
KoColorTransformation * m_transfo
void colorifyBristles(KisPaintDeviceSP source, QPointF point)
similar to sample input color in spray
void addBristleInk(Bristle *bristle, const QPointF &pos, const KoColor &color)
paints single bristle
int m_saturationId
KoColor m_color
Trajectory m_trajectory
double computeMousePressure(double distance)
compute mouse pressure according distance
void darkenPixel(int wx, int wy, const KoColor &color)
check the opacity of dab pixel and if the opacity is less than color, it will copy color to dab
void fromDabWithDensity(KisFixedPaintDeviceSP dab, qreal density)
set the shape of the bristles according the dab
KisRandomAccessorSP m_dabAccessor
void paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation)
qreal fetchInkDepletion(Bristle *bristle, int inkDepletionSize)
fetch actual ink status according depletion curve
quint32 m_pixelSize
virtual quint8 * rawData()=0
void sampleOldColor(typename Traits::coord_type x, typename Traits::coord_type y, quint8 *dst)
const KoColorSpace * colorSpace() const
quint8 bristleInkAmountWeight
Definition hairy_brush.h:46
quint8 inkDepletionWeight
Definition hairy_brush.h:47
quint8 bristleLengthWeight
Definition hairy_brush.h:45
QVector< qreal > inkDepletionCurve
Definition hairy_brush.h:31
const KoColorSpace * colorSpace() const
KisRandomAccessorSP createRandomAccessorNG()
KisRandomSourceSP randomSource() const
const QPointF & pos() const
qreal pressure() const
The pressure of the value (from 0.0 to 1.0)
virtual void moveTo(qint32 x, qint32 y)=0
qreal generateNormalized() const
virtual quint32 pixelSize() const =0
virtual qreal opacityF(const quint8 *pixel) const =0
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
virtual quint8 opacityU8(const quint8 *pixel) const =0
KoColorTransformation * createColorTransformation(const QString &id, const QHash< QString, QVariant > &parameters) const
const KoCompositeOp * compositeOp(const QString &id, const KoColorSpace *srcSpace=nullptr) const
virtual int parameterId(const QString &name) const
virtual void setParameter(int id, const QVariant &parameter)
virtual void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const =0
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
quint8 opacityU8() const
Definition KoColor.cpp:341
const QVector< QPointF > & getLinearTrajectory(const QPointF &start, const QPointF &end, double space)
int size() const
Definition trajectory.h:23
#define dbgKrita
Definition kis_debug.h:45
constexpr const T & kisBoundFast(const T &min, const T &val, const T &max)
Definition kis_global.h:37
void composite(quint8 *dstRowStart, qint32 dstRowStride, const quint8 *srcRowStart, qint32 srcRowStride, const quint8 *maskRowStart, qint32 maskRowStride, qint32 rows, qint32 numColumns, float opacity, const QBitArray &channelFlags=QBitArray()) const