Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_auto_brush.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2004, 2007-2009 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
4 * SPDX-FileCopyrightText: 2012 Sven Langkamp <sven.langkamp@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "kis_auto_brush.h"
10
11#include <kis_debug.h>
12#include <math.h>
13
14#include <QPainterPath>
15#include <QRect>
16#include <QDomElement>
17#include <QBuffer>
18#include <QFile>
19
20#include <KoColor.h>
21#include <KoColorSpace.h>
23
24#include <kis_datamanager.h>
26#include <kis_paint_device.h>
28#include <kis_mask_generator.h>
29#include <kis_boundary.h>
32#include "kis_algebra_2d.h"
34
35#if defined(_WIN32) || defined(_WIN64)
36#include <stdlib.h>
37#define srand48 srand
38inline double drand48()
39{
40 return double(rand()) / RAND_MAX;
41}
42#endif
43
46 : randomness(0)
47 , density(1.0)
49 {}
50
51 Private(const Private &rhs)
52 : shape(rhs.shape->clone())
54 , density(rhs.density)
56 {
57 }
58
59
60 QScopedPointer<KisMaskGenerator> shape;
62 qreal density;
64};
65
66KisAutoBrush::KisAutoBrush(KisMaskGenerator* as, qreal angle, qreal randomness, qreal density)
67 : KisBrush(),
68 d(new Private)
69{
70 d->shape.reset(as);
71 d->randomness = randomness;
72 d->density = density;
73 d->idealThreadCountCached = QThread::idealThreadCount();
75
76 {
84 setWidth(qMax(qreal(1.0), d->shape->width()));
85 setHeight(qMax(qreal(1.0), d->shape->height()));
86
87 const int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation());
88 const int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation());
89
90 setWidth(qMax(1, width));
91 setHeight(qMax(1, height));
92 }
93
94 // We don't initialize setBrushTipImage(), because
95 // auto brush doesn't use image pyramid. And generation
96 // of a full-scaled QImage may cause a significant delay
97 // in the beginning of the stroke
98
101}
102
106
108{
109 return true;
110}
111
112bool KisAutoBrush::loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface)
113{
114 Q_UNUSED(dev);
115 Q_UNUSED(resourcesInterface);
116 return false;
117}
118
119bool KisAutoBrush::saveToDevice(QIODevice *dev) const
120{
121 Q_UNUSED(dev);
122 return false;
123}
124
126{
127 bool result = false;
128
129 if (d->shape->id() == SoftId.id()) {
130 result = d->shape->valueAt(0,0) > 0.05 * 255;
131 }
132
133 return result;
134}
135
137{
139 KisDabShape inverseTransform(1.0 / scale(), 1.0, -angle());
140
142 dev = new KisFixedPaintDevice(cs);
143 mask(dev, KoColor(Qt::black, cs), inverseTransform, KisPaintInformation());
144
145 return dev;
146}
147
149{
150 return d->shape->diameter();
151}
152
154{
155 d->shape->setDiameter(value);
156}
157
159 : KisBrush(rhs)
160 , d(new Private(*rhs.d))
161{
162}
163
165{
166 return KoResourceSP(new KisAutoBrush(*this));
167}
168
169/* It's difficult to predict the mask height exactly when there are
170 * more than 2 spikes, so we return an upperbound instead. */
171static KisDabShape lieAboutDabShape(KisDabShape const& shape, int spikes)
172{
173 return spikes > 2 ? KisDabShape(shape.scale(), 1.0, shape.rotation()) : shape;
174}
175
177 qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
178{
180 lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info);
181}
182
184 qreal subPixelX, qreal subPixelY, const KisPaintInformation& info) const
185{
186 return KisBrush::maskWidth(
187 lieAboutDabShape(shape, maskGenerator()->spikes()), subPixelX, subPixelY, info);
188}
189
191{
193}
194
196 return 0; // The autobrush does NOT support images!
197}
198
199
200inline void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size)
201{
209 int block1 = size / 8;
210 int block2 = size % 8;
211
212 quint32 *src = reinterpret_cast<quint32*>(color);
213 quint32 *dst = reinterpret_cast<quint32*>(buf);
214
215 // check whether all buffers are 4 bytes aligned
216 // (uncomment if experience some problems)
217 // Q_ASSERT(((qint64)src & 3) == 0);
218 // Q_ASSERT(((qint64)dst & 3) == 0);
219
220 for (int i = 0; i < block1; i++) {
221 *dst = *src;
222 *(dst + 1) = *src;
223 *(dst + 2) = *src;
224 *(dst + 3) = *src;
225 *(dst + 4) = *src;
226 *(dst + 5) = *src;
227 *(dst + 6) = *src;
228 *(dst + 7) = *src;
229
230 dst += 8;
231 }
232
233 for (int i = 0; i < block2; i++) {
234 *dst = *src;
235 dst++;
236 }
237}
238
239inline void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize)
240{
248 int block1 = size / 8;
249 int block2 = size % 8;
250
251 for (int i = 0; i < block1; i++) {
252 quint8 *d1 = buf;
253 quint8 *d2 = buf + pixelSize;
254 quint8 *d3 = buf + 2 * pixelSize;
255 quint8 *d4 = buf + 3 * pixelSize;
256 quint8 *d5 = buf + 4 * pixelSize;
257 quint8 *d6 = buf + 5 * pixelSize;
258 quint8 *d7 = buf + 6 * pixelSize;
259 quint8 *d8 = buf + 7 * pixelSize;
260
261 for (int j = 0; j < pixelSize; j++) {
262 *(d1 + j) = color[j];
263 *(d2 + j) = color[j];
264 *(d3 + j) = color[j];
265 *(d4 + j) = color[j];
266 *(d5 + j) = color[j];
267 *(d6 + j) = color[j];
268 *(d7 + j) = color[j];
269 *(d8 + j) = color[j];
270 }
271
272 buf += 8 * pixelSize;
273 }
274
275 for (int i = 0; i < block2; i++) {
276 memcpy(buf, color, pixelSize);
277 buf += pixelSize;
278 }
279}
280
282 KisBrush::ColoringInformation* coloringInformation,
283 KisDabShape const& shape,
284 const KisPaintInformation& info,
285 double subPixelX , double subPixelY, qreal softnessFactor, qreal lightnessStrength) const
286{
287 Q_UNUSED(info);
288 Q_UNUSED(lightnessStrength);
289
290 // Generate the paint device from the mask
291 const KoColorSpace* cs = dst->colorSpace();
292 quint32 pixelSize = cs->pixelSize();
293
294 // mask dimension methods already includes KisBrush::angle()
295 int dstWidth = maskWidth(shape, subPixelX, subPixelY, info);
296 int dstHeight = maskHeight(shape, subPixelX, subPixelY, info);
297 QPointF hotSpot = this->hotSpot(shape, info);
298
299 // mask size and hotSpot function take the KisBrush rotation into account
300 qreal angle = shape.rotation() + KisBrush::angle();
301
302 // if there's coloring information, we merely change the alpha: in that case,
303 // the dab should be big enough!
304 if (coloringInformation) {
305 // new bounds. we don't care if there is some extra memory occupied.
306 dst->setRect(QRect(0, 0, dstWidth, dstHeight));
308 }
309 else {
310 KIS_SAFE_ASSERT_RECOVER_RETURN(dst->bounds().width() >= dstWidth &&
311 dst->bounds().height() >= dstHeight);
312 }
313
314 KIS_SAFE_ASSERT_RECOVER_RETURN(coloringInformation);
315
316 quint8* dabPointer = dst->data();
317
318 quint8* color = 0;
319 if (dynamic_cast<PlainColoringInformation*>(coloringInformation)) {
320 color = const_cast<quint8*>(coloringInformation->color());
321 }
322
323 double centerX = hotSpot.x() - 0.5 + subPixelX;
324 double centerY = hotSpot.y() - 0.5 + subPixelY;
325
326 d->shape->setSoftness(softnessFactor); // softness must be set first
327 d->shape->setScale(shape.scaleX(), shape.scaleY());
328
329 if (!color) {
330 for (int y = 0; y < dstHeight; y++) {
331 for (int x = 0; x < dstWidth; x++) {
332 memcpy(dabPointer, coloringInformation->color(), pixelSize);
333 coloringInformation->nextColumn();
334 dabPointer += pixelSize;
335 }
336 coloringInformation->nextRow();
337 }
338 }
339
340 MaskProcessingData data(dst, cs, color,
341 d->randomness, d->density,
342 centerX, centerY,
343 angle);
344
345 const QRect rect(0, 0, dstWidth, dstHeight);
346 KisBrushMaskApplicatorBase *applicator = d->shape->applicator();
347 applicator->initializeData(&data);
348 applicator->process(rect);
349}
350
352{
353 // do nothing, since we don't use the pyramid!
354}
355
360
361void KisAutoBrush::toXML(QDomDocument& doc, QDomElement& e) const
362{
363 QDomElement shapeElt = doc.createElement("MaskGenerator");
364 d->shape->toXML(doc, shapeElt);
365 e.appendChild(shapeElt);
366 e.setAttribute("type", "auto_brush");
367 e.setAttribute("spacing", QString::number(spacing()));
368 e.setAttribute("useAutoSpacing", QString::number(autoSpacingActive()));
369 e.setAttribute("autoSpacingCoeff", QString::number(autoSpacingCoeff()));
370 e.setAttribute("angle", QString::number(KisBrush::angle()));
371 e.setAttribute("randomness", QString::number(d->randomness));
372 e.setAttribute("density", QString::number(d->density));
373 KisBrush::toXML(doc, e);
374}
375
377{
378 KisDabShape shape;
379
380 int width = maskWidth(KisDabShape(), 0.0, 0.0, KisPaintInformation());
381 int height = maskHeight(KisDabShape(), 0.0, 0.0, KisPaintInformation());
382
383 QSize size(width, height);
384
385 if (maxSize > 0 && KisAlgebra2D::maxDimension(size) > maxSize) {
386 size.scale(128, 128, Qt::KeepAspectRatio);
387
388 qreal scale = 1.0;
389
390 if (width > height) {
391 scale = qreal(size.width()) / width;
392 } else {
393 scale = qreal(size.height()) / height;
394 }
395
396 shape = KisDabShape(scale, 1.0, 0.0);
397 width = maskWidth(shape, 0.0, 0.0, KisPaintInformation());
398 height = maskHeight(shape, 0.0, 0.0, KisPaintInformation());
399 }
400
401 KisPaintInformation info(QPointF(width * 0.5, height * 0.5), 0.5, 0, 0, angle(), 0, 0, 0, 0);
402
404 fdev->setRect(QRect(0, 0, width, height));
405 fdev->initialize();
406
407 mask(fdev, KoColor(Qt::black, fdev->colorSpace()), shape, info);
408 return fdev->convertToQImage(0);
409}
410
411
413{
414 return d->shape.data();
415}
416
418{
419 return d->density;
420}
421
423{
424 return d->randomness;
425}
426
427KisOptimizedBrushOutline KisAutoBrush::outline(bool forcePreciseOutline) const
428{
429 const bool requiresComplexOutline = d->shape->spikes() > 2;
430 if (!requiresComplexOutline && !forcePreciseOutline) {
431 QPainterPath path;
432 QRectF brushBoundingbox(0, 0, width(), height());
433 if (maskGenerator()->type() == KisMaskGenerator::CIRCLE) {
434 path.addEllipse(brushBoundingbox);
435 }
436 else { // if (maskGenerator()->type() == KisMaskGenerator::RECTANGLE)
437 path.addRect(brushBoundingbox);
438 }
439
440 return path;
441 }
442
443 return KisBrush::outline();
444}
445
447{
449
450 if (!qFuzzyCompare(density(), 1.0)) {
451 l->limitations << KoID("auto-brush-density", i18nc("PaintOp instant preview limitation", "Brush Density recommended value 100.0"));
452 }
453
454 if (!qFuzzyCompare(randomness(), 0.0)) {
455 l->limitations << KoID("auto-brush-randomness", i18nc("PaintOp instant preview limitation", "Brush Randomness recommended value 0.0"));
456 }
457}
458
460{
461 return qFuzzyCompare(density(), 1.0) && qFuzzyCompare(randomness(), 0.0);
462}
float value(const T *src, size_t ch)
void toXML(QDomDocument &, QDomElement &) const override
KisFixedPaintDeviceSP paintDevice(const KoColorSpace *, KisDabShape const &, const KisPaintInformation &, double=0, double=0) const override
qint32 maskWidth(KisDabShape const &shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const override
const QScopedPointer< Private > d
void coldInitBrush() override
const KisMaskGenerator * maskGenerator() const
qreal density() const
bool loadFromDevice(QIODevice *dev, KisResourcesInterfaceSP resourcesInterface) override
bool saveToDevice(QIODevice *dev) const override
KisFixedPaintDeviceSP outlineSourceImage() const override
void lodLimitations(KisPaintopLodLimitations *l) const override
bool supportsCaching() const override
void notifyBrushIsGoingToBeClonedForStroke() override
void setUserEffectiveSize(qreal value) override
qreal userEffectiveSize() const override
KoResourceSP clone() const override
bool isPiercedApprox() const override
qint32 maskHeight(KisDabShape const &shape, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const override
void generateMaskAndApplyMaskOrCreateDab(KisFixedPaintDeviceSP dst, KisBrush::ColoringInformation *src, KisDabShape const &, const KisPaintInformation &info, double subPixelX=0, double subPixelY=0, qreal softnessFactor=DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength=DEFAULT_LIGHTNESS_STRENGTH) const override
qreal randomness() const
QImage createBrushPreview(int maxSize=-1)
bool isEphemeral() const override
KisAutoBrush(KisMaskGenerator *as, qreal angle, qreal randomness, qreal density=1.0)
KisOptimizedBrushOutline outline(bool forcePreciseOutline=false) const override
~KisAutoBrush() override
QSizeF characteristicSize(KisDabShape const &) const override
virtual void process(const QRect &rect)=0
void initializeData(const MaskProcessingData *data)
virtual const quint8 * color() const =0
virtual qint32 maskHeight(KisDabShape const &, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const
qint32 width() const
virtual void lodLimitations(KisPaintopLodLimitations *l) const
virtual void setAngle(qreal _angle)
virtual void setBrushType(enumBrushType type)
bool autoSpacingActive() const
double spacing() const
QPointF hotSpot(KisDabShape const &, const KisPaintInformation &info) const
void mask(KisFixedPaintDeviceSP dst, const KoColor &color, KisDabShape const &shape, const KisPaintInformation &info, double subPixelX=0, double subPixelY=0, qreal softnessFactor=DEFAULT_SOFTNESS_FACTOR, qreal lightnessStrength=DEFAULT_LIGHTNESS_STRENGTH) const
void generateOutlineCache()
virtual QSizeF characteristicSize(KisDabShape const &) const
virtual void toXML(QDomDocument &, QDomElement &) const
virtual KisOptimizedBrushOutline outline(bool forcePreciseOutline=false) const
qreal autoSpacingCoeff() const
void setWidth(qint32 width)
qint32 height() const
void setHeight(qint32 height)
qreal scale() const
virtual qint32 maskWidth(KisDabShape const &, qreal subPixelX, qreal subPixelY, const KisPaintInformation &info) const
qreal angle() const
qreal scaleY() const
qreal scale() const
qreal scaleX() const
qreal rotation() const
void setRect(const QRect &rc)
bool initialize(quint8 defaultValue=0)
virtual QImage convertToQImage(const KoColorProfile *dstProfile, qint32 x, qint32 y, qint32 w, qint32 h, KoColorConversionTransformation::Intent renderingIntent=KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::ConversionFlags conversionFlags=KoColorConversionTransformation::internalConversionFlags()) const
const KoColorSpace * colorSpace() const
virtual quint32 pixelSize() const =0
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
static KisDabShape lieAboutDabShape(KisDabShape const &shape, int spikes)
void fillPixelOptimized_4bytes(quint8 *color, quint8 *buf, int size)
void fillPixelOptimized_general(quint8 *color, quint8 *buf, int size, int pixelSize)
const KoID SoftId("soft", ki18n("Soft"))
generate brush mask from former softbrush paintop, where softness is based on curve
@ MASK
Definition kis_brush.h:32
QSharedPointer< KoResource > KoResourceSP
auto maxDimension(Size size) -> decltype(size.width())
Private(const Private &rhs)
QScopedPointer< KisMaskGenerator > shape
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
void setImage(const QImage &image)