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"