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 }