Krita Source Code Documentation
Loading...
Searching...
No Matches
patterngenerator.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE project
3 *
4 * SPDX-FileCopyrightText: 2008 Boudewijn Rempt <boud@valdyas.org>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include "patterngenerator.h"
10
11#include <QPoint>
12
13
14#include <kpluginfactory.h>
15#include <klocalizedstring.h>
16
17#include <KoColor.h>
18#include <KisResourceTypes.h>
19#include <resources/KoPattern.h>
20
21#include <kis_debug.h>
22#include <kis_fill_painter.h>
23#include <kis_image.h>
24#include <kis_paint_device.h>
25#include <kis_layer.h>
27#include <kis_global.h>
28#include <kis_selection.h>
29#include <kis_types.h>
32#include <kis_pattern_chooser.h>
35
36#include "kis_wdg_pattern.h"
37#include "ui_wdgpatternoptions.h"
38
39K_PLUGIN_FACTORY_WITH_JSON(KritaPatternGeneratorFactory, "kritapatterngenerator.json", registerPlugin<KritaPatternGenerator>();)
40
41KritaPatternGenerator::KritaPatternGenerator(QObject *parent, const QVariantList &)
42 : QObject(parent)
43{
45}
46
50
51/****************************************************************************/
52/* KoPatternGeneratorConfiguration */
53/****************************************************************************/
54
56{
57public:
62
67
68 virtual KisFilterConfigurationSP clone() const override {
69 return new PatternGeneratorConfiguration(*this);
70 }
71
73 {
74 const QString patternMD5 = getString("md5sum", "");
75 const QString patternName = getString("pattern", "Grid01.pat");
76 const QString patternFileName = getString("fileName", "");
78 KoResourceLoadResult res = source.bestMatchLoadResult(patternMD5, patternFileName, patternName);
79 return res;
80 }
81
83 return pattern(resourcesInterface()).resource<KoPattern>();
84 }
85
86 QTransform transform() const {
87 const bool constrainScale = getBool("transform_keep_scale_aspect", true);
88 const qreal scaleX = getDouble("transform_scale_x", 1.0);
89 // Ensure that the size y component is equal to the x component if keepSizeSquare is true
90 const qreal scaleY = constrainScale ? scaleX : getDouble("transform_scale_y", 1.0);
91 const qreal positionX = getInt("transform_offset_x", 0);
92 const qreal positionY = getInt("transform_offset_y", 0);
93 const qreal shearX = getDouble("transform_shear_x", 0.0);
94 const qreal shearY = getDouble("transform_shear_y", 0.0);
95 const qreal rotationX = getDouble("transform_rotation_x", 0.0);
96 const qreal rotationY = getDouble("transform_rotation_y", 0.0);
97 const qreal rotationZ = getDouble("transform_rotation_z", 0.0);
98 const bool align = getBool("transform_align_to_pixel_grid", false);
99 const qint32 alignX = getInt("transform_align_to_pixel_grid_x", 1);
100 const qint32 alignY = getInt("transform_align_to_pixel_grid_y", 1);
101
102 QTransform transform;
103
104 if (align && qFuzzyIsNull(rotationX) && qFuzzyIsNull(rotationY) && pattern()) {
105 // STEP 1: compose the transformation
106 transform.shear(shearX, shearY);
107 transform.scale(scaleX, scaleY);
108 transform.rotate(rotationZ);
109 // STEP 2: transform the horizontal and vertical vectors of the
110 // "repetition rect" (which size is some multiple of the
111 // pattern size)
112 const QSizeF repetitionRectSize(
113 static_cast<qreal>(alignX * pattern()->width()),
114 static_cast<qreal>(alignY * pattern()->height())
115 );
116 // u1 is the unaligned vector that goes from the origin to the top-right
117 // corner of the repetition rect. u2 is the unaligned vector that
118 // goes from the origin to the bottom-left corner of the repetition rect
119 const QPointF u1 = transform.map(QPointF(repetitionRectSize.width(), 0.0));
120 const QPointF u2 = transform.map(QPointF(0.0, repetitionRectSize.height()));
121 // STEP 3: align the transformed vectors to the pixel grid. v1 is
122 // the aligned version of u1 and v2 is the aligned version of u2
123 QPointF v1(qRound(u1.x()), qRound(u1.y()));
124 QPointF v2(qRound(u2.x()), qRound(u2.y()));
125 // If the following condition is met, that means that the pattern is
126 // transformed in such a way that the repetition rect corners are
127 // colinear so we move v1 or v2 to a neighbor position
128 if (qFuzzyCompare(v1.y() * v2.x(), v2.y() * v1.x()) &&
129 !qFuzzyIsNull(v1.x() * v2.x() + v1.y() * v2.y())) {
130 // Choose point to move based on distance from non aligned point to
131 // aligned point
132 const qreal dist1 = kisSquareDistance(u1, v1);
133 const qreal dist2 = kisSquareDistance(u2, v2);
134 const QPointF *p_u = dist1 > dist2 ? &u1 : &u2;
135 QPointF *p_v = dist1 > dist2 ? &v1 : &v2;
136 // Then we get the closest pixel aligned point to the current,
137 // colinear, point
138 QPair<int, qreal> dists[4]{
139 {1, kisSquareDistance(*p_u, *p_v + QPointF(0.0, -1.0))},
140 {2, kisSquareDistance(*p_u, *p_v + QPointF(1.0, 0.0))},
141 {3, kisSquareDistance(*p_u, *p_v + QPointF(0.0, 1.0))},
142 {4, kisSquareDistance(*p_u, *p_v + QPointF(-1.0, 0.0))}
143 };
144 std::sort(
145 std::begin(dists), std::end(dists),
146 [](const QPair<int, qreal> &a, const QPair<int, qreal> &b)
147 {
148 return a.second < b.second;
149 }
150 );
151 // Move the point
152 if (dists[0].first == 1) {
153 p_v->setY(p_v->y() - 1.0);
154 } else if (dists[0].first == 2) {
155 p_v->setX(p_v->x() + 1.0);
156 } else if (dists[0].first == 3) {
157 p_v->setY(p_v->y() + 1.0);
158 } else {
159 p_v->setX(p_v->x() - 1.0);
160 }
161 }
162 // STEP 4: get the transform that maps the aligned vectors to the
163 // untransformed rect (this is in fact the inverse transform)
164 QPolygonF quad;
165 quad.append(QPointF(0, 0));
166 quad.append(v1 / repetitionRectSize.width());
167 quad.append(v1 / repetitionRectSize.width() + v2 / repetitionRectSize.height());
168 quad.append(v2 / repetitionRectSize.height());
169 QTransform::quadToSquare(quad, transform);
170 // STEP 5: get the forward transform
171 transform = transform.inverted();
172 transform.translate(qRound(positionX), qRound(positionY));
173 } else {
174 transform.shear(shearX, shearY);
175 transform.scale(scaleX, scaleY);
176 transform.rotate(rotationX, Qt::XAxis);
177 transform.rotate(rotationY, Qt::YAxis);
178 transform.rotate(rotationZ, Qt::ZAxis);
179 transform.translate(positionX, positionY);
180 }
181
182 return transform;
183 }
184
186 {
187 return {pattern(globalResourcesInterface)};
188 }
189};
190
191
192
193/****************************************************************************/
194/* KoPatternGenerator */
195/****************************************************************************/
196
198 : KisGenerator(id(), KoID("basic"), i18n("&Pattern..."))
199{
202}
203
205{
206 return new PatternGeneratorConfiguration(id().id(), 1, resourcesInterface);
207}
208
210{
211 KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
212
213 auto source = resourcesInterface->source<KoPattern>(ResourceType::Patterns);
214
215 if (!source.fallbackResource()) {
216 return config;
217 }
218
219 config->setProperty("md5sum", QVariant::fromValue(source.fallbackResource()->md5Sum()));
220 config->setProperty("fileName", QVariant::fromValue(source.fallbackResource()->filename()));
221 config->setProperty("pattern", QVariant::fromValue(source.fallbackResource()->name()));
222
223 config->setProperty("transform_shear_x", QVariant::fromValue(0.0));
224 config->setProperty("transform_shear_y", QVariant::fromValue(0.0));
225
226 config->setProperty("transform_scale_x", QVariant::fromValue(1.0));
227 config->setProperty("transform_scale_y", QVariant::fromValue(1.0));
228
229 config->setProperty("transform_rotation_x", QVariant::fromValue(0.0));
230 config->setProperty("transform_rotation_y", QVariant::fromValue(0.0));
231 config->setProperty("transform_rotation_z", QVariant::fromValue(0.0));
232
233 config->setProperty("transform_offset_x", QVariant::fromValue(0));
234 config->setProperty("transform_offset_y", QVariant::fromValue(0));
235
236 config->setProperty("transform_keep_scale_aspect", QVariant::fromValue(true));
237
238 config->setProperty("transform_align_to_pixel_grid", QVariant::fromValue(false));
239 config->setProperty("transform_align_to_pixel_grid_x", QVariant::fromValue(1));
240 config->setProperty("transform_align_to_pixel_grid_y", QVariant::fromValue(1));
241
242 return config;
243}
244
246{
247 Q_UNUSED(dev);
248 return new KisWdgPattern(parent);
249}
250
252 const QSize& size,
253 const KisFilterConfigurationSP _config,
254 KoUpdater* progressUpdater) const
255{
256 KisPaintDeviceSP dst = dstInfo.paintDevice();
257
258 Q_ASSERT(!dst.isNull());
259
260 const PatternGeneratorConfiguration *config =
261 dynamic_cast<const PatternGeneratorConfiguration*>(_config.data());
262
264 KoPatternSP pattern = config->pattern();
265 QTransform transform = config->transform();
266
267 KisFillPainter gc(dst);
268 gc.setPattern(pattern);
269 gc.setProgress(progressUpdater);
270 gc.setChannelFlags(config->channelFlags());
271 gc.setOpacityToUnit();
272 gc.setSelection(dstInfo.selection());
273 gc.setWidth(size.width());
274 gc.setHeight(size.height());
282 gc.fillRectNoCompose(QRect(dstInfo.topLeft(), size), pattern, transform);
283 gc.end();
284
285}
286
287#include "patterngenerator.moc"
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
@ FULLY_INDEPENDENT
void setWidth(int w)
void setHeight(int h)
void fillRectNoCompose(const QRect &rc, const KoPatternSP pattern, const QTransform transform)
fillRect Fill a rectangle with a certain pattern. The pattern is repeated if it does not fit the enti...
static KisGeneratorRegistry * instance()
void add(KisGeneratorSP item)
void setSelection(KisSelectionSP selection)
void setFillStyle(FillStyle fillStyle)
Set the current style with which to fill.
void setOpacityToUnit()
void setPattern(const KoPatternSP pattern)
Set the current pattern.
void setProgress(KoUpdater *progressUpdater)
void setChannelFlags(QBitArray channelFlags)
bool isNull() const
Definition KoID.h:30
Write API docs here.
Definition KoPattern.h:21
KritaPatternGenerator(QObject *parent, const QVariantList &)
virtual KisFilterConfigurationSP clone() const override
PatternGeneratorConfiguration(const PatternGeneratorConfiguration &rhs)
QList< KoResourceLoadResult > linkedResources(KisResourcesInterfaceSP globalResourcesInterface) const override
PatternGeneratorConfiguration(const QString &name, qint32 version, KisResourcesInterfaceSP resourcesInterface)
KoResourceLoadResult pattern(KisResourcesInterfaceSP resourcesInterface) const
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
void generate(KisProcessingInformation dst, const QSize &size, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override
static bool qFuzzyCompare(half p1, half p2)
static bool qFuzzyIsNull(half h)
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
const QString Patterns
void setSupportsPainting(bool v)
void setColorSpaceIndependence(ColorSpaceIndependence v)
KisResourcesInterfaceSP resourcesInterface
QString getString(const QString &name, const QString &def=QString()) const
bool getBool(const QString &name, bool def=false) const
int getInt(const QString &name, int def=0) const
double getDouble(const QString &name, double def=0.0) const