Krita Source Code Documentation
Loading...
Searching...
No Matches
multigridpatterngenerator.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the KDE project
3 *
4 * SPDX-FileCopyrightText: 2020 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10
11#include <QPoint>
12#include <QPolygonF>
13#include <QMap>
14#include <QtMath>
15#include <QDomDocument>
16
17#include <kis_debug.h>
18
19#include <kpluginfactory.h>
20#include <klocalizedstring.h>
21
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>
33#include <KoStopGradient.h>
34
36#include "ui_wdgmultigridpatternoptions.h"
37
38
39K_PLUGIN_FACTORY_WITH_JSON(KritaMultigridPatternGeneratorFactory, "kritamultigridpatterngenerator.json", registerPlugin<KritaMultigridPatternGenerator>();)
40
42 : QObject(parent)
43{
45}
46
50
56
58{
59 KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
60
61 QLinearGradient gradient;
62 gradient.setColorAt(0, Qt::green);
63 gradient.setColorAt(1.0, Qt::blue);
65 if (grad) {
66 QDomDocument doc;
67 QDomElement elt = doc.createElement("gradient");
68 grad->toXML(doc, elt);
69 doc.appendChild(elt);
70 config->setProperty("gradientXML", doc.toString());
71 }
72
73 QVariant v;
74 KoColor c;
75 v.setValue(c);
76 config->setProperty("lineColor", v);
77 config->setProperty("divisions", 5);
78 config->setProperty("lineWidth", 1);
79 config->setProperty("dimensions", 5);
80 config->setProperty("offset", .2);
81
82 config->setProperty("colorRatio", 1.0);
83 config->setProperty("colorIndex", 0.0);
84 config->setProperty("colorIntersect", 0.0);
85
86 config->setProperty("connectorColor", v);
87 config->setProperty("connectorType", Connector::None);
88 config->setProperty("connectorWidth", 1);
89 return config;
90}
91
93{
94 Q_UNUSED(dev);
95 return new KisWdgMultigridPattern(parent);
96}
97
99 const QSize& size,
100 const KisFilterConfigurationSP config,
101 KoUpdater* progressUpdater) const
102{
103 KisPaintDeviceSP dst = dstInfo.paintDevice();
104
105 Q_ASSERT(!dst.isNull());
106 Q_ASSERT(config);
107
108 if (config) {
109
110 QLinearGradient gradient;
111 gradient.setColorAt(0, Qt::green);
112 gradient.setColorAt(1.0, Qt::blue);
114 if (config->hasProperty("gradientXML")) {
115 QDomDocument doc;
116 doc.setContent(config->getString("gradientXML", ""));
117 KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
118 if (gradient.stops().size() > 0) {
119 grad->setStops(gradient.stops());
120 }
121 }
122
123 int divisions = config->getInt("divisions", 1);
124 int dimensions = config->getInt("dimensions", 5);
125 qreal offset = config->getFloat("offset", .2);
126 QRectF bounds(QPoint(), size);
127
128 KoColor lineColor = config->getColor("lineColor");
129 lineColor.setOpacity(1.0);
130 int lineWidth = config->getInt("lineWidth", 1);
131
132 qreal colorRatio = config->getFloat("colorRatio", 1.0);
133 qreal colorIndex = config->getFloat("colorIndex", 0.0);
134 qreal colorIntersect = config->getFloat("colorIntersect", 0.0);
135
136 qreal diameter = QLineF(bounds.topLeft(), bounds.bottomRight()).length();
137 qreal scale = diameter/2/divisions;
138
139 QList<KisMultiGridRhomb> rhombs = generateRhombs(dimensions, divisions, offset);
140
141 KisProgressUpdateHelper progress(progressUpdater, 100, rhombs.size());
142
143
144 KisPainter gc(dst);
145 gc.setChannelFlags(config->channelFlags());
146 gc.setOpacityToUnit();
149 gc.setSelection(dstInfo.selection());
150
151 gc.fill(bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), lineColor);
152
153 QTransform tf;
154 tf.translate(bounds.center().x(), bounds.center().y());
155 tf.scale(scale, scale);
156 if (dimensions%2>0) {
157 tf.rotateRadians((M_PI/dimensions)*0.5);
158 }
159 KoColor c = grad->stops()[0].color;
160 for (int i= 0; i < rhombs.size(); i++){
161 KisMultiGridRhomb rhomb = rhombs.at(i);
162 QPolygonF shape = tf.map(rhomb.shape);
163
164 QPointF center = shape.at(0)+shape.at(1)+shape.at(2)+shape.at(3);
165 center.setX(center.x()/4.0);
166 center.setY(center.y()/4.0);
167
168 QTransform lineWidthTransform;
169
170 qreal scaleForLineWidth = qMax(1-(qreal(lineWidth)/scale), 0.0);
171 lineWidthTransform.scale(scaleForLineWidth, scaleForLineWidth);
172 QPointF scaledCenter = lineWidthTransform.map(center);
173 lineWidthTransform.reset();
174
175 lineWidthTransform.translate(center.x()-scaledCenter.x(), center.y()-scaledCenter.y());
176 lineWidthTransform.scale(scaleForLineWidth, scaleForLineWidth);
177
178 shape = lineWidthTransform.map(shape);
179 if (shape.intersects(bounds) && shape.boundingRect().width()>0) {
180 QPainterPath p;
181 p.addPolygon(lineWidthTransform.map(shape));
182
183 qreal gradientPos = 1;
184
185 qreal w1 = QLineF (shape.at(0), shape.at(2)).length();
186 qreal w2 = QLineF (shape.at(1), shape.at(3)).length();
187 qreal shapeRatio = qMin(w1, w2)/qMax(w1, w2);
188
189 qreal intersectRatio = qreal(rhomb.line1)/qreal(dimensions);
190 intersectRatio += qreal(rhomb.line2)/qreal(dimensions);
191 intersectRatio *= 0.5;
192
193 qreal indexRatio = 1-abs(qreal(rhomb.parallel1)/qreal(divisions/2.0));
194 indexRatio *= 1-abs(qreal(rhomb.parallel2)/qreal(divisions/2.0));
195
196 if (colorRatio>=0) {
197 gradientPos *= 1-(shapeRatio*colorRatio);
198 } else {
199 gradientPos *= 1-((1-shapeRatio)*abs(colorRatio));
200 }
201 if (colorIntersect>=0) {
202 gradientPos *= 1-(intersectRatio*colorIntersect);
203 } else {
204 gradientPos *= 1-((1-intersectRatio)*abs(colorIntersect));
205 }
206 if (colorIndex>=0) {
207 gradientPos *= 1-(indexRatio*colorIndex);
208 } else {
209 gradientPos *= 1-((1-indexRatio)*abs(colorIndex));
210 }
211
212 grad->colorAt(c, gradientPos);
213 gc.setBackgroundColor(c);
214
215 gc.fillPainterPath(p, p.boundingRect().adjusted(-2, -2, 2, 2).toRect());
216
217 int connectorType = config->getInt("connectorType", Connector::None);
218
219 if (connectorType != Connector::None) {
220 gc.setBackgroundColor(config->getColor("connectorColor"));
221 qreal connectorWidth = qreal(config->getInt("connectorWidth", 1))*.5;
222 QPainterPath pConnect;
223 qreal lower = connectorWidth/scale;
224
225 if (connectorType == Connector::Cross) {
226 QPointF cl = QLineF(shape.at(0), shape.at(1)).pointAt(0.5-lower);
227 pConnect.moveTo(cl);
228 cl = QLineF(shape.at(0), shape.at(1)).pointAt(0.5+lower);
229 pConnect.lineTo(cl);
230 cl = QLineF(shape.at(2), shape.at(3)).pointAt(0.5-lower);
231 pConnect.lineTo(cl);
232 cl = QLineF(shape.at(2), shape.at(3)).pointAt(0.5+lower);
233 pConnect.lineTo(cl);
234 pConnect.closeSubpath();
235
236 cl = QLineF(shape.at(1), shape.at(2)).pointAt(0.5-lower);
237 pConnect.moveTo(cl);
238 cl = QLineF(shape.at(1), shape.at(2)).pointAt(0.5+lower);
239 pConnect.lineTo(cl);
240 cl = QLineF(shape.at(3), shape.at(0)).pointAt(0.5-lower);
241 pConnect.lineTo(cl);
242 cl = QLineF(shape.at(3), shape.at(0)).pointAt(0.5+lower);
243 pConnect.lineTo(cl);
244 pConnect.closeSubpath();
245
246 } else if (connectorType == Connector::CornerDot) {
247 QPointF cW(connectorWidth, connectorWidth);
248
249 QRectF dot (shape.at(0)-cW, shape.at(0)+cW);
250 pConnect.addEllipse(dot);
251 dot = QRectF(shape.at(1)-cW, shape.at(1)+cW);
252 pConnect.addEllipse(dot);
253 dot = QRectF(shape.at(2)-cW, shape.at(2)+cW);
254 pConnect.addEllipse(dot);
255 dot = QRectF(shape.at(3)-cW, shape.at(3)+cW);
256 pConnect.addEllipse(dot);
257 pConnect = pConnect.intersected(p);
258
259 } else if (connectorType == Connector::CenterDot) {
260
261 QRectF dot (center-QPointF(connectorWidth, connectorWidth), center+QPointF(connectorWidth, connectorWidth));
262 pConnect.addEllipse(dot);
263
264 } else {
265 for (int i=1; i<shape.size(); i++) {
266 QPainterPath pAngle;
267 QPointF curPoint = shape.at(i);
268 QLineF l1(curPoint, shape.at(i-1));
269 QPointF np;
270 if (i==4) {
271 np = shape.at(1);
272 } else {
273 np = shape.at(i+1);
274 }
275 QLineF l2(curPoint, np);
276 qreal angleDiff = abs(fmod(abs(l1.angle()-l2.angle())+180, 360)-180);
277
278 if (round(angleDiff) == 90) {
279 continue;
280 }
281 if (angleDiff > 90 && connectorType == Connector::Acute) {
282 continue;
283 }
284 if (angleDiff < 90 && connectorType == Connector::Obtuse) {
285 continue;
286 }
287
288 qreal length = (l1.length()*0.5)-connectorWidth;
289 QRectF sweep(curPoint-QPointF(length, length), curPoint+QPointF(length, length));
290 length = (l1.length()*0.5)+connectorWidth;
291 QRectF sweep2(curPoint-QPointF(length, length), curPoint+QPointF(length, length));
292
293 pAngle.moveTo(shape.at(i));
294 pAngle.addEllipse(sweep2);
295 pAngle.addEllipse(sweep);
296 pAngle = pAngle.intersected(p);
297 pAngle.closeSubpath();
298 pConnect.addPath(pAngle);
299 }
300 pConnect.closeSubpath();
301
302 }
303 pConnect.setFillRule(Qt::WindingFill);
304 gc.fillPainterPath(pConnect);
305
306 }
307
308 progress.step();
309 }
310 }
311
312 gc.end();
313 }
314}
315
317{
319 QList<QLineF> parallelLines;
320 QList<qreal> angles;
321
322 int halfLines = divisions;
323 int totalLines = (halfLines*2) +1;
324
325 //setup our imaginary lines...
326 int dimensions = lines;
327 for (int i = 0; i< dimensions; i++) {
328 qreal angle = 2*(M_PI/lines)*i;
329 angles.append(angle);
330 }
331
332
333 for (int i = 0; i <angles.size(); i++ ) {
334 qreal angle1 = angles.at(i);
335 QPointF p1(totalLines*cos(angle1), -totalLines*sin(angle1));
336 QPointF p2 = -p1;
337
338
339 for (int parallel1 = 0; parallel1 < totalLines; parallel1++) {
340 int index1 = halfLines-parallel1;
341
342 QPointF offset1((index1+offset)*sin(angle1), (index1+offset)*cos(angle1));
343 QLineF l1(p1, p2);
344 l1.translate(offset1);
345
346 for (int k = i+1; k <angles.size(); k++ ) {
347 qreal angle2 = angles.at(k);
348 QPointF p3(totalLines*cos(angle2), -totalLines*sin(angle2));
349 QPointF p4 = -p3;
350
351
352 for (int parallel2 = 0; parallel2 < totalLines; parallel2++) {
353 int index2 = halfLines-parallel2;
354
355 QPointF offset2((index2+offset)*sin(angle2), (index2+offset)*cos(angle2));
356 QLineF l2(p3, p4);
357 l2.translate(offset2);
358
359
360 QPointF intersect;
361 if (l1.intersects(l2, &intersect) == QLineF::BoundedIntersection) {
362
363 QList<int> indices = getIndicesFromPoint(intersect, angles, offset );
364
365 QPolygonF shape;
366 indices[i] = index1+1;
367 indices[k] = index2+1;
368 shape << getVertice(indices, angles);
369 indices[i] = index1;
370 indices[k] = index2+1;
371 shape << getVertice(indices, angles);
372 indices[i] = index1;
373 indices[k] = index2;
374 shape << getVertice(indices, angles);
375 indices[i] = index1+1;
376 indices[k] = index2;
377 shape << getVertice(indices, angles);
378 indices[i] = index1+1;
379 indices[k] = index2+1;
380 shape << getVertice(indices, angles);
381
382 KisMultiGridRhomb rhomb;
383 rhomb.shape = shape;
384 rhomb.parallel1 = index1;
385 rhomb.parallel2 = index2;
386 rhomb.line1 = i;
387 rhomb.line2 = k;
388
389 rhombs.append(rhomb);
390 }
391
392 }
393 }
394 }
395 }
396
397
398 return rhombs;
399}
400
402{
403 QList<int> indices;
404
405 for (int a=0; a< angles.size(); a++) {
406
407 QPointF p = point;
408
409 qreal index = p.x() * sin(angles.at(a)) + (p.y()) * cos(angles.at(a));
410 indices.append(floor(index-offset+1));
411 }
412 return indices;
413}
414
416{
417 if (indices.isEmpty() || angles.isEmpty()) {
418 qDebug() << "error";
419 return QPointF();
420 }
421 qreal x = 0;
422 qreal y = 0;
423
424 for (int i=0; i<indices.size(); i++) {
425 x += indices.at(i)*cos(angles.at(i));
426 y += indices.at(i)*sin(angles.at(i));
427 }
428
429 return QPointF(x, y);
430}
431
432#include "multigridpatterngenerator.moc"
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
const Params2D p
qreal v
QPointF p2
QPointF p3
QPointF p1
@ FULLY_INDEPENDENT
static KisGeneratorRegistry * instance()
void add(KisGeneratorSP item)
void generate(KisProcessingInformation dst, const QSize &size, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const override
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
QPointF getVertice(QList< int > indices, QList< qreal > angles) const
QList< KisMultiGridRhomb > generateRhombs(int lines, int divisions, qreal offset) const
QList< int > getIndicesFromPoint(QPointF point, QList< qreal > angles, qreal offset) const
KisFilterConfigurationSP defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
@ FillStyleBackgroundColor
void setStrokeStyle(StrokeStyle strokeStyle)
Set the current brush stroke style.
void fill(qint32 x, qint32 y, qint32 width, qint32 height, const KoColor &color)
void setSelection(KisSelectionSP selection)
void setBackgroundColor(const KoColor &color)
void setFillStyle(FillStyle fillStyle)
Set the current style with which to fill.
void setOpacityToUnit()
void fillPainterPath(const QPainterPath &path)
void setChannelFlags(QBitArray channelFlags)
bool isNull() const
void setOpacity(quint8 alpha)
Definition KoColor.cpp:333
Definition KoID.h:30
static QSharedPointer< KoStopGradient > fromQGradient(const QGradient *gradient)
Creates KoStopGradient from a QGradient.
static KoStopGradient fromXML(const QDomElement &elt)
fromXML convert a gradient from xml.
QList< KoGradientStop > stops() const
KritaMultigridPatternGenerator(QObject *parent, const QVariantList &)
K_PLUGIN_FACTORY_WITH_JSON(KritaASCCDLFactory, "kritaasccdl.json", registerPlugin< KritaASCCDL >();) KritaASCCDL
#define bounds(x, a, b)
#define M_PI
Definition kis_global.h:111
virtual KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const
void setSupportsPainting(bool v)
void setColorSpaceIndependence(ColorSpaceIndependence v)