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 }