File indexing completed on 2024-05-19 04:24:16
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_abr_brush.h" 0016 #include "kis_svg_brush.h" 0017 #include <kis_dom_utils.h> 0018 #include <KisResourcesInterface.h> 0019 #include "kis_imagepipe_brush.h" 0020 0021 KisPredefinedBrushFactory::KisPredefinedBrushFactory(const QString &brushType) 0022 : m_id(brushType) 0023 { 0024 } 0025 0026 QString KisPredefinedBrushFactory::id() const 0027 { 0028 return m_id; 0029 } 0030 0031 KoResourceLoadResult KisPredefinedBrushFactory::createBrush(const KisBrushModel::BrushData &brushData, KisResourcesInterfaceSP resourcesInterface) 0032 { 0033 auto resourceSourceAdapter = resourcesInterface->source<KisBrush>(ResourceType::Brushes); 0034 const QString brushFileName = brushData.predefinedBrush.resourceSignature.filename; 0035 const QString brushMD5Sum = brushData.predefinedBrush.resourceSignature.md5sum; 0036 KisBrushSP brush = resourceSourceAdapter.bestMatch(brushMD5Sum, brushFileName, ""); 0037 if (!brush) { 0038 return KoResourceSignature(ResourceType::Brushes, brushMD5Sum, brushFileName, ""); 0039 } 0040 0041 // we always return a copy of the brush! 0042 brush = brush->clone().dynamicCast<KisBrush>(); 0043 0044 brush->setSpacing(brushData.common.spacing); 0045 brush->setAutoSpacing(brushData.common.useAutoSpacing, brushData.common.autoSpacingCoeff); 0046 brush->setAngle(brushData.common.angle); 0047 brush->setScale(brushData.predefinedBrush.scale); 0048 0049 KisColorfulBrush *colorfulBrush = dynamic_cast<KisColorfulBrush*>(brush.data()); 0050 if (colorfulBrush) { 0051 colorfulBrush->setAdjustmentMidPoint(brushData.predefinedBrush.adjustmentMidPoint); 0052 colorfulBrush->setBrightnessAdjustment(brushData.predefinedBrush.brightnessAdjustment); 0053 colorfulBrush->setContrastAdjustment(brushData.predefinedBrush.contrastAdjustment); 0054 colorfulBrush->setAutoAdjustMidPoint(brushData.predefinedBrush.autoAdjustMidPoint); 0055 } 0056 0057 brush->setBrushApplication(brushData.predefinedBrush.application); 0058 0059 return brush; 0060 } 0061 0062 KoResourceLoadResult KisPredefinedBrushFactory::createBrush(const QDomElement& brushDefinition, KisResourcesInterfaceSP resourcesInterface) 0063 { 0064 auto data = createBrushModelImpl(brushDefinition, resourcesInterface); 0065 0066 if (std::holds_alternative<KisBrushModel::BrushData>(data)) { 0067 return createBrush(std::get<KisBrushModel::BrushData>(data), resourcesInterface); 0068 } else if (std::holds_alternative<KoResourceSignature>(data)) { 0069 return std::get<KoResourceSignature>(data); 0070 } 0071 0072 // fallback, should never reach! 0073 return KoResourceSignature(ResourceType::Brushes, "", "", ""); 0074 } 0075 0076 std::optional<KisBrushModel::BrushData> KisPredefinedBrushFactory::createBrushModel(const QDomElement &element, KisResourcesInterfaceSP resourcesInterface) 0077 { 0078 auto data = createBrushModelImpl(element, resourcesInterface); 0079 return std::holds_alternative<KisBrushModel::BrushData>(data) ? 0080 std::make_optional(std::get<KisBrushModel::BrushData>(data)) : 0081 std::nullopt; 0082 } 0083 0084 std::variant<KisBrushModel::BrushData, KoResourceSignature> KisPredefinedBrushFactory::createBrushModelImpl(const QDomElement &element, KisResourcesInterfaceSP resourcesInterface) 0085 { 0086 KisBrushModel::BrushData brush; 0087 0088 auto resourceSourceAdapter = resourcesInterface->source<KisBrush>(ResourceType::Brushes); 0089 const QString brushFileName = element.attribute("filename", ""); 0090 const QString brushMD5Sum = element.attribute("md5sum", ""); 0091 KisBrushSP brushResource = resourceSourceAdapter.bestMatch(brushMD5Sum, brushFileName, ""); 0092 if (!brushResource) { 0093 return KoResourceSignature(ResourceType::Brushes, brushMD5Sum, brushFileName, ""); 0094 } 0095 0096 brush.type = KisBrushModel::Predefined; 0097 0098 /// we should first initialize data from the brush, because it may 0099 /// change the spacing properties embedded into some brushes 0100 loadFromBrushResource(brush.common, brush.predefinedBrush, brushResource); 0101 0102 brush.common.angle = KisDomUtils::toDouble(element.attribute("angle", "0.0")); 0103 brush.common.spacing = KisDomUtils::toDouble(element.attribute("spacing", "0.25")); 0104 brush.common.useAutoSpacing = KisDomUtils::toInt(element.attribute("useAutoSpacing", "0")); 0105 brush.common.autoSpacingCoeff = KisDomUtils::toDouble(element.attribute("autoSpacingCoeff", "1.0")); 0106 0107 brush.predefinedBrush.scale = KisDomUtils::toDouble(element.attribute("scale", "1.0")); 0108 0109 // legacy support... 0110 if (element.attribute("BrushVersion", "1") == "1") { 0111 brush.predefinedBrush.scale *= 2.0; 0112 } 0113 0114 KisColorfulBrush *colorfulBrush = dynamic_cast<KisColorfulBrush*>(brushResource.data()); 0115 if (colorfulBrush) { 0116 quint8 adjustmentMidPoint = element.attribute("AdjustmentMidPoint", "127").toInt(); 0117 qreal brightnessAdjustment = element.attribute("BrightnessAdjustment").toDouble(); 0118 qreal contrastAdjustment = element.attribute("ContrastAdjustment").toDouble(); 0119 0120 const int adjustmentVersion = element.attribute("AdjustmentVersion", "1").toInt(); 0121 const bool autoAdjustMidPoint = element.attribute("AutoAdjustMidPoint", "0").toInt(); 0122 const bool hasAutoAdjustMidPoint = element.hasAttribute("AutoAdjustMidPoint"); 0123 0124 /** 0125 * In Krita 4.x releases there was a bug that caused lightness 0126 * adjustments to be applied to the brush **twice**. It happened 0127 * due to the fact that copy-ctor called brushTipImage() virtual 0128 * method instead of just copying the image itself. 0129 * 0130 * In Krita 5 we should open these brushes in somewhat the same way. 0131 * The problem is that we cannot convert the numbers precisely, because 0132 * after applying a piecewise-linear function twice we get a 0133 * quadratic function. So we fall-back to a blunt parameters scaling, 0134 * which gives result that is just "good enough". 0135 * 0136 * NOTE: AutoAdjustMidPoint option appeared only in Krita 5, so it 0137 * automatically means the adjustments should be applied in the new way. 0138 */ 0139 if (adjustmentVersion < 2 && !hasAutoAdjustMidPoint) { 0140 adjustmentMidPoint = qBound(0, 127 + (int(adjustmentMidPoint) - 127) * 2, 255); 0141 brightnessAdjustment *= 2.0; 0142 contrastAdjustment *= 2.0; 0143 0144 /** 0145 * In Krita we also changed formula for contrast calculation in 0146 * negative part, so we need to convert that as well. 0147 */ 0148 if (contrastAdjustment < 0) { 0149 contrastAdjustment = 1.0 / (1.0 - contrastAdjustment) - 1.0; 0150 } 0151 } 0152 0153 brush.predefinedBrush.adjustmentMidPoint = adjustmentMidPoint; 0154 brush.predefinedBrush.brightnessAdjustment = brightnessAdjustment; 0155 brush.predefinedBrush.contrastAdjustment = contrastAdjustment; 0156 brush.predefinedBrush.autoAdjustMidPoint = autoAdjustMidPoint; 0157 brush.predefinedBrush.hasColorAndTransparency = colorfulBrush->hasColorAndTransparency(); 0158 } 0159 0160 auto legacyBrushApplication = [] (KisColorfulBrush *colorfulBrush, bool forceColorToAlpha) { 0161 /** 0162 * In Krita versions before 4.4 series "ColorAsMask" could 0163 * be overridden to false when the brush had no **color** 0164 * inside. That changed in Krita 4.4.x series, when 0165 * "brushApplication" replaced all the automatic heuristics 0166 */ 0167 return (colorfulBrush && colorfulBrush->hasColorAndTransparency() && !forceColorToAlpha) ? IMAGESTAMP : ALPHAMASK; 0168 }; 0169 0170 0171 if (element.hasAttribute("preserveLightness")) { 0172 const int preserveLightness = KisDomUtils::toInt(element.attribute("preserveLightness", "0")); 0173 const bool useColorAsMask = (bool)element.attribute("ColorAsMask", "1").toInt(); 0174 brush.predefinedBrush.application = preserveLightness ? LIGHTNESSMAP : legacyBrushApplication(colorfulBrush, useColorAsMask); 0175 } 0176 else if (element.hasAttribute("brushApplication")) { 0177 enumBrushApplication brushApplication = static_cast<enumBrushApplication>(KisDomUtils::toInt(element.attribute("brushApplication", "0"))); 0178 brush.predefinedBrush.application = brushApplication; 0179 } 0180 else if (element.hasAttribute("ColorAsMask")) { 0181 KIS_SAFE_ASSERT_RECOVER_NOOP(colorfulBrush); 0182 0183 const bool useColorAsMask = (bool)element.attribute("ColorAsMask", "1").toInt(); 0184 brush.predefinedBrush.application =legacyBrushApplication(colorfulBrush, useColorAsMask); 0185 } 0186 else { 0187 /** 0188 * In Krita versions before 4.4 series we used to automatically select 0189 * the brush application depending on the presence of the color in the 0190 * brush, even when there was no "ColorAsMask" field. 0191 */ 0192 brush.predefinedBrush.application = legacyBrushApplication(colorfulBrush, false); 0193 } 0194 0195 return {brush}; 0196 } 0197 0198 void KisPredefinedBrushFactory::loadFromBrushResource(KisBrushModel::CommonData &commonData, KisBrushModel::PredefinedBrushData &predefinedBrushData, KisBrushSP brushResource) 0199 { 0200 commonData.spacing = brushResource->spacing(); 0201 commonData.useAutoSpacing = brushResource->autoSpacingActive(); 0202 commonData.autoSpacingCoeff = brushResource->autoSpacingCoeff(); 0203 predefinedBrushData.resourceSignature = brushResource->signature(); 0204 predefinedBrushData.brushType = brushResource->brushType(); 0205 predefinedBrushData.baseSize = { brushResource->width(), brushResource->height() }; 0206 0207 if (brushResource.dynamicCast<KisGbrBrush>()) { 0208 predefinedBrushData.subtype = "gbr_brush"; 0209 } else if (brushResource.dynamicCast<KisAbrBrush>()) { 0210 predefinedBrushData.subtype = "abr_brush"; 0211 } else if (brushResource.dynamicCast<KisPngBrush>()) { 0212 predefinedBrushData.subtype = "png_brush"; 0213 } else if (brushResource.dynamicCast<KisSvgBrush>()) { 0214 predefinedBrushData.subtype = "svg_brush"; 0215 } else { 0216 KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "unknown brush type"); 0217 } 0218 0219 KisImagePipeBrushSP imagepipeBrush = brushResource.dynamicCast<KisImagePipeBrush>(); 0220 if (imagepipeBrush) { 0221 predefinedBrushData.parasiteSelection = imagepipeBrush->parasiteSelection(); 0222 } 0223 } 0224 0225 void KisPredefinedBrushFactory::toXML(QDomDocument &doc, QDomElement &e, const KisBrushModel::BrushData &model) 0226 { 0227 Q_UNUSED(doc); 0228 0229 e.setAttribute("type", id()); 0230 e.setAttribute("BrushVersion", "2"); 0231 0232 e.setAttribute("filename", model.predefinedBrush.resourceSignature.filename); 0233 e.setAttribute("md5sum", model.predefinedBrush.resourceSignature.md5sum); 0234 e.setAttribute("spacing", QString::number(model.common.spacing)); 0235 e.setAttribute("useAutoSpacing", QString::number(model.common.useAutoSpacing)); 0236 e.setAttribute("autoSpacingCoeff", QString::number(model.common.autoSpacingCoeff)); 0237 e.setAttribute("angle", QString::number(model.common.angle)); 0238 e.setAttribute("scale", QString::number(model.predefinedBrush.scale)); 0239 e.setAttribute("brushApplication", QString::number((int)model.predefinedBrush.application)); 0240 0241 if (id() == "abr_brush") { 0242 e.setAttribute("name", model.predefinedBrush.resourceSignature.name); 0243 0244 } else { 0245 // all other brushes are derived from KisColorfulBrush 0246 0247 // legacy setting, now 'brushApplication' is used instead 0248 e.setAttribute("ColorAsMask", QString::number((int)(model.predefinedBrush.application != IMAGESTAMP))); 0249 0250 e.setAttribute("AdjustmentMidPoint", QString::number(model.predefinedBrush.adjustmentMidPoint)); 0251 e.setAttribute("BrightnessAdjustment", QString::number(model.predefinedBrush.brightnessAdjustment)); 0252 e.setAttribute("ContrastAdjustment", QString::number(model.predefinedBrush.contrastAdjustment)); 0253 e.setAttribute("AutoAdjustMidPoint", QString::number(model.predefinedBrush.autoAdjustMidPoint)); 0254 e.setAttribute("AdjustmentVersion", QString::number(2)); 0255 } 0256 }