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 }