File indexing completed on 2024-06-16 04:16:48

0001 /*
0002  * This file is part of the KDE project
0003  *
0004  * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "patterngenerator.h"
0010 
0011 #include <QPoint>
0012 
0013 
0014 #include <kpluginfactory.h>
0015 #include <klocalizedstring.h>
0016 
0017 #include <KoColor.h>
0018 #include <KisResourceTypes.h>
0019 #include <resources/KoPattern.h>
0020 
0021 #include <kis_debug.h>
0022 #include <kis_fill_painter.h>
0023 #include <kis_image.h>
0024 #include <kis_paint_device.h>
0025 #include <kis_layer.h>
0026 #include <generator/kis_generator_registry.h>
0027 #include <kis_global.h>
0028 #include <kis_selection.h>
0029 #include <kis_types.h>
0030 #include <filter/kis_filter_configuration.h>
0031 #include <kis_processing_information.h>
0032 #include <kis_pattern_chooser.h>
0033 #include <KisResourcesInterface.h>
0034 #include <KoResourceLoadResult.h>
0035 
0036 #include "kis_wdg_pattern.h"
0037 #include "ui_wdgpatternoptions.h"
0038 
0039 K_PLUGIN_FACTORY_WITH_JSON(KritaPatternGeneratorFactory, "kritapatterngenerator.json", registerPlugin<KritaPatternGenerator>();)
0040 
0041 KritaPatternGenerator::KritaPatternGenerator(QObject *parent, const QVariantList &)
0042         : QObject(parent)
0043 {
0044     KisGeneratorRegistry::instance()->add(new PatternGenerator());
0045 }
0046 
0047 KritaPatternGenerator::~KritaPatternGenerator()
0048 {
0049 }
0050 
0051 /****************************************************************************/
0052 /*              KoPatternGeneratorConfiguration                             */
0053 /****************************************************************************/
0054 
0055 class PatternGeneratorConfiguration : public KisFilterConfiguration
0056 {
0057 public:
0058     PatternGeneratorConfiguration(const QString & name, qint32 version, KisResourcesInterfaceSP resourcesInterface)
0059         : KisFilterConfiguration(name, version, resourcesInterface)
0060     {
0061     }
0062 
0063     PatternGeneratorConfiguration(const PatternGeneratorConfiguration &rhs)
0064         : KisFilterConfiguration(rhs)
0065     {
0066     }
0067 
0068     virtual KisFilterConfigurationSP clone() const override {
0069         return new PatternGeneratorConfiguration(*this);
0070     }
0071 
0072     KoResourceLoadResult pattern(KisResourcesInterfaceSP resourcesInterface) const
0073     {
0074         const QString patternMD5 = getString("md5sum", "");
0075         const QString patternName = getString("pattern", "Grid01.pat");
0076         const QString patternFileName = getString("fileName", "");
0077         auto source = resourcesInterface->source<KoPattern>(ResourceType::Patterns);
0078         KoResourceLoadResult res = source.bestMatchLoadResult(patternMD5, patternFileName, patternName);
0079         return res;
0080     }
0081 
0082     KoPatternSP pattern() const {
0083         return pattern(resourcesInterface()).resource<KoPattern>();
0084     }
0085 
0086     QTransform transform() const {
0087         const bool constrainScale = getBool("transform_keep_scale_aspect", true);
0088         const qreal scaleX = getDouble("transform_scale_x", 1.0);
0089         // Ensure that the size y component is equal to the x component if keepSizeSquare is true
0090         const qreal scaleY = constrainScale ? scaleX : getDouble("transform_scale_y", 1.0);
0091         const qreal positionX = getInt("transform_offset_x", 0);
0092         const qreal positionY = getInt("transform_offset_y", 0);
0093         const qreal shearX = getDouble("transform_shear_x", 0.0);
0094         const qreal shearY = getDouble("transform_shear_y", 0.0);
0095         const qreal rotationX = getDouble("transform_rotation_x", 0.0);
0096         const qreal rotationY = getDouble("transform_rotation_y", 0.0);
0097         const qreal rotationZ = getDouble("transform_rotation_z", 0.0);
0098         const bool align = getBool("transform_align_to_pixel_grid", false);
0099         const qint32 alignX = getInt("transform_align_to_pixel_grid_x", 1);
0100         const qint32 alignY = getInt("transform_align_to_pixel_grid_y", 1);
0101 
0102         QTransform transform;
0103 
0104         if (align && qFuzzyIsNull(rotationX) && qFuzzyIsNull(rotationY) && pattern()) {
0105             // STEP 1: compose the transformation
0106             transform.shear(shearX, shearY);
0107             transform.scale(scaleX, scaleY);
0108             transform.rotate(rotationZ);
0109             // STEP 2: transform the horizontal and vertical vectors of the
0110             //         "repetition rect" (which size is some multiple of the
0111             //         pattern size)
0112             const QSizeF repetitionRectSize(
0113                 static_cast<qreal>(alignX * pattern()->width()),
0114                 static_cast<qreal>(alignY * pattern()->height())
0115             );
0116             // u1 is the unaligned vector that goes from the origin to the top-right
0117             // corner of the repetition rect. u2 is the unaligned vector that
0118             // goes from the origin to the bottom-left corner of the repetition rect
0119             const QPointF u1 = transform.map(QPointF(repetitionRectSize.width(), 0.0));
0120             const QPointF u2 = transform.map(QPointF(0.0, repetitionRectSize.height()));
0121             // STEP 3: align the transformed vectors to the pixel grid. v1 is
0122             //         the aligned version of u1 and v2 is the aligned version of u2
0123             QPointF v1(qRound(u1.x()), qRound(u1.y()));
0124             QPointF v2(qRound(u2.x()), qRound(u2.y()));
0125             // If the following condition is met, that means that the pattern is
0126             // transformed in such a way that the repetition rect corners are
0127             // colinear so we move v1 or v2 to a neighbor position
0128             if (qFuzzyCompare(v1.y() * v2.x(), v2.y() * v1.x()) &&
0129                 !qFuzzyIsNull(v1.x() * v2.x() + v1.y() * v2.y())) {
0130                 // Choose point to move based on distance from non aligned point to
0131                 // aligned point
0132                 const qreal dist1 = kisSquareDistance(u1, v1);
0133                 const qreal dist2 = kisSquareDistance(u2, v2);
0134                 const QPointF *p_u = dist1 > dist2 ? &u1 : &u2;
0135                 QPointF *p_v = dist1 > dist2 ? &v1 : &v2;
0136                 // Then we get the closest pixel aligned point to the current,
0137                 // colinear, point
0138                 QPair<int, qreal> dists[4]{
0139                     {1, kisSquareDistance(*p_u, *p_v + QPointF(0.0, -1.0))},
0140                     {2, kisSquareDistance(*p_u, *p_v + QPointF(1.0, 0.0))},
0141                     {3, kisSquareDistance(*p_u, *p_v + QPointF(0.0, 1.0))},
0142                     {4, kisSquareDistance(*p_u, *p_v + QPointF(-1.0, 0.0))}
0143                 };
0144                 std::sort(
0145                     std::begin(dists), std::end(dists),
0146                     [](const QPair<int, qreal> &a, const QPair<int, qreal> &b)
0147                     {
0148                         return a.second < b.second;
0149                     }
0150                 );
0151                 // Move the point
0152                 if (dists[0].first == 1) {
0153                     p_v->setY(p_v->y() - 1.0);
0154                 } else if (dists[0].first == 2) {
0155                     p_v->setX(p_v->x() + 1.0);
0156                 } else if (dists[0].first == 3) {
0157                     p_v->setY(p_v->y() + 1.0);
0158                 } else {
0159                     p_v->setX(p_v->x() - 1.0);
0160                 }
0161             }
0162             // STEP 4: get the transform that maps the aligned vectors to the
0163             //         untransformed rect (this is in fact the inverse transform)
0164             QPolygonF quad;
0165             quad.append(QPointF(0, 0));
0166             quad.append(v1 / repetitionRectSize.width());
0167             quad.append(v1 / repetitionRectSize.width() + v2 / repetitionRectSize.height());
0168             quad.append(v2 / repetitionRectSize.height());
0169             QTransform::quadToSquare(quad, transform);
0170             // STEP 5: get the forward transform
0171             transform = transform.inverted();
0172             transform.translate(qRound(positionX), qRound(positionY));
0173         } else {
0174             transform.shear(shearX, shearY);
0175             transform.scale(scaleX, scaleY);
0176             transform.rotate(rotationX, Qt::XAxis);
0177             transform.rotate(rotationY, Qt::YAxis);
0178             transform.rotate(rotationZ, Qt::ZAxis);
0179             transform.translate(positionX, positionY);
0180         }
0181         
0182         return transform;
0183     }
0184 
0185     QList<KoResourceLoadResult> linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const override
0186     {
0187         return {pattern(globalResourcesInterface)};
0188     }
0189 };
0190 
0191 
0192 
0193 /****************************************************************************/
0194 /*              KoPatternGenerator                                          */
0195 /****************************************************************************/
0196 
0197 PatternGenerator::PatternGenerator()
0198     : KisGenerator(id(), KoID("basic"), i18n("&Pattern..."))
0199 {
0200     setColorSpaceIndependence(FULLY_INDEPENDENT);
0201     setSupportsPainting(true);
0202 }
0203 
0204 KisFilterConfigurationSP PatternGenerator::factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0205 {
0206     return new PatternGeneratorConfiguration(id().id(), 1, resourcesInterface);
0207 }
0208 
0209 KisFilterConfigurationSP PatternGenerator::defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0210 {
0211     KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
0212 
0213     auto source = resourcesInterface->source<KoPattern>(ResourceType::Patterns);
0214 
0215     if (!source.fallbackResource()) {
0216         return config;
0217     }
0218 
0219     config->setProperty("md5sum", QVariant::fromValue(source.fallbackResource()->md5Sum()));
0220     config->setProperty("fileName", QVariant::fromValue(source.fallbackResource()->filename()));
0221     config->setProperty("pattern", QVariant::fromValue(source.fallbackResource()->name()));
0222 
0223     config->setProperty("transform_shear_x", QVariant::fromValue(0.0));
0224     config->setProperty("transform_shear_y", QVariant::fromValue(0.0));
0225 
0226     config->setProperty("transform_scale_x", QVariant::fromValue(1.0));
0227     config->setProperty("transform_scale_y", QVariant::fromValue(1.0));
0228 
0229     config->setProperty("transform_rotation_x", QVariant::fromValue(0.0));
0230     config->setProperty("transform_rotation_y", QVariant::fromValue(0.0));
0231     config->setProperty("transform_rotation_z", QVariant::fromValue(0.0));
0232 
0233     config->setProperty("transform_offset_x", QVariant::fromValue(0));
0234     config->setProperty("transform_offset_y", QVariant::fromValue(0));
0235 
0236     config->setProperty("transform_keep_scale_aspect", QVariant::fromValue(true));
0237 
0238     config->setProperty("transform_align_to_pixel_grid", QVariant::fromValue(false));
0239     config->setProperty("transform_align_to_pixel_grid_x", QVariant::fromValue(1));
0240     config->setProperty("transform_align_to_pixel_grid_y", QVariant::fromValue(1));
0241 
0242     return config;
0243 }
0244 
0245 KisConfigWidget * PatternGenerator::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const
0246 {
0247     Q_UNUSED(dev);
0248     return new KisWdgPattern(parent);
0249 }
0250 
0251 void PatternGenerator::generate(KisProcessingInformation dstInfo,
0252                                  const QSize& size,
0253                                  const KisFilterConfigurationSP _config,
0254                                  KoUpdater* progressUpdater) const
0255 {
0256     KisPaintDeviceSP dst = dstInfo.paintDevice();
0257 
0258     Q_ASSERT(!dst.isNull());
0259 
0260     const PatternGeneratorConfiguration *config =
0261         dynamic_cast<const PatternGeneratorConfiguration*>(_config.data());
0262 
0263     KIS_SAFE_ASSERT_RECOVER_RETURN(config);
0264     KoPatternSP pattern = config->pattern();
0265     QTransform transform = config->transform();
0266 
0267     KisFillPainter gc(dst);
0268     gc.setPattern(pattern);
0269     gc.setProgress(progressUpdater);
0270     gc.setChannelFlags(config->channelFlags());
0271     gc.setOpacity(OPACITY_OPAQUE_U8);
0272     gc.setSelection(dstInfo.selection());
0273     gc.setWidth(size.width());
0274     gc.setHeight(size.height());
0275     gc.setFillStyle(KisFillPainter::FillStylePattern);
0276     /**
0277      * HACK ALERT: using "no-compose" version of `fillRect` discards all the opacity,
0278      * selection, and channel flags options. Though it doesn't seem that we have a any
0279      * GUI in Krita that actually passes a selection to the generator itself. Fill
0280      * layers apply their settings on a later stage of the compositing pipeline.
0281      */
0282     gc.fillRectNoCompose(QRect(dstInfo.topLeft(), size), pattern, transform);
0283     gc.end();
0284 
0285 }
0286 
0287 #include "patterngenerator.moc"