File indexing completed on 2024-05-12 15:58:49

0001 /*
0002  *  SPDX-FileCopyrightText: 2020 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisBezierGradientMesh.h"
0008 
0009 #include "kis_grid_interpolation_tools.h"
0010 #include "kis_debug.h"
0011 #include "kis_dom_utils.h"
0012 
0013 namespace KisBezierGradientMeshDetail {
0014 
0015 struct QImageGradientOp
0016 {
0017     QImageGradientOp(const std::array<QColor, 4> &colors, QImage &dstImage,
0018                     const QPointF &dstImageOffset)
0019         : m_colors(colors), m_dstImage(dstImage),
0020           m_dstImageOffset(dstImageOffset),
0021           m_dstImageRect(m_dstImage.rect())
0022     {
0023     }
0024 
0025     void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon) {
0026         this->operator() (srcPolygon, dstPolygon, dstPolygon);
0027     }
0028 
0029     void operator() (const QPolygonF &srcPolygon, const QPolygonF &dstPolygon, const QPolygonF &clipDstPolygon) {
0030         QRect boundRect = clipDstPolygon.boundingRect().toAlignedRect();
0031         KisFourPointInterpolatorBackward interp(srcPolygon, dstPolygon);
0032 
0033         for (int y = boundRect.top(); y <= boundRect.bottom(); y++) {
0034             interp.setY(y);
0035             for (int x = boundRect.left(); x <= boundRect.right(); x++) {
0036 
0037                 QPointF srcPoint(x, y);
0038                 if (clipDstPolygon.containsPoint(srcPoint, Qt::OddEvenFill)) {
0039 
0040                     interp.setX(srcPoint.x());
0041                     QPointF dstPoint = interp.getValue();
0042 
0043                     // about srcPoint/dstPoint hell please see a
0044                     // comment in PaintDevicePolygonOp::operator() ()
0045 
0046                     srcPoint -= m_dstImageOffset;
0047 
0048                     QPoint srcPointI = srcPoint.toPoint();
0049 
0050                     if (!m_dstImageRect.contains(srcPointI)) continue;
0051 
0052                     // TODO: move vertical calculation into the upper loop
0053                     const QColor c1 = lerp(m_colors[0], m_colors[1], qBound(0.0, dstPoint.x(), 1.0));
0054                     const QColor c2 = lerp(m_colors[2], m_colors[3], qBound(0.0, dstPoint.x(), 1.0));
0055 
0056                     m_dstImage.setPixelColor(srcPointI, lerp(c1, c2, qBound(0.0, dstPoint.y(), 1.0)));
0057                 }
0058             }
0059         }
0060     }
0061 
0062     const std::array<QColor, 4> &m_colors;
0063     QImage &m_dstImage;
0064     QPointF m_dstImageOffset;
0065     QRect m_dstImageRect;
0066 };
0067 
0068 void saveValue(QDomElement *parent, const QString &tag, const GradientMeshNode &node)
0069 {
0070     QDomDocument doc = parent->ownerDocument();
0071     QDomElement e = doc.createElement(tag);
0072     parent->appendChild(e);
0073 
0074     e.setAttribute("type", "gradient-mesh-node");
0075     KisDomUtils::saveValue(&e, "color", node.color);
0076     KisDomUtils::saveValue(&e, "node", node.node);
0077     KisDomUtils::saveValue(&e, "left-control", node.leftControl);
0078     KisDomUtils::saveValue(&e, "right-control", node.rightControl);
0079     KisDomUtils::saveValue(&e, "top-control", node.topControl);
0080     KisDomUtils::saveValue(&e, "bottom-control", node.bottomControl);
0081 
0082 }
0083 
0084 bool loadValue(const QDomElement &parent, GradientMeshNode *node)
0085 {
0086     if (!KisDomUtils::Private::checkType(parent, "gradient-mesh-node")) return false;
0087 
0088     KisDomUtils::loadValue(parent, "node", &node->node);
0089     KisDomUtils::loadValue(parent, "left-control", &node->leftControl);
0090     KisDomUtils::loadValue(parent, "right-control", &node->rightControl);
0091     KisDomUtils::loadValue(parent, "top-control", &node->topControl);
0092     KisDomUtils::loadValue(parent, "bottom-control", &node->bottomControl);
0093 
0094     return true;
0095 }
0096 
0097 void saveValue(QDomElement *parent, const QString &tag, const KisBezierGradientMesh &mesh)
0098 {
0099     QDomDocument doc = parent->ownerDocument();
0100     QDomElement e = doc.createElement(tag);
0101     parent->appendChild(e);
0102 
0103     e.setAttribute("type", "gradient-mesh");
0104 
0105     KisDomUtils::saveValue(&e, "size", mesh.m_size);
0106     KisDomUtils::saveValue(&e, "srcRect", mesh.m_originalRect);
0107     KisDomUtils::saveValue(&e, "columns", mesh.m_columns);
0108     KisDomUtils::saveValue(&e, "rows", mesh.m_rows);
0109     KisDomUtils::saveValue(&e, "nodes", mesh.m_nodes);
0110 }
0111 
0112 bool loadValue(const QDomElement &parent, const QString &tag, KisBezierGradientMesh *mesh)
0113 {
0114     QDomElement e;
0115     if (!KisDomUtils::findOnlyElement(parent, tag, &e)) return false;
0116 
0117     if (!KisDomUtils::Private::checkType(e, "gradient-mesh")) return false;
0118 
0119     mesh->m_columns.clear();
0120     mesh->m_rows.clear();
0121     mesh->m_nodes.clear();
0122 
0123     KisDomUtils::loadValue(e, "size", &mesh->m_size);
0124     KisDomUtils::loadValue(e, "srcRect", &mesh->m_originalRect);
0125     KisDomUtils::loadValue(e, "columns", &mesh->m_columns);
0126     KisDomUtils::loadValue(e, "rows", &mesh->m_rows);
0127     KisDomUtils::loadValue(e, "nodes", &mesh->m_nodes);
0128 
0129     return true;
0130 }
0131 
0132 }
0133 
0134 KisBezierGradientMesh::PatchIndex KisBezierGradientMesh::hitTestPatch(const QPointF &pt, QPointF *localPointResult) const {
0135     auto result = endPatches();
0136 
0137     const QRectF unitRect(0, 0, 1, 1);
0138 
0139     for (auto it = beginPatches(); it != endPatches(); ++it) {
0140         Patch patch = *it;
0141 
0142         if (patch.dstBoundingRect().contains(pt)) {
0143             const QPointF localPos = KisBezierUtils::calculateLocalPosSVG2(patch.points, pt);
0144 
0145             if (unitRect.contains(localPos)) {
0146 
0147                 if (localPointResult) {
0148                     *localPointResult = localPos;
0149                 }
0150 
0151                 result = it;
0152                 break;
0153             }
0154         }
0155     }
0156 
0157     return result.patchIndex();
0158 }
0159 
0160 void KisBezierGradientMesh::renderPatch(const KisBezierGradientMeshDetail::GradientMeshPatch &patch,
0161                                         const QPoint &dstQImageOffset,
0162                                         QImage *dstImage)
0163 {
0164     QVector<QPointF> originalPointsLocal;
0165     QVector<QPointF> transformedPointsLocal;
0166     QSize gridSize;
0167 
0168     patch.sampleRegularGridSVG2(gridSize, originalPointsLocal, transformedPointsLocal, QPointF(8,8));
0169 
0170     const QRect dstBoundsI = patch.dstBoundingRect().toAlignedRect();
0171     const QRect imageSize = QRect(dstQImageOffset, dstImage->size());
0172     KIS_SAFE_ASSERT_RECOVER_NOOP(imageSize.contains(dstBoundsI));
0173 
0174     {
0175         QImageGradientOp polygonOp(patch.colors, *dstImage, dstQImageOffset);
0176 
0177 
0178         GridIterationTools::RegularGridIndexesOp indexesOp(gridSize);
0179         GridIterationTools::iterateThroughGrid
0180                 <GridIterationTools::AlwaysCompletePolygonPolicy>(polygonOp, indexesOp,
0181                                                                   gridSize,
0182                                                                   originalPointsLocal,
0183                                                                   transformedPointsLocal);
0184 
0185     }
0186 }
0187 
0188 void KisBezierGradientMesh::renderMesh(const QPoint &dstQImageOffset,
0189                                        QImage *dstImage) const
0190 {
0191     for (auto it = beginPatches(); it != endPatches(); ++it) {
0192         renderPatch(*it, dstQImageOffset, dstImage);
0193     }
0194 }