File indexing completed on 2024-05-12 15:56:11

0001 /*
0002  *  SPDX-FileCopyrightText: 2013 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kis_predefined_brush_factory.h"
0008 
0009 #include <QApplication>
0010 #include <QThread>
0011 #include <QDomDocument>
0012 #include <QFileInfo>
0013 #include "kis_gbr_brush.h"
0014 #include "kis_png_brush.h"
0015 #include <kis_dom_utils.h>
0016 #include <KisResourcesInterface.h>
0017 
0018 KisPredefinedBrushFactory::KisPredefinedBrushFactory(const QString &brushType)
0019     : m_id(brushType)
0020 {
0021 }
0022 
0023 QString KisPredefinedBrushFactory::id() const
0024 {
0025     return m_id;
0026 }
0027 
0028 KoResourceLoadResult KisPredefinedBrushFactory::createBrush(const QDomElement& brushDefinition, KisResourcesInterfaceSP resourcesInterface)
0029 {
0030     auto resourceSourceAdapter = resourcesInterface->source<KisBrush>(ResourceType::Brushes);
0031     const QString brushFileName = brushDefinition.attribute("filename", "");
0032     const QString brushMD5Sum = brushDefinition.attribute("md5sum", "");
0033     KisBrushSP brush = resourceSourceAdapter.bestMatch(brushMD5Sum, brushFileName, "");
0034     if (!brush) {
0035         return KoResourceSignature(ResourceType::Brushes, brushMD5Sum, brushFileName, "");
0036     }
0037 
0038     // we always return a copy of the brush!
0039     brush = brush->clone().dynamicCast<KisBrush>();
0040 
0041     double spacing = KisDomUtils::toDouble(brushDefinition.attribute("spacing", "0.25"));
0042     brush->setSpacing(spacing);
0043 
0044     bool useAutoSpacing = KisDomUtils::toInt(brushDefinition.attribute("useAutoSpacing", "0"));
0045     qreal autoSpacingCoeff = KisDomUtils::toDouble(brushDefinition.attribute("autoSpacingCoeff", "1.0"));
0046     brush->setAutoSpacing(useAutoSpacing, autoSpacingCoeff);
0047 
0048     double angle = KisDomUtils::toDouble(brushDefinition.attribute("angle", "0.0"));
0049     brush->setAngle(angle);
0050 
0051     double scale = KisDomUtils::toDouble(brushDefinition.attribute("scale", "1.0"));
0052     brush->setScale(scale);
0053 
0054     KisColorfulBrush *colorfulBrush = dynamic_cast<KisColorfulBrush*>(brush.data());
0055     if (colorfulBrush) {
0056         quint8 adjustmentMidPoint = brushDefinition.attribute("AdjustmentMidPoint", "127").toInt();
0057         qreal brightnessAdjustment = brushDefinition.attribute("BrightnessAdjustment").toDouble();
0058         qreal contrastAdjustment = brushDefinition.attribute("ContrastAdjustment").toDouble();
0059 
0060         const int adjustmentVersion = brushDefinition.attribute("AdjustmentVersion", "1").toInt();
0061         const bool autoAdjustMidPoint = brushDefinition.attribute("AutoAdjustMidPoint", "0").toInt();
0062         const bool hasAutoAdjustMidPoint = brushDefinition.hasAttribute("AutoAdjustMidPoint");
0063 
0064         /**
0065          * In Krita 4.x releases there was a bug that caused lightness
0066          * adjustments to be applied to the brush **twice**. It happened
0067          * due to the fact that copy-ctor called brushTipImage() virtual
0068          * method instead of just copying the image itself.
0069          *
0070          * In Krita 5 we should open these brushes in somewhat the same way.
0071          * The problem is that we cannot convert the numbers precisely, because
0072          * after applying a piecewice-linear function twice we get a
0073          * quadratic function. So we fall-back to a blunt parameters scaling,
0074          * which gives result that is just "good enough".
0075          *
0076          * NOTE: AutoAdjustMidPoint option appeared only in Krita 5, so it
0077          * automatically means the adjustments should be applied in the new way.
0078          */
0079         if (adjustmentVersion < 2 && !hasAutoAdjustMidPoint) {
0080             adjustmentMidPoint = qBound(0, 127 + (int(adjustmentMidPoint) - 127) * 2, 255);
0081             brightnessAdjustment *= 2.0;
0082             contrastAdjustment *= 2.0;
0083 
0084             /**
0085              * In Krita we also changed formula for contrast calculation in
0086              * negative part, so we need to convert that as well.
0087              */
0088             if (contrastAdjustment < 0) {
0089                 contrastAdjustment = 1.0 / (1.0 - contrastAdjustment) - 1.0;
0090             }
0091         }
0092 
0093         colorfulBrush->setAdjustmentMidPoint(adjustmentMidPoint);
0094         colorfulBrush->setBrightnessAdjustment(brightnessAdjustment);
0095         colorfulBrush->setContrastAdjustment(contrastAdjustment);
0096         colorfulBrush->setAutoAdjustMidPoint(autoAdjustMidPoint);
0097     }
0098 
0099     auto legacyBrushApplication = [] (KisColorfulBrush *colorfulBrush, bool forceColorToAlpha) {
0100         /**
0101          * In Krita versions before 4.4 series "ColorAsMask" could
0102          * be overridden to false when the brush had no **color**
0103          * inside. That changed in Krita 4.4.x series, when
0104          * "brushApplication" replaced all the automatic heuristics
0105          */
0106         return (colorfulBrush && colorfulBrush->hasColorAndTransparency() && !forceColorToAlpha) ? IMAGESTAMP : ALPHAMASK;
0107     };
0108 
0109 
0110     if (brushDefinition.hasAttribute("preserveLightness")) {
0111         const int preserveLightness = KisDomUtils::toInt(brushDefinition.attribute("preserveLightness", "0"));
0112         const bool useColorAsMask = (bool)brushDefinition.attribute("ColorAsMask", "1").toInt();
0113         brush->setBrushApplication(preserveLightness ? LIGHTNESSMAP : legacyBrushApplication(colorfulBrush, useColorAsMask));
0114     }
0115     else if (brushDefinition.hasAttribute("brushApplication")) {
0116         enumBrushApplication brushApplication = static_cast<enumBrushApplication>(KisDomUtils::toInt(brushDefinition.attribute("brushApplication", "0")));
0117         brush->setBrushApplication(brushApplication);
0118     }
0119     else if (brushDefinition.hasAttribute("ColorAsMask")) {
0120         KIS_SAFE_ASSERT_RECOVER_NOOP(colorfulBrush);
0121 
0122         const bool useColorAsMask = (bool)brushDefinition.attribute("ColorAsMask", "1").toInt();
0123         brush->setBrushApplication(legacyBrushApplication(colorfulBrush, useColorAsMask));
0124     }
0125     else {
0126         /**
0127          * In Krita versions before 4.4 series we used to automatrically select
0128          * the brush application depending on the presence of the color in the
0129          * brush, even when there was no "ColorAsMask" field.
0130          */
0131         brush->setBrushApplication(legacyBrushApplication(colorfulBrush, false));
0132     }
0133 
0134     return brush;
0135 }