Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_texture_option.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2012 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2014 Mohit Goyal <mohit.bits2011@gmail.com>
4 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com>
5 *
6 * SPDX-License-Identifier: LGPL-2.0-or-later
7 */
9
10#include <QString>
11#include <QCheckBox>
12#include <QBuffer>
13#include <QFormLayout>
14#include <QPainter>
15#include <QBoxLayout>
16
17#include <klocalizedstring.h>
18
19#include <KoPattern.h>
20#include <KoAbstractGradient.h>
21#include <KoResource.h>
23#include <kis_paint_device.h>
24#include <kis_fill_painter.h>
25#include <kis_painter.h>
26#include <kis_iterator_ng.h>
28#include "KoMixColorsOp.h"
33
38
39/**********************************************************************/
40/* KisTextureOption */
41/**********************************************************************/
42
43
45 KisResourcesInterfaceSP resourcesInterface,
46 KoCanvasResourcesInterfaceSP canvasResourcesInterface,
47 int levelOfDetail,
48 KisBrushTextureFlags flags)
49 : m_gradient(0)
50 , m_levelOfDetail(levelOfDetail)
51 , m_strengthOption(setting)
52 , m_flags(flags)
53{
54 fillProperties(setting, resourcesInterface, canvasResourcesInterface);
55}
56
58{
60 data.read(setting);
61
62 if (data.textureData.isNull()) {
63 m_enabled = false;
64 return;
65 }
66
69
72 }
73
76 }
77
79
80 QString effectiveCompositeOp = COMPOSITE_OVER;
81 bool additionalInvert = false;
82
83 if (canvasResourcesInterface && data.autoInvertOnErase) {
84 effectiveCompositeOp = canvasResourcesInterface->resource(KoCanvasResource::CurrentEffectiveCompositeOp).toString();
85 KIS_SAFE_ASSERT_RECOVER (!effectiveCompositeOp.isEmpty()) {
86 effectiveCompositeOp = COMPOSITE_OVER;
87 }
88 additionalInvert = effectiveCompositeOp == COMPOSITE_ERASE;
89 }
90
92 if (!m_maskInfo->fillProperties(setting, resourcesInterface, additionalInvert)) {
93 warnKrita << "WARNING: Couldn't load the pattern for a stroke (KisTextureProperties)";
94 m_enabled = false;
95 return;
96 }
97
99
100 m_enabled = data.isEnabled;
101 m_offsetX = data.offsetX;
102 m_offsetY = data.offsetY;
105
106 if (m_texturingMode == KisTextureOptionData::GRADIENT && canvasResourcesInterface) {
107 KoAbstractGradientSP gradient = canvasResourcesInterface->resource(KoCanvasResource::CurrentGradient).value<KoAbstractGradientSP>()->cloneAndBakeVariableColors(canvasResourcesInterface);
108 if (gradient) {
109 m_gradient = gradient;
110 m_cachedGradient.setGradient(gradient, 256);
111 }
112 }
113}
114
116{
118 return m_isRandomOffsetX ?
119 info.perStrokeRandomSource()->generate("texture_offset_x", 0, m_maskInfo->maskBounds().width()) :
120 m_offsetX;
121}
122
124{
126 return m_isRandomOffsetY ?
127 info.perStrokeRandomSource()->generate("texture_offset_y", 0, m_maskInfo->maskBounds().height()) :
128 m_offsetY;
129}
130
132{
139
141 data.read(setting.data());
142
143 if (data.isEnabled && !data.textureData.patternBase64.isEmpty()) {
144 patterns << data.textureData.loadLinkedPattern(resourcesInterface);
145 }
146
147 return patterns;
148}
149
151{
158
160 data.read(setting.data());
161
162 if (data.isEnabled && data.textureData.patternBase64.isEmpty()) {
163 patterns << data.textureData.loadLinkedPattern(resourcesInterface);
164 }
165
166 return patterns;
167}
168
173
175{
177 data.read(settings);
178
180}
181
183{
185 data.read(settings);
186
187 return data.isEnabled && data.autoInvertOnErase;
188}
189
190
192 if (!m_enabled) return;
193 if (!m_maskInfo->isValid()) return;
194
195 KisPaintDeviceSP mask = m_maskInfo->mask();
196
197 const QRect rect = dab->bounds();
198 const QRect maskBounds = m_maskInfo->maskBounds();
199
201 KisPaintDeviceSP fillMaskDevice = g.device();
202
203 int x = offset.x() % maskBounds.width() - effectiveOffsetX(info);
204 int y = offset.y() % maskBounds.height() - effectiveOffsetY(info);
205
206 const QRect maskPatchRect = QRect(x, y, rect.width(), rect.height());
207
208 KisFillPainter fillMaskPainter(fillMaskDevice);
209 fillMaskPainter.setCompositeOpId(COMPOSITE_COPY);
210 fillMaskPainter.fillRect(kisGrowRect(maskPatchRect, 1), mask, maskBounds);
211 fillMaskPainter.end();
212
213 qreal pressure = m_strengthOption.apply(info);
214 quint8* dabData = dab->data();
215
216 KisSequentialConstIterator it(fillMaskDevice, QRect(x, y, rect.width(), rect.height()));
217 while (it.nextPixel()) {
218 const QRgb *maskQRgb = reinterpret_cast<const QRgb*>(it.oldRawData());
219 dab->colorSpace()->fillGrayBrushWithColorAndLightnessWithStrength(dabData, maskQRgb, dabData, pressure, 1);
220 dabData += dab->pixelSize();
221 }
222}
223
225 if (!m_enabled) return;
226 if (!m_maskInfo->isValid()) return;
227
229
230 KisPaintDeviceSP mask = m_maskInfo->mask();
231 const QRect maskBounds = m_maskInfo->maskBounds();
232 QRect rect = dab->bounds();
233
235 KisPaintDeviceSP fillDevice = g.device();
236
237 int x = offset.x() % maskBounds.width() - effectiveOffsetX(info);
238 int y = offset.y() % maskBounds.height() - effectiveOffsetY(info);
239
240 const QRect maskPatchRect = QRect(x, y, rect.width(), rect.height());
241
242 KisFillPainter fillPainter(fillDevice);
243 fillPainter.setCompositeOpId(COMPOSITE_COPY);
244 fillPainter.fillRect(kisGrowRect(maskPatchRect, 1), mask, maskBounds);
245 fillPainter.end();
246
247 qreal pressure = m_strengthOption.apply(info);
248 quint8* dabData = dab->data();
249
250 //for gradient textures...
251 KoMixColorsOp* colorMix = dab->colorSpace()->mixColorsOp();
252 qint16 colorWeights[2];
253 colorWeights[0] = qRound(pressure * 255);
254 colorWeights[1] = 255 - colorWeights[0];
255 quint8* colors[2];
256 m_cachedGradient.setColorSpace(dab->colorSpace()); //Change colorspace here so we don't have to convert each pixel drawn
257
258 KisHLineIteratorSP iter = fillDevice->createHLineIteratorNG(x, y, rect.width());
259 for (int row = 0; row < rect.height(); ++row) {
260 for (int col = 0; col < rect.width(); ++col) {
261
262 const QRgb* maskQRgb = reinterpret_cast<const QRgb*>(iter->oldRawData());
263 qreal gradientvalue = qreal(qGray(*maskQRgb))/255.0;//qreal(*iter->oldRawData()) / 255.0;
264 KoColor paintcolor;
265 paintcolor.setColor(m_cachedGradient.cachedAt(gradientvalue), dab->colorSpace());
266 qreal paintOpacity = paintcolor.opacityF() * (qreal(qAlpha(*maskQRgb)) / 255.0);
267 paintcolor.setOpacity(qMin(paintOpacity, dab->colorSpace()->opacityF(dabData)));
268 colors[0] = paintcolor.data();
269 KoColor dabColor(dabData, dab->colorSpace());
270 colors[1] = dabColor.data();
271 colorMix->mixColors(colors, colorWeights, 2, dabData);
272
273 iter->nextPixel();
274 dabData += dab->pixelSize();
275 }
276 iter->nextRow();
277 }
278}
279
280void KisTextureOption::apply(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation & info)
281{
282 if (!m_enabled) return;
283 if (!m_maskInfo->isValid()) return;
284
286 applyLightness(dab, offset, info);
287 return;
288 }
290 applyGradient(dab, offset, info);
291 return;
292 }
293
294 QRect rect = dab->bounds();
295 KisPaintDeviceSP mask = m_maskInfo->mask();
296 const QRect maskBounds = m_maskInfo->maskBounds();
297
299 KisPaintDeviceSP maskPatch = g.device();
300
301 int x = offset.x() % maskBounds.width() - effectiveOffsetX(info);
302 int y = offset.y() % maskBounds.height() - effectiveOffsetY(info);
303
304 const QRect maskPatchRect = QRect(x, y, rect.width(), rect.height());
305
306 KisFillPainter fillPainter(maskPatch);
307 fillPainter.setCompositeOpId(COMPOSITE_COPY);
308 fillPainter.fillRect(kisGrowRect(maskPatchRect, 1), mask, maskBounds);
309 fillPainter.end();
310
311 // Compute final strength
312 qreal strength = m_strengthOption.apply(info);
313
314 // Select mask compositing op
316 int alphaChannelOffset = -1;
317
318 const QList<KoChannelInfo *> channels = dab->colorSpace()->channels();
319 for (quint32 i = 0; i < dab->pixelSize(); i++) {
320 if (channels[i]->channelType() == KoChannelInfo::ALPHA) {
321 // TODO: check correctness for 16bits!
322 alphaChannelOffset = channels[i]->pos()/* * channels[i]->size()*/;
323 alphaChannelType = channels[i]->channelValueType();
324 break;
325 }
326 }
327
328 KIS_SAFE_ASSERT_RECOVER (alphaChannelOffset >= 0) {
329 alphaChannelOffset = 0;
330 }
331
332 QScopedPointer<KisMaskingBrushCompositeOpBase> compositeOp;
333 QString compositeOpId;
334
335 switch (m_texturingMode) {
336 case KisTextureOptionData::MULTIPLY: compositeOpId = COMPOSITE_MULT; break;
337 case KisTextureOptionData::SUBTRACT: compositeOpId = COMPOSITE_SUBTRACT; break;
338 case KisTextureOptionData::DARKEN: compositeOpId = COMPOSITE_DARKEN; break;
339 case KisTextureOptionData::OVERLAY: compositeOpId = COMPOSITE_OVERLAY; break;
340 case KisTextureOptionData::COLOR_DODGE: compositeOpId = COMPOSITE_DODGE; break;
341 case KisTextureOptionData::COLOR_BURN: compositeOpId = COMPOSITE_BURN; break;
343 case KisTextureOptionData::LINEAR_BURN: compositeOpId = COMPOSITE_LINEAR_BURN; break;
346 case KisTextureOptionData::HEIGHT: compositeOpId = "height"; break;
347 case KisTextureOptionData::LINEAR_HEIGHT: compositeOpId = "linear_height"; break;
348 case KisTextureOptionData::HEIGHT_PHOTOSHOP: compositeOpId = "height_photoshop"; break;
349 case KisTextureOptionData::LINEAR_HEIGHT_PHOTOSHOP: compositeOpId = "linear_height_photoshop"; break;
350 default: return;
351 }
353 compositeOpId, alphaChannelType, dab->pixelSize(),
354 alphaChannelOffset, strength, m_useSoftTexturing));
355
356 // Apply the mask to the dab
357 {
358 quint8 *dabIt = nullptr;
359 KisRandomConstAccessorSP maskPatchIt = maskPatch->createRandomConstAccessorNG();
360
361 qint32 dabY = dab->bounds().y();
362 qint32 maskPatchY = maskPatchRect.y();
363 qint32 rowsRemaining = dab->bounds().height();
364 const qint32 dabRowStride = dab->bounds().width() * dab->pixelSize();
365
366 while (rowsRemaining > 0) {
367 qint32 dabX = dab->bounds().x();
368 qint32 maskPatchX = maskPatchRect.x();
369 const qint32 numContiguousMaskPatchRows = maskPatchIt->numContiguousRows(maskPatchY);
370 const qint32 rows = std::min(rowsRemaining, numContiguousMaskPatchRows);
371 qint32 columnsRemaining = dab->bounds().width();
372
373 while (columnsRemaining > 0) {
374 const qint32 numContiguousMaskPatchColumns = maskPatchIt->numContiguousColumns(maskPatchX);
375 const qint32 columns = std::min(columnsRemaining, numContiguousMaskPatchColumns);
376
377 const qint32 maskPatchRowStride = maskPatchIt->rowStride(maskPatchX, maskPatchY);
378
379 dabIt = dab->data() + (dabY * dab->bounds().width() + dabX) * dab->pixelSize();
380 maskPatchIt->moveTo(maskPatchX, maskPatchY);
381
382 compositeOp->composite(maskPatchIt->rawDataConst(), maskPatchRowStride,
383 dabIt, dabRowStride,
384 columns, rows);
385
386 dabX += columns;
387 maskPatchX += columns;
388 columnsRemaining -= columns;
389
390 }
391
392 dabY += rows;
393 maskPatchY += rows;
394 rowsRemaining -= rows;
395 }
396 }
397}
@ SupportsGradientMode
@ SupportsLightnessMode
const QString COMPOSITE_OVER
const QString COMPOSITE_COPY
const QString COMPOSITE_DARKEN
const QString COMPOSITE_OVERLAY
const QString COMPOSITE_DODGE
const QString COMPOSITE_LINEAR_BURN
const QString COMPOSITE_MULT
const QString COMPOSITE_ERASE
const QString COMPOSITE_SUBTRACT
const QString COMPOSITE_BURN
const QString COMPOSITE_LINEAR_DODGE
const QString COMPOSITE_HARD_MIX_SOFTER_PHOTOSHOP
const QString COMPOSITE_HARD_MIX_PHOTOSHOP
virtual const quint8 * rawDataConst() const =0
KoResourceLoadResult loadLinkedPattern(KisResourcesInterfaceSP resourcesInterface) const
void fillRect(qint32 x, qint32 y, qint32 w, qint32 h, const KoColor &c, quint8 opacity)
const KoColorSpace * colorSpace() const
static KisMaskingBrushCompositeOpBase * createForAlphaSrc(const QString &id, KoChannelInfo::enumChannelValueType channelType, int pixelSize, int alphaOffset)
KisRandomConstAccessorSP createRandomConstAccessorNG() const
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
KisPerStrokeRandomSourceSP perStrokeRandomSource() const
void setCompositeOpId(const KoCompositeOp *op)
int generate(const QString &key, int min, int max) const
virtual qint32 rowStride(qint32 x, qint32 y) const =0
virtual qint32 numContiguousRows(qint32 y) const =0
virtual void moveTo(qint32 x, qint32 y)=0
virtual qint32 numContiguousColumns(qint32 x) const =0
ALWAYS_INLINE const quint8 * oldRawData() const
qreal apply(const KisPaintInformation &info) const
static bool requiresEffectiveCompositeOp(const KisPropertiesConfiguration *settings)
void fillProperties(const KisPropertiesConfiguration *setting, KisResourcesInterfaceSP resourcesInterface, KoCanvasResourcesInterfaceSP canvasResourcesInterface)
KisTextureMaskInfoSP m_maskInfo
static QList< KoResourceLoadResult > prepareEmbeddedResources(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface)
KisBrushTextureFlags m_flags
KisCachedPaintDevice m_cachedPaintDevice
KisStrengthOption m_strengthOption
KoAbstractGradientSP m_gradient
void applyGradient(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation &info)
int effectiveOffsetY(const KisPaintInformation &info) const
bool applyingGradient() const
KoCachedGradient m_cachedGradient
void apply(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation &info)
apply combine the texture map with the dab
KisTextureOption(const KisPropertiesConfiguration *setting, KisResourcesInterfaceSP resourcesInterface, KoCanvasResourcesInterfaceSP canvasResourcesInterface, int levelOfDetail, KisBrushTextureFlags flags=None)
void applyLightness(KisFixedPaintDeviceSP dab, const QPoint &offset, const KisPaintInformation &info)
int effectiveOffsetX(const KisPaintInformation &info) const
KisTextureOptionData::TexturingMode m_texturingMode
static QList< KoResourceLoadResult > prepareLinkedResources(const KisPropertiesConfigurationSP setting, KisResourcesInterfaceSP resourcesInterface)
void setGradient(const KoAbstractGradientSP gradient, qint32 steps, const KoColorSpace *cs)
void setColorSpace(const KoColorSpace *colorSpace)
const quint8 * cachedAt(qreal t) const
gets the color data at position 0 <= t <= 1
@ ALPHA
The channel represents the opacity of a pixel.
enumChannelValueType
enum to define the value of the channel
@ UINT8
use this for an unsigned integer 8bits channel
virtual qreal opacityF(const quint8 *pixel) const =0
QList< KoChannelInfo * > channels
virtual void fillGrayBrushWithColorAndLightnessWithStrength(quint8 *dst, const QRgb *brush, quint8 *brushColor, qreal strength, qint32 nPixels) const
KoMixColorsOp * mixColorsOp
qreal opacityF() const
Definition KoColor.cpp:345
void setColor(const quint8 *data, const KoColorSpace *colorSpace=0)
Definition KoColor.cpp:186
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
quint8 * data()
Definition KoColor.h:144
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
#define KIS_SAFE_ASSERT_RECOVER(cond)
Definition kis_assert.h:126
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
unsigned int QRgb
#define warnKrita
Definition kis_debug.h:87
T kisGrowRect(const T &rect, U offset)
Definition kis_global.h:186
QSharedPointer< T > toQShared(T *ptr)
KisPaintDeviceSP device() const
KisTextureMaskInfoSP fetchCachedTextureInfo(KisTextureMaskInfoSP info)
static KisTextureMaskInfoCache * instance()
bool read(const KisPropertiesConfiguration *setting)
KisEmbeddedTextureData textureData
static KoColorSpaceRegistry * instance()