Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_sketch_paintop.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
3 * SPDX-FileCopyrightText: 2010 Ricardo Cabello <hello@mrdoob.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
10
11#include <cmath>
12#include <QRect>
13
14#include <KoColor.h>
15#include <KoColorSpace.h>
16
17#include <kis_image.h>
18#include <kis_debug.h>
19
20#include <kis_global.h>
21#include <kis_paint_device.h>
22#include <kis_painter.h>
23#include <kis_types.h>
28
29#include <kis_dab_cache.h>
30#include "kis_lod_transform.h"
32
33
34#include <QtGlobal>
35
36/*
37* Based on Harmony project https://github.com/mrdoob/harmony/
38*/
39// chrome : diff 0.2, sketchy : 0.3, fur: 0.5
40// fur : distance / thresholdDistance
41
42// shaded: opacity per line :/
43// ((1 - (d / 1000)) * 0.1 * BRUSH_PRESSURE), offset == 0
44// chrome: color per line :/
45//this.context.strokeStyle = "rgba(" + Math.floor(Math.random() * COLOR[0]) + ", " + Math.floor(Math.random() * COLOR[1]) + ", " + Math.floor(Math.random() * COLOR[2]) + ", " + 0.1 * BRUSH_PRESSURE + " )";
46
47// long fur
48// from: count + offset * -random
49// to: i point - (offset * -random) + random * 2
50// probability distance / thresholdDistance
51
52// shaded: probability : paint always - 0.0 density
53
55 : KisPaintOp(painter)
56 , m_opacityOption(settings.data(), node)
57 , m_sizeOption(settings.data())
58 , m_rotationOption(settings.data())
59 , m_rateOption(settings.data())
60 , m_densityOption(settings.data())
61 , m_lineWidthOption(settings.data())
62 , m_offsetScaleOption(settings.data())
63{
64 Q_UNUSED(image);
65 Q_UNUSED(node);
66
67 m_airbrushOption.read(settings.data());
68 m_sketchProperties.read(settings.data());
69 m_brushOption.readOptionSetting(settings, settings->resourcesInterface(), settings->canvasResourcesInterface());
70
73
74 m_painter = 0;
75 m_count = 0;
76}
77
83
85{
86 KisBrushOptionProperties brushOption;
87 return brushOption.prepareLinkedResources(settings, resourcesInterface);
88}
89
90void KisSketchPaintOp::drawConnection(const QPointF& start, const QPointF& end, double lineWidth)
91{
92 //Both drawWuLine() and the drawDDALine produce nicer 1px lines than the drawLine()
94 if (lineWidth == 1.0) {
95 m_painter->drawWuLine(start, end);
96 }
97 else {
98 m_painter->drawLine(start, end, lineWidth, true);
99 }
100 }
101 else {
102 if (lineWidth == 1.0) {
103 m_painter->drawDDALine(start, end);
104 }
105 else {
106 m_painter->drawLine(start, end, lineWidth, false);
107 }
108 }
109}
110
111void KisSketchPaintOp::updateBrushMask(const KisPaintInformation& info, qreal scale, qreal rotation)
112{
113 QRect dstRect;
115 painter()->paintColor(),
116 info.pos(),
117 KisDabShape(scale, 1.0, rotation),
118 info, 1.0,
119 &dstRect);
120
121 m_brushBoundingBox = dstRect;
122 m_hotSpot = QPointF(0.5 * m_brushBoundingBox.width(),
123 0.5 * m_brushBoundingBox.height());
124}
125
127 KisDistanceInformation *currentDistance)
128{
129 // Use superclass behavior for lines of zero length. Otherwise, airbrushing can happen faster
130 // than it is supposed to.
131 if (pi1.pos() == pi2.pos()) {
132 KisPaintOp::paintLine(pi1, pi2, currentDistance);
133 }
134 else {
135 doPaintLine(pi1, pi2);
136 }
137}
139{
140 if (!m_brush || !painter()) return;
141
142 if (!m_dab) {
145 m_painter->setPaintColor(painter()->paintColor());
146 }
147 else {
148 m_dab->clear();
149 }
150
151 QPointF prevMouse = pi1.pos();
152 QPointF mousePosition = pi2.pos();
153 m_points.append(mousePosition);
154
155
156 const qreal lodAdditionalScale = KisLodTransform::lodToScale(painter()->device());
157 const qreal scale = lodAdditionalScale * m_sizeOption.apply(pi2);
158 if ((scale * m_brush->width()) <= 0.01 || (scale * m_brush->height()) <= 0.01) return;
159
160 const qreal currentLineWidth = qMax(0.9, lodAdditionalScale * m_lineWidthOption.apply(pi2) * m_sketchProperties.lineWidth);
161
162 const qreal currentOffsetScale = m_offsetScaleOption.apply(pi2) * m_sketchProperties.offset * 0.01;
163 const double rotation = m_rotationOption.apply(pi2);
164 const double currentProbability = m_densityOption.apply(pi2) * m_sketchProperties.probability;
165
166 // shaded: does not draw this line, chrome does, fur does
168 drawConnection(prevMouse, mousePosition, currentLineWidth);
169 }
170
171
172 qreal thresholdDistance = 0.0;
173
174 // update the mask for simple mode only once
175 // determine the radius
177 updateBrushMask(pi2, 1.0, 0.0);
178 //m_radius = qMax(m_maskDab->bounds().width(),m_maskDab->bounds().height()) * 0.5;
179 m_radius = 0.5 * qMax(m_brush->width(), m_brush->height());
180 }
181
183 updateBrushMask(pi2, scale, rotation);
184 m_radius = qMax(m_maskDab->bounds().width(), m_maskDab->bounds().height()) * 0.5;
185 thresholdDistance = pow(m_radius, 2);
186 }
187
189 // update the radius according scale in simple mode
190 thresholdDistance = pow(m_radius * scale, 2);
191 }
192
193 // determine density
194 const qreal density = thresholdDistance * currentProbability;
195
196 // probability behaviour
197 qreal probability = 1.0 - currentProbability;
198
199 QColor painterColor = painter()->paintColor().toQColor();
200 QColor randomColor;
201 KoColor color(m_dab->colorSpace());
202
203 int w = m_maskDab->bounds().width();
204 quint8 opacityU8 = 0;
205 quint8 * pixel;
206 qreal distance;
207 QPoint positionInMask;
208 QPointF diff;
209
210 int size = m_points.size();
211 // MAIN LOOP
212 for (int i = 0; i < size; i++) {
213 diff = m_points.at(i) - mousePosition;
214 distance = diff.x() * diff.x() + diff.y() * diff.y();
215
216 // circle test
217 bool makeConnection = false;
219 if (distance < thresholdDistance) {
220 makeConnection = true;
221 }
222 // mask test
223 }
224 else {
225 if (m_brushBoundingBox.contains(m_points.at(i))) {
226 positionInMask = (diff + m_hotSpot).toPoint();
227 uint pos = ((positionInMask.y() * w + positionInMask.x()) * m_maskDab->pixelSize());
228 if (pos < m_maskDab->allocatedPixels() * m_maskDab->pixelSize()) {
229 pixel = m_maskDab->data() + pos;
230 opacityU8 = m_maskDab->colorSpace()->opacityU8(pixel);
231 if (opacityU8 != 0) {
232 makeConnection = true;
233 }
234 }
235 }
236
237 }
238
239 if (!makeConnection) {
240 // check next point
241 continue;
242 }
243
245 probability = distance / density;
246 }
247
248 KisRandomSourceSP randomSource = pi2.randomSource();
249
250 // density check
251 if (randomSource->generateNormalized() >= probability) {
252 QPointF offsetPt = diff * currentOffsetScale;
253
261 qreal r1 = randomSource->generateNormalized();
262 qreal r2 = randomSource->generateNormalized();
263 qreal r3 = randomSource->generateNormalized();
264
265 // some color transformation per line goes here
266 randomColor.setRgbF(r1 * painterColor.redF(),
267 r2 * painterColor.greenF(),
268 r3 * painterColor.blueF());
269 color.fromQColor(randomColor);
270 m_painter->setPaintColor(color);
271 }
272
273 // distance based opacity
274 qreal opacity = OPACITY_OPAQUE_F;
276 // TODO: check if the formula is correct, do we actually
277 // need rounding here?
278 opacity *= qRound((1.0 - (distance / thresholdDistance)));
279 }
280
282 opacity *= randomSource->generateNormalized();
283 }
284
285 m_painter->setOpacityF(opacity);
286
288 drawConnection(mousePosition + offsetPt, m_points.at(i) - offsetPt, currentLineWidth);
289 }
290 else {
291 drawConnection(mousePosition + offsetPt, mousePosition - offsetPt, currentLineWidth);
292 }
293
294
295
296 }
297 }// end of MAIN LOOP
298
299 m_count++;
300
301 QRect rc = m_dab->extent();
303
304 painter()->bitBlt(rc.x(), rc.y(), m_dab, rc.x(), rc.y(), rc.width(), rc.height());
306}
307
308
309
315
317{
318 return KisPaintOpPluginUtils::effectiveSpacing(0.0, 0.0, true, 0.0, false, 0.0, false, 0.0,
320 &m_airbrushOption, nullptr, info);
321}
322
QPointF r2
QPointF r1
QPointF r3
const qreal OPACITY_OPAQUE_F
qreal distance(const QPointF &p1, const QPointF &p2)
unsigned int uint
The KisDabCache class provides caching for dabs into the brush paintop.
KisFixedPaintDeviceSP fetchDab(const KoColorSpace *cs, KisColorSource *colorSource, const QPointF &cursorPoint, KisDabShape const &, const KisPaintInformation &info, qreal softnessFactor, QRect *dstDabRect, qreal lightnessStrength=1.0)
const KoColorSpace * colorSpace() const
static qreal lodToScale(int levelOfDetail)
void apply(KisPainter *painter, const KisPaintInformation &info) const
virtual void clear()
KisPaintDeviceSP createCompositionSourceDevice() const
QRect extent() const
const KoColorSpace * colorSpace() const
KisRandomSourceSP randomSource() const
const QPointF & pos() const
KoColor paintColor
void drawDDALine(const QPointF &start, const QPointF &end)
void renderMirrorMask(QRect rc, KisFixedPaintDeviceSP dab)
void drawWuLine(const QPointF &start, const QPointF &end)
void setOpacityF(qreal opacity)
void bitBlt(qint32 dstX, qint32 dstY, const KisPaintDeviceSP srcDev, qint32 srcX, qint32 srcY, qint32 srcWidth, qint32 srcHeight)
void drawLine(const QPointF &start, const QPointF &end)
void setPaintColor(const KoColor &color)
QList< KoResourceLoadResult > prepareLinkedResources(const KisPropertiesConfigurationPointer settings, KisResourcesInterfaceSP resourcesInterface) const
void readOptionSetting(KisPropertiesConfigurationPointer settings, KisResourcesInterfaceSP resourcesInterface, KoCanvasResourcesInterfaceSP canvasResourcesInterface)
qreal generateNormalized() const
qreal apply(const KisPaintInformation &info) const
KisSpacingInformation paintAt(const KisPaintInformation &info) override
KisOffsetScaleOption m_offsetScaleOption
KisRotationOption m_rotationOption
KisSketchPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image)
void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) override
KisDensityOption m_densityOption
KisDabCache * m_dabCache
void drawConnection(const QPointF &start, const QPointF &end, double lineWidth)
KisSizeOption m_sizeOption
KisTimingInformation updateTimingImpl(const KisPaintInformation &info) const override
KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override
KisRateOption m_rateOption
KisFixedPaintDeviceSP m_maskDab
KisOpacityOption m_opacityOption
KisSketchOpOptionData m_sketchProperties
KisAirbrushOptionData m_airbrushOption
void doPaintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2)
void updateBrushMask(const KisPaintInformation &info, qreal scale, qreal rotation)
KisPaintDeviceSP m_dab
static QList< KoResourceLoadResult > prepareLinkedResources(const KisPaintOpSettingsSP settings, KisResourcesInterfaceSP resourcesInterface)
QVector< QPointF > m_points
KisBrushOptionProperties m_brushOption
KisLineWidthOption m_lineWidthOption
qreal apply(const KisPaintInformation &info) const
virtual quint8 opacityU8(const quint8 *pixel) const =0
void fromQColor(const QColor &c)
Convenient function for converting from a QColor.
Definition KoColor.cpp:213
void toQColor(QColor *c) const
a convenience method for the above.
Definition KoColor.cpp:198
KisSpacingInformation effectiveSpacing(qreal dabWidth, qreal dabHeight, bool isotropicSpacing, qreal rotation, bool axesFlipped, qreal spacingVal, bool autoSpacingActive, qreal autoSpacingCoeff, qreal lodScale, const KisAirbrushOptionData *airbrushOption, const KisSpacingOption *spacingOption, const KisPaintInformation &pi)
KisTimingInformation effectiveTiming(const KisAirbrushOptionData *airbrushOption, const KisRateOption *rateOption, const KisPaintInformation &pi)
bool read(const KisPropertiesConfiguration *setting)
KisPainter * painter
KisPaintDeviceSP source() const
virtual void paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance)
bool read(const KisPropertiesConfiguration *setting)