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"