Krita Source Code Documentation
Loading...
Searching...
No Matches
KisTextureMaskInfo.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
10#include <resources/KoPattern.h>
13
14#include <KoColorSpace.h>
16
17#include <kis_algebra_2d.h>
18#include <kis_lod_transform.h>
19#include <kis_iterator_ng.h>
20
21#include <QGlobalStatic>
22#include <QMutexLocker>
23
24/**********************************************************************/
25/* KisTextureMaskInfo */
26/**********************************************************************/
27
28
29KisTextureMaskInfo::KisTextureMaskInfo(int levelOfDetail, bool preserveAlpha)
30 : m_levelOfDetail(levelOfDetail)
31 , m_preserveAlpha(preserveAlpha)
32{
33}
34
36 : m_levelOfDetail(rhs.m_levelOfDetail)
37 , m_preserveAlpha(rhs.m_preserveAlpha)
38 , m_pattern(rhs.m_pattern)
39 , m_scale(rhs.m_scale)
40 , m_brightness(rhs.m_brightness)
41 , m_contrast(rhs.m_contrast)
42 , m_neutralPoint(rhs.m_neutralPoint)
43 , m_invert(rhs.m_invert)
44 , m_cutoffLeft(rhs.m_cutoffLeft)
45 , m_cutoffRight(rhs.m_cutoffRight)
46 , m_cutoffPolicy(rhs.m_cutoffPolicy)
47
48{
49}
50
54
55bool operator==(const KisTextureMaskInfo &lhs, const KisTextureMaskInfo &rhs) {
56 return
58 (lhs.m_pattern == rhs.m_pattern ||
59 (lhs.m_pattern &&
60 rhs.m_pattern &&
61 lhs.m_pattern->md5Sum() == rhs.m_pattern->md5Sum())) &&
62 qFuzzyCompare(lhs.m_scale, rhs.m_scale) &&
66 lhs.m_invert == rhs.m_invert &&
67 lhs.m_cutoffLeft == rhs.m_cutoffLeft &&
68 lhs.m_cutoffRight == rhs.m_cutoffRight &&
69 lhs.m_cutoffPolicy == rhs.m_cutoffPolicy &&
71}
72
89
91{
92 return (m_mask && m_maskBounds.isValid());
93}
94
98
100 return m_mask;
101}
102
106
108 return m_maskBounds;
109}
110
111bool KisTextureMaskInfo::fillProperties(const KisPropertiesConfiguration *setting, KisResourcesInterfaceSP resourcesInterface, bool invertAdditionally)
112{
114 data.read(setting);
115
116 if (!data.isEnabled || data.textureData.isNull()) {
117 return false;
118 }
119
120 m_pattern = data.textureData.loadLinkedPattern(resourcesInterface).resource<KoPattern>();
121
122 if (!m_pattern) {
123 qWarning() << "WARNING: Couldn't load the pattern for a stroke (KisTextureMaskInfo)";
124 return false;
125 }
126
127 m_scale = data.scale;
129 m_contrast = data.contrast;
131 m_invert = data.invert != invertAdditionally;
135
136 return true;
137}
138
140{
141 if (!m_pattern) return;
142
143 const KoColorSpace* cs;
144 const bool useAlpha = m_pattern->hasAlpha() && m_preserveAlpha;
145
146 if (useAlpha) {
148 } else {
150 }
151 if (!m_mask) {
152 m_mask = new KisPaintDevice(cs);
153 }
154
155 QImage mask = m_pattern->pattern();
156
157 if ((mask.format() != QImage::Format_RGB32)
158 || (mask.format() != QImage::Format_ARGB32)) {
159 mask.convertTo(QImage::Format_ARGB32);
160 }
161
163
164 if (!qFuzzyCompare(scale, 0.0) && !qFuzzyCompare(scale, 1.0)) {
165 QTransform tf;
166 tf.scale(scale, scale);
167 QRect rc = KisAlgebra2D::ensureRectNotSmaller(tf.mapRect(mask.rect()), QSize(2,2));
168 mask = mask.scaled(rc.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
169 } else {
170 // detach the mask from the file loaded from the storage
171 mask = QImage(mask);
172 }
173
174 QRgb* pixel = reinterpret_cast<QRgb*>(mask.bits());
175 const int width = mask.width();
176 const int height = mask.height();
177
179
180 for (int row = 0; row < height; ++row) {
181 for (int col = 0; col < width; ++col) {
182 const QRgb currentPixel = pixel[row * width + col];
183
184 const int red = qRed(currentPixel);
185 const int green = qGreen(currentPixel);
186 const int blue = qBlue(currentPixel);
187 float alpha = qAlpha(currentPixel) / 255.0;
188
189 const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
190 float maskValue = (grayValue / 255.0) * alpha + (1 - alpha);
191
192 maskValue = maskValue - m_brightness;
193
194 maskValue = ((maskValue - 0.5)*m_contrast)+0.5;
195
196 if (maskValue > 1.0) {maskValue = 1;}
197 else if (maskValue < 0) {maskValue = 0;}
198
199 if (m_invert) {
200 maskValue = 1 - maskValue;
201 }
202
203 maskValue = qBound(0.0f, maskValue, 1.0f);
204
205 float neutralAdjustedValue;
206
207 //Adjust neutral point in linear fashion. Uses separate linear equations from 0 to neutralPoint, and neutralPoint to 1,
208 //to prevent loss of detail (clipping).
209 if (m_neutralPoint == 1 || (m_neutralPoint != 0 && maskValue <= m_neutralPoint)) {
210 neutralAdjustedValue = maskValue / (2 * m_neutralPoint);
211 } else {
212 neutralAdjustedValue = 0.5 + (maskValue - m_neutralPoint) / (2 - 2 * m_neutralPoint);
213 }
214
215 if (m_cutoffPolicy == 1 && (neutralAdjustedValue < (m_cutoffLeft / 255.0) || neutralAdjustedValue >(m_cutoffRight / 255.0))) {
216 // mask out the dab if it's outside the pattern's cutoff points
217 alpha = OPACITY_TRANSPARENT_F;
218 if (!useAlpha) {
219 neutralAdjustedValue = alpha;
220 }
221 } else if (m_cutoffPolicy == 2 && (neutralAdjustedValue < (m_cutoffLeft / 255.0) || neutralAdjustedValue >(m_cutoffRight / 255.0))) {
222 alpha = OPACITY_OPAQUE_F;
223 if (!useAlpha) {
224 neutralAdjustedValue = alpha;
225 }
226 }
227
228 if (useAlpha) {
229 int finalValue = qRound(neutralAdjustedValue * 255.0);
230 pixel[row * width + col] = QColor(finalValue, finalValue, finalValue, qRound(alpha * 255.0)).rgba();
231 } else {
232 cs->setOpacity(iter->rawData(), neutralAdjustedValue, 1);
233 iter->nextPixel();
234 }
235 }
236 if (!useAlpha) {
237 iter->nextRow();
238 }
239 }
240 if (useAlpha) {
242 }
243 m_maskBounds = QRect(0, 0, width, height);
244}
245
247 return m_pattern->hasAlpha();
248}
249
250/**********************************************************************/
251/* KisTextureMaskInfoCache */
252/**********************************************************************/
253
255
257{
258 return s_instance;
259}
260
262 QMutexLocker locker(&m_mutex);
263
264 KisTextureMaskInfoSP &cachedInfo =
265 info->levelOfDetail() > 0 ? m_lodInfo : m_mainInfo;
266
267 if (!cachedInfo || *cachedInfo != *info) {
268 cachedInfo = info;
269 cachedInfo->recalculateMask();
270 }
271
272 return cachedInfo;
273}
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
bool operator==(const KisTextureMaskInfo &lhs, const KisTextureMaskInfo &rhs)
const qreal OPACITY_OPAQUE_F
const qreal OPACITY_TRANSPARENT_F
PythonPluginManager * instance
KoResourceLoadResult loadLinkedPattern(KisResourcesInterfaceSP resourcesInterface) const
static qreal lodToScale(int levelOfDetail)
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
void convertFromQImage(const QImage &image, const KoColorProfile *profile, qint32 offsetX=0, qint32 offsetY=0)
bool fillProperties(const KisPropertiesConfiguration *setting, KisResourcesInterfaceSP resourcesInterface, bool invertAdditionally)
KisTextureMaskInfo & operator=(const KisTextureMaskInfo &rhs)
KisPaintDeviceSP m_mask
KisTextureMaskInfo(int levelOfDetail, bool preserveAlpha)
KisPaintDeviceSP mask()
virtual void setOpacity(quint8 *pixels, quint8 alpha, qint32 nPixels) const =0
Write API docs here.
Definition KoPattern.h:21
KoResourceSP resource() const noexcept
static bool qFuzzyCompare(half p1, half p2)
unsigned int QRgb
Rect ensureRectNotSmaller(Rect rc, const decltype(Rect().size()) &size)
QSharedPointer< KisTextureMaskInfo > m_lodInfo
KisTextureMaskInfoSP fetchCachedTextureInfo(KisTextureMaskInfoSP info)
QSharedPointer< KisTextureMaskInfo > m_mainInfo
bool read(const KisPropertiesConfiguration *setting)
KisEmbeddedTextureData textureData
static KoColorSpaceRegistry * instance()
const KoColorSpace * rgb8(const QString &profileName=QString())
const KoColorSpace * alpha8()