File indexing completed on 2025-01-26 04:11:30
0001 /* 0002 * SPDX-FileCopyrightText: 2017 Dmitry Kazakov <dimula73@gmail.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KisTextureMaskInfo.h" 0008 0009 #include <kis_paintop_settings.h> 0010 #include <resources/KoPattern.h> 0011 #include "KisTextureOptionData.h" 0012 #include <KoResourceLoadResult.h> 0013 0014 #include <KoColorSpace.h> 0015 #include <KoColorSpaceRegistry.h> 0016 0017 #include <kis_algebra_2d.h> 0018 #include <kis_lod_transform.h> 0019 #include <kis_iterator_ng.h> 0020 0021 #include <QGlobalStatic> 0022 0023 /**********************************************************************/ 0024 /* KisTextureMaskInfo */ 0025 /**********************************************************************/ 0026 0027 0028 KisTextureMaskInfo::KisTextureMaskInfo(int levelOfDetail, bool preserveAlpha) 0029 : m_levelOfDetail(levelOfDetail) 0030 , m_preserveAlpha(preserveAlpha) 0031 { 0032 } 0033 0034 KisTextureMaskInfo::KisTextureMaskInfo(const KisTextureMaskInfo &rhs) 0035 : m_levelOfDetail(rhs.m_levelOfDetail) 0036 , m_preserveAlpha(rhs.m_preserveAlpha) 0037 , m_pattern(rhs.m_pattern) 0038 , m_scale(rhs.m_scale) 0039 , m_brightness(rhs.m_brightness) 0040 , m_contrast(rhs.m_contrast) 0041 , m_neutralPoint(rhs.m_neutralPoint) 0042 , m_invert(rhs.m_invert) 0043 , m_cutoffLeft(rhs.m_cutoffLeft) 0044 , m_cutoffRight(rhs.m_cutoffRight) 0045 , m_cutoffPolicy(rhs.m_cutoffPolicy) 0046 0047 { 0048 } 0049 0050 KisTextureMaskInfo::~KisTextureMaskInfo() 0051 { 0052 } 0053 0054 bool operator==(const KisTextureMaskInfo &lhs, const KisTextureMaskInfo &rhs) { 0055 return 0056 lhs.m_levelOfDetail == rhs.m_levelOfDetail && 0057 (lhs.m_pattern == rhs.m_pattern || 0058 (lhs.m_pattern && 0059 rhs.m_pattern && 0060 lhs.m_pattern->md5Sum() == rhs.m_pattern->md5Sum())) && 0061 qFuzzyCompare(lhs.m_scale, rhs.m_scale) && 0062 qFuzzyCompare(lhs.m_brightness, rhs.m_brightness) && 0063 qFuzzyCompare(lhs.m_contrast, rhs.m_contrast) && 0064 qFuzzyCompare(lhs.m_neutralPoint, rhs.m_neutralPoint) && 0065 lhs.m_invert == rhs.m_invert && 0066 lhs.m_cutoffLeft == rhs.m_cutoffLeft && 0067 lhs.m_cutoffRight == rhs.m_cutoffRight && 0068 lhs.m_cutoffPolicy == rhs.m_cutoffPolicy && 0069 lhs.m_preserveAlpha == rhs.m_preserveAlpha; 0070 } 0071 0072 KisTextureMaskInfo &KisTextureMaskInfo::operator=(const KisTextureMaskInfo &rhs) 0073 { 0074 m_levelOfDetail = rhs.m_levelOfDetail; 0075 m_pattern = rhs.m_pattern; 0076 m_scale = rhs.m_scale; 0077 m_brightness = rhs.m_brightness; 0078 m_contrast = rhs.m_contrast; 0079 m_neutralPoint = rhs.m_neutralPoint; 0080 m_invert = rhs.m_invert; 0081 m_cutoffLeft = rhs.m_cutoffLeft; 0082 m_cutoffRight = rhs.m_cutoffRight; 0083 m_cutoffPolicy = rhs.m_cutoffPolicy; 0084 m_preserveAlpha = rhs.m_preserveAlpha; 0085 0086 return *this; 0087 } 0088 0089 bool KisTextureMaskInfo::isValid() const 0090 { 0091 return (m_mask && m_maskBounds.isValid()); 0092 } 0093 0094 int KisTextureMaskInfo::levelOfDetail() const { 0095 return m_levelOfDetail; 0096 } 0097 0098 bool KisTextureMaskInfo::hasMask() const { 0099 return m_mask; 0100 } 0101 0102 KisPaintDeviceSP KisTextureMaskInfo::mask() { 0103 return m_mask; 0104 } 0105 0106 QRect KisTextureMaskInfo::maskBounds() const { 0107 return m_maskBounds; 0108 } 0109 0110 bool KisTextureMaskInfo::fillProperties(const KisPropertiesConfiguration *setting, KisResourcesInterfaceSP resourcesInterface) 0111 { 0112 KisTextureOptionData data; 0113 data.read(setting); 0114 0115 if (!data.isEnabled || data.textureData.isNull()) { 0116 return false; 0117 } 0118 0119 m_pattern = data.textureData.loadLinkedPattern(resourcesInterface).resource<KoPattern>(); 0120 0121 if (!m_pattern) { 0122 qWarning() << "WARNING: Couldn't load the pattern for a stroke (KisTextureMaskInfo)"; 0123 return false; 0124 } 0125 0126 m_scale = data.scale; 0127 m_brightness = data.brightness; 0128 m_contrast = data.contrast; 0129 m_neutralPoint = data.neutralPoint; 0130 m_invert = data.invert; 0131 m_cutoffLeft = data.cutOffLeft; 0132 m_cutoffRight = data.cutOffRight; 0133 m_cutoffPolicy = data.cutOffPolicy; 0134 0135 return true; 0136 } 0137 0138 void KisTextureMaskInfo::recalculateMask() 0139 { 0140 if (!m_pattern) return; 0141 0142 const KoColorSpace* cs; 0143 const bool useAlpha = m_pattern->hasAlpha() && m_preserveAlpha; 0144 0145 if (useAlpha) { 0146 cs = KoColorSpaceRegistry::instance()->rgb8(); 0147 } else { 0148 cs = KoColorSpaceRegistry::instance()->alpha8(); 0149 } 0150 if (!m_mask) { 0151 m_mask = new KisPaintDevice(cs); 0152 } 0153 0154 QImage mask = m_pattern->pattern(); 0155 0156 if ((mask.format() != QImage::Format_RGB32) 0157 || (mask.format() != QImage::Format_ARGB32)) { 0158 mask.convertTo(QImage::Format_ARGB32); 0159 } 0160 0161 qreal scale = m_scale * KisLodTransform::lodToScale(m_levelOfDetail); 0162 0163 if (!qFuzzyCompare(scale, 0.0) && !qFuzzyCompare(scale, 1.0)) { 0164 QTransform tf; 0165 tf.scale(scale, scale); 0166 QRect rc = KisAlgebra2D::ensureRectNotSmaller(tf.mapRect(mask.rect()), QSize(2,2)); 0167 mask = mask.scaled(rc.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); 0168 } else { 0169 // detach the mask from the file loaded from the storage 0170 mask = QImage(mask); 0171 } 0172 0173 QRgb* pixel = reinterpret_cast<QRgb*>(mask.bits()); 0174 const int width = mask.width(); 0175 const int height = mask.height(); 0176 0177 KisHLineIteratorSP iter = m_mask->createHLineIteratorNG(0, 0, width); 0178 0179 for (int row = 0; row < height; ++row) { 0180 for (int col = 0; col < width; ++col) { 0181 const QRgb currentPixel = pixel[row * width + col]; 0182 0183 const int red = qRed(currentPixel); 0184 const int green = qGreen(currentPixel); 0185 const int blue = qBlue(currentPixel); 0186 float alpha = qAlpha(currentPixel) / 255.0; 0187 0188 const int grayValue = (red * 11 + green * 16 + blue * 5) / 32; 0189 float maskValue = (grayValue / 255.0) * alpha + (1 - alpha); 0190 0191 maskValue = maskValue - m_brightness; 0192 0193 maskValue = ((maskValue - 0.5)*m_contrast)+0.5; 0194 0195 if (maskValue > 1.0) {maskValue = 1;} 0196 else if (maskValue < 0) {maskValue = 0;} 0197 0198 if (m_invert) { 0199 maskValue = 1 - maskValue; 0200 } 0201 0202 maskValue = qBound(0.0f, maskValue, 1.0f); 0203 0204 float neutralAdjustedValue; 0205 0206 //Adjust neutral point in linear fashion. Uses separate linear equations from 0 to neutralPoint, and neutralPoint to 1, 0207 //to prevent loss of detail (clipping). 0208 if (m_neutralPoint == 1 || (m_neutralPoint != 0 && maskValue <= m_neutralPoint)) { 0209 neutralAdjustedValue = maskValue / (2 * m_neutralPoint); 0210 } else { 0211 neutralAdjustedValue = 0.5 + (maskValue - m_neutralPoint) / (2 - 2 * m_neutralPoint); 0212 } 0213 0214 if (m_cutoffPolicy == 1 && (neutralAdjustedValue < (m_cutoffLeft / 255.0) || neutralAdjustedValue >(m_cutoffRight / 255.0))) { 0215 // mask out the dab if it's outside the pattern's cutoff points 0216 alpha = OPACITY_TRANSPARENT_F; 0217 if (!useAlpha) { 0218 neutralAdjustedValue = alpha; 0219 } 0220 } else if (m_cutoffPolicy == 2 && (neutralAdjustedValue < (m_cutoffLeft / 255.0) || neutralAdjustedValue >(m_cutoffRight / 255.0))) { 0221 alpha = OPACITY_OPAQUE_F; 0222 if (!useAlpha) { 0223 neutralAdjustedValue = alpha; 0224 } 0225 } 0226 0227 if (useAlpha) { 0228 int finalValue = qRound(neutralAdjustedValue * 255.0); 0229 pixel[row * width + col] = QColor(finalValue, finalValue, finalValue, qRound(alpha * 255.0)).rgba(); 0230 } else { 0231 cs->setOpacity(iter->rawData(), neutralAdjustedValue, 1); 0232 iter->nextPixel(); 0233 } 0234 } 0235 if (!useAlpha) { 0236 iter->nextRow(); 0237 } 0238 } 0239 if (useAlpha) { 0240 m_mask->convertFromQImage(mask, 0); 0241 } 0242 m_maskBounds = QRect(0, 0, width, height); 0243 } 0244 0245 bool KisTextureMaskInfo::hasAlpha() { 0246 return m_pattern->hasAlpha(); 0247 } 0248 0249 /**********************************************************************/ 0250 /* KisTextureMaskInfoCache */ 0251 /**********************************************************************/ 0252 0253 Q_GLOBAL_STATIC(KisTextureMaskInfoCache, s_instance) 0254 0255 KisTextureMaskInfoCache *KisTextureMaskInfoCache::instance() 0256 { 0257 return s_instance; 0258 } 0259 0260 KisTextureMaskInfoSP KisTextureMaskInfoCache::fetchCachedTextureInfo(KisTextureMaskInfoSP info) { 0261 QMutexLocker locker(&m_mutex); 0262 0263 KisTextureMaskInfoSP &cachedInfo = 0264 info->levelOfDetail() > 0 ? m_lodInfo : m_mainInfo; 0265 0266 if (!cachedInfo || *cachedInfo != *info) { 0267 cachedInfo = info; 0268 cachedInfo->recalculateMask(); 0269 } 0270 0271 return cachedInfo; 0272 }