File indexing completed on 2024-12-22 04:15:32

0001 /*
0002  * This file is part of the KDE project
0003  *
0004  * SPDX-FileCopyrightText: 2020 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
0005  *
0006  *  SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "multigridpatterngenerator.h"
0010 
0011 #include <QPoint>
0012 #include <QPolygonF>
0013 #include <QVector>
0014 #include <QMap>
0015 #include <QtMath>
0016 #include <QDomDocument>
0017 
0018 #include <kis_debug.h>
0019 
0020 #include <kpluginfactory.h>
0021 #include <klocalizedstring.h>
0022 
0023 #include <kis_fill_painter.h>
0024 #include <kis_image.h>
0025 #include <kis_paint_device.h>
0026 #include <kis_layer.h>
0027 #include <generator/kis_generator_registry.h>
0028 #include <kis_global.h>
0029 #include <kis_selection.h>
0030 #include <kis_types.h>
0031 #include <filter/kis_filter_configuration.h>
0032 #include <kis_processing_information.h>
0033 #include <kis_progress_update_helper.h>
0034 #include <KoStopGradient.h>
0035 
0036 #include "kis_wdg_multigrid_pattern.h"
0037 #include "ui_wdgmultigridpatternoptions.h"
0038 
0039 
0040 K_PLUGIN_FACTORY_WITH_JSON(KritaMultigridPatternGeneratorFactory, "kritamultigridpatterngenerator.json", registerPlugin<KritaMultigridPatternGenerator>();)
0041 
0042 KritaMultigridPatternGenerator::KritaMultigridPatternGenerator(QObject *parent, const QVariantList &)
0043         : QObject(parent)
0044 {
0045     KisGeneratorRegistry::instance()->add(new KisMultigridPatternGenerator());
0046 }
0047 
0048 KritaMultigridPatternGenerator::~KritaMultigridPatternGenerator()
0049 {
0050 }
0051 
0052 KisMultigridPatternGenerator::KisMultigridPatternGenerator() : KisGenerator(id(), KoID("basic"), i18n("&Multigrid Pattern..."))
0053 {
0054     setColorSpaceIndependence(FULLY_INDEPENDENT);
0055     setSupportsPainting(true);
0056 }
0057 
0058 KisFilterConfigurationSP KisMultigridPatternGenerator::defaultConfiguration(KisResourcesInterfaceSP resourcesInterface) const
0059 {
0060     KisFilterConfigurationSP config = factoryConfiguration(resourcesInterface);
0061 
0062     QLinearGradient gradient;
0063     gradient.setColorAt(0, Qt::green);
0064     gradient.setColorAt(1.0, Qt::blue);
0065     KoStopGradientSP grad = KoStopGradient::fromQGradient(&gradient);
0066     if (grad) {
0067         QDomDocument doc;
0068         QDomElement elt = doc.createElement("gradient");
0069         grad->toXML(doc, elt);
0070         doc.appendChild(elt);
0071         config->setProperty("gradientXML", doc.toString());
0072     }
0073 
0074     QVariant v;
0075     KoColor c;
0076     v.setValue(c);
0077     config->setProperty("lineColor", v);
0078     config->setProperty("divisions", 5);
0079     config->setProperty("lineWidth", 1);
0080     config->setProperty("dimensions", 5);
0081     config->setProperty("offset", .2);
0082 
0083     config->setProperty("colorRatio", 1.0);
0084     config->setProperty("colorIndex", 0.0);
0085     config->setProperty("colorIntersect", 0.0);
0086 
0087     config->setProperty("connectorColor", v);
0088     config->setProperty("connectorType", Connector::None);
0089     config->setProperty("connectorWidth", 1);
0090     return config;
0091 }
0092 
0093 KisConfigWidget * KisMultigridPatternGenerator::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev, bool) const
0094 {
0095     Q_UNUSED(dev);
0096     return new KisWdgMultigridPattern(parent);
0097 }
0098 
0099 void KisMultigridPatternGenerator::generate(KisProcessingInformation dstInfo,
0100                                  const QSize& size,
0101                                  const KisFilterConfigurationSP config,
0102                                  KoUpdater* progressUpdater) const
0103 {
0104     KisPaintDeviceSP dst = dstInfo.paintDevice();
0105 
0106     Q_ASSERT(!dst.isNull());
0107     Q_ASSERT(config);
0108 
0109     if (config) {
0110 
0111         QLinearGradient gradient;
0112         gradient.setColorAt(0, Qt::green);
0113         gradient.setColorAt(1.0, Qt::blue);
0114         KoStopGradientSP grad = KoStopGradient::fromQGradient(&gradient);
0115         if (config->hasProperty("gradientXML")) {
0116             QDomDocument doc;
0117             doc.setContent(config->getString("gradientXML", ""));
0118             KoStopGradient gradient = KoStopGradient::fromXML(doc.firstChildElement());
0119             if (gradient.stops().size() > 0) {
0120                 grad->setStops(gradient.stops());
0121             }
0122         }
0123 
0124         int divisions = config->getInt("divisions", 1);
0125         int dimensions = config->getInt("dimensions", 5);
0126         qreal offset = config->getFloat("offset", .2);
0127         QRectF bounds(QPoint(), size);
0128 
0129         KoColor lineColor = config->getColor("lineColor");
0130         lineColor.setOpacity(1.0);
0131         int lineWidth = config->getInt("lineWidth", 1);
0132 
0133         qreal colorRatio = config->getFloat("colorRatio", 1.0);
0134         qreal colorIndex = config->getFloat("colorIndex", 0.0);
0135         qreal colorIntersect = config->getFloat("colorIntersect", 0.0);
0136 
0137         qreal diameter = QLineF(bounds.topLeft(), bounds.bottomRight()).length();
0138         qreal scale = diameter/2/divisions;
0139 
0140         QList<KisMultiGridRhomb> rhombs = generateRhombs(dimensions, divisions, offset);
0141 
0142         KisProgressUpdateHelper progress(progressUpdater, 100, rhombs.size());
0143 
0144 
0145         KisPainter gc(dst);
0146         gc.setChannelFlags(config->channelFlags());
0147         gc.setOpacity(255);
0148         gc.setFillStyle(KisPainter::FillStyleBackgroundColor);
0149         gc.setStrokeStyle(KisPainter::StrokeStyleBrush);
0150         gc.setSelection(dstInfo.selection());
0151 
0152         gc.fill(bounds.left(), bounds.top(), bounds.right(), bounds.bottom(), lineColor);
0153 
0154         QTransform tf;
0155         tf.translate(bounds.center().x(), bounds.center().y());
0156         tf.scale(scale, scale);
0157         if (dimensions%2>0) {
0158             tf.rotateRadians((M_PI/dimensions)*0.5);
0159         }
0160         KoColor c = grad->stops()[0].color;
0161         for (int i= 0; i < rhombs.size(); i++){
0162             KisMultiGridRhomb rhomb = rhombs.at(i);
0163             QPolygonF shape = tf.map(rhomb.shape);
0164 
0165             QPointF center = shape.at(0)+shape.at(1)+shape.at(2)+shape.at(3);
0166             center.setX(center.x()/4.0);
0167             center.setY(center.y()/4.0);
0168 
0169             QTransform lineWidthTransform;
0170 
0171             qreal scaleForLineWidth = qMax(1-(qreal(lineWidth)/scale), 0.0);
0172             lineWidthTransform.scale(scaleForLineWidth, scaleForLineWidth);
0173             QPointF scaledCenter = lineWidthTransform.map(center);
0174             lineWidthTransform.reset();
0175 
0176             lineWidthTransform.translate(center.x()-scaledCenter.x(), center.y()-scaledCenter.y());
0177             lineWidthTransform.scale(scaleForLineWidth, scaleForLineWidth);
0178 
0179             shape = lineWidthTransform.map(shape);
0180 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
0181             if (shape.intersects(bounds) && shape.boundingRect().width()>0) {
0182 #else
0183             if (!shape.intersected(bounds).isEmpty() && shape.boundingRect().width()>0) {
0184 #endif
0185                 QPainterPath p;
0186                 p.addPolygon(lineWidthTransform.map(shape));
0187 
0188                 qreal gradientPos = 1;
0189 
0190                 qreal w1 = QLineF (shape.at(0), shape.at(2)).length();
0191                 qreal w2 = QLineF (shape.at(1), shape.at(3)).length();
0192                 qreal shapeRatio = qMin(w1, w2)/qMax(w1, w2);
0193 
0194                 qreal intersectRatio = qreal(rhomb.line1)/qreal(dimensions);
0195                 intersectRatio += qreal(rhomb.line2)/qreal(dimensions);
0196                 intersectRatio *= 0.5;
0197 
0198                 qreal indexRatio = 1-abs(qreal(rhomb.parallel1)/qreal(divisions/2.0));
0199                 indexRatio *= 1-abs(qreal(rhomb.parallel2)/qreal(divisions/2.0));
0200 
0201                 if (colorRatio>=0) {
0202                     gradientPos *= 1-(shapeRatio*colorRatio);
0203                 } else {
0204                     gradientPos *= 1-((1-shapeRatio)*abs(colorRatio));
0205                 }
0206                 if (colorIntersect>=0) {
0207                     gradientPos *= 1-(intersectRatio*colorIntersect);
0208                 } else {
0209                     gradientPos *= 1-((1-intersectRatio)*abs(colorIntersect));
0210                 }
0211                 if (colorIndex>=0) {
0212                     gradientPos *= 1-(indexRatio*colorIndex);
0213                 } else {
0214                     gradientPos *= 1-((1-indexRatio)*abs(colorIndex));
0215                 }
0216 
0217                 grad->colorAt(c, gradientPos);
0218                 gc.setBackgroundColor(c);
0219 
0220                 gc.fillPainterPath(p, p.boundingRect().adjusted(-2, -2, 2, 2).toRect());
0221 
0222                 int connectorType = config->getInt("connectorType", Connector::None);
0223 
0224                 if (connectorType != Connector::None) {
0225                     gc.setBackgroundColor(config->getColor("connectorColor"));
0226                     qreal connectorWidth = qreal(config->getInt("connectorWidth", 1))*.5;
0227                     QPainterPath pConnect;
0228                     qreal lower = connectorWidth/scale;
0229 
0230                     if (connectorType == Connector::Cross) {
0231                         QPointF cl = QLineF(shape.at(0), shape.at(1)).pointAt(0.5-lower);
0232                         pConnect.moveTo(cl);
0233                         cl = QLineF(shape.at(0), shape.at(1)).pointAt(0.5+lower);
0234                         pConnect.lineTo(cl);
0235                         cl = QLineF(shape.at(2), shape.at(3)).pointAt(0.5-lower);
0236                         pConnect.lineTo(cl);
0237                         cl = QLineF(shape.at(2), shape.at(3)).pointAt(0.5+lower);
0238                         pConnect.lineTo(cl);
0239                         pConnect.closeSubpath();
0240 
0241                         cl = QLineF(shape.at(1), shape.at(2)).pointAt(0.5-lower);
0242                         pConnect.moveTo(cl);
0243                         cl = QLineF(shape.at(1), shape.at(2)).pointAt(0.5+lower);
0244                         pConnect.lineTo(cl);
0245                         cl = QLineF(shape.at(3), shape.at(0)).pointAt(0.5-lower);
0246                         pConnect.lineTo(cl);
0247                         cl = QLineF(shape.at(3), shape.at(0)).pointAt(0.5+lower);
0248                         pConnect.lineTo(cl);
0249                         pConnect.closeSubpath();
0250 
0251                     } else if (connectorType == Connector::CornerDot) {
0252                         QPointF cW(connectorWidth, connectorWidth);
0253                         
0254                         QRectF dot (shape.at(0)-cW, shape.at(0)+cW);
0255                         pConnect.addEllipse(dot);
0256                         dot = QRectF(shape.at(1)-cW, shape.at(1)+cW);
0257                         pConnect.addEllipse(dot);
0258                         dot = QRectF(shape.at(2)-cW, shape.at(2)+cW);
0259                         pConnect.addEllipse(dot);
0260                         dot = QRectF(shape.at(3)-cW, shape.at(3)+cW);
0261                         pConnect.addEllipse(dot);
0262                         pConnect = pConnect.intersected(p);
0263                         
0264                     } else if (connectorType == Connector::CenterDot) {
0265                         
0266                         QRectF dot (center-QPointF(connectorWidth, connectorWidth), center+QPointF(connectorWidth, connectorWidth));
0267                         pConnect.addEllipse(dot);
0268                         
0269                     } else {
0270                         for (int i=1; i<shape.size(); i++) {
0271                             QPainterPath pAngle;
0272                             QPointF curPoint = shape.at(i);
0273                             QLineF l1(curPoint, shape.at(i-1));
0274                             QPointF np;
0275                             if (i==4) {
0276                                 np = shape.at(1);
0277                             } else {
0278                                 np = shape.at(i+1);
0279                             }
0280                             QLineF l2(curPoint, np);
0281                             qreal angleDiff = abs(fmod(abs(l1.angle()-l2.angle())+180, 360)-180);
0282 
0283                             if (round(angleDiff) == 90) {
0284                                 continue;
0285                             }
0286                             if (angleDiff > 90 && connectorType == Connector::Acute) {
0287                                 continue;
0288                             }
0289                             if (angleDiff < 90 && connectorType == Connector::Obtuse) {
0290                                 continue;
0291                             }
0292 
0293                             qreal length = (l1.length()*0.5)-connectorWidth;
0294                             QRectF sweep(curPoint-QPointF(length, length), curPoint+QPointF(length, length));
0295                             length = (l1.length()*0.5)+connectorWidth;
0296                             QRectF sweep2(curPoint-QPointF(length, length), curPoint+QPointF(length, length));
0297 
0298                             pAngle.moveTo(shape.at(i));
0299                             pAngle.addEllipse(sweep2);
0300                             pAngle.addEllipse(sweep);
0301                             pAngle = pAngle.intersected(p);
0302                             pAngle.closeSubpath();
0303                             pConnect.addPath(pAngle);
0304                         }
0305                         pConnect.closeSubpath();
0306 
0307                     }
0308                     pConnect.setFillRule(Qt::WindingFill);
0309                     gc.fillPainterPath(pConnect);
0310 
0311                 }
0312 
0313                 progress.step();
0314             }
0315         }
0316 
0317         gc.end();
0318     }
0319 }
0320 
0321 QList<KisMultiGridRhomb> KisMultigridPatternGenerator::generateRhombs(int lines, int divisions, qreal offset) const
0322 {
0323     QList<KisMultiGridRhomb> rhombs;
0324     QList<QLineF> parallelLines;
0325     QList<qreal> angles;
0326 
0327     int halfLines = divisions;
0328     int totalLines = (halfLines*2) +1;
0329 
0330     //setup our imaginary lines...
0331     int dimensions = lines;
0332     for (int i = 0; i< dimensions; i++) {
0333         qreal angle = 2*(M_PI/lines)*i;
0334         angles.append(angle);
0335     }
0336 
0337 
0338     for (int i = 0; i <angles.size(); i++ ) {
0339         qreal angle1 = angles.at(i);
0340         QPointF p1(totalLines*cos(angle1), -totalLines*sin(angle1));
0341         QPointF p2 = -p1;
0342 
0343 
0344         for (int parallel1 = 0; parallel1 < totalLines; parallel1++) {
0345             int index1 = halfLines-parallel1;
0346 
0347             QPointF offset1((index1+offset)*sin(angle1), (index1+offset)*cos(angle1));
0348             QLineF l1(p1, p2);
0349             l1.translate(offset1);
0350 
0351             for (int k = i+1; k <angles.size(); k++ ) {
0352                 qreal angle2 = angles.at(k);
0353                 QPointF p3(totalLines*cos(angle2), -totalLines*sin(angle2));
0354                 QPointF p4 = -p3;
0355 
0356 
0357                 for (int parallel2 = 0; parallel2 < totalLines; parallel2++) {
0358                     int index2 = halfLines-parallel2;
0359 
0360                     QPointF offset2((index2+offset)*sin(angle2), (index2+offset)*cos(angle2));
0361                     QLineF l2(p3, p4);
0362                     l2.translate(offset2);
0363 
0364 
0365                     QPointF intersect;
0366 #if (QT_VERSION < QT_VERSION_CHECK(5, 14, 0))
0367                     int intersection = l1.intersect(l2, &intersect);
0368 #else
0369                     int intersection = l1.intersects(l2, &intersect);
0370 #endif
0371                     if (intersection==QLineF::BoundedIntersection) {
0372 
0373                         QList<int> indices = getIndicesFromPoint(intersect, angles, offset );
0374 
0375                         QPolygonF shape;
0376                         indices[i] = index1+1;
0377                         indices[k] = index2+1;
0378                         shape << getVertice(indices, angles);
0379                         indices[i] = index1;
0380                         indices[k] = index2+1;
0381                         shape << getVertice(indices, angles);
0382                         indices[i] = index1;
0383                         indices[k] = index2;
0384                         shape << getVertice(indices, angles);
0385                         indices[i] = index1+1;
0386                         indices[k] = index2;
0387                         shape << getVertice(indices, angles);
0388                         indices[i] = index1+1;
0389                         indices[k] = index2+1;
0390                         shape << getVertice(indices, angles);
0391 
0392                         KisMultiGridRhomb rhomb;
0393                         rhomb.shape = shape;
0394                         rhomb.parallel1 = index1;
0395                         rhomb.parallel2 = index2;
0396                         rhomb.line1 = i;
0397                         rhomb.line2 = k;
0398 
0399                         rhombs.append(rhomb);
0400                     }
0401 
0402                 }
0403             }
0404         }
0405     }
0406 
0407 
0408     return rhombs;
0409 }
0410 
0411 QList<int> KisMultigridPatternGenerator::getIndicesFromPoint(QPointF point, QList<qreal> angles, qreal offset) const
0412 {
0413     QList<int> indices;
0414 
0415     for (int a=0; a< angles.size(); a++) {
0416 
0417         QPointF p = point;
0418 
0419         qreal index = p.x() * sin(angles.at(a)) + (p.y()) * cos(angles.at(a));
0420         indices.append(floor(index-offset+1));
0421     }
0422     return indices;
0423 }
0424 
0425 QPointF KisMultigridPatternGenerator::getVertice(QList<int> indices, QList<qreal> angles) const
0426 {
0427     if (indices.isEmpty() || angles.isEmpty()) {
0428         qDebug() << "error";
0429         return QPointF();
0430     }
0431     qreal x = 0;
0432     qreal y = 0;
0433 
0434     for (int i=0; i<indices.size(); i++) {
0435         x += indices.at(i)*cos(angles.at(i));
0436         y += indices.at(i)*sin(angles.at(i));
0437     }
0438 
0439     return QPointF(x, y);
0440 }
0441 
0442 #include "multigridpatterngenerator.moc"