File indexing completed on 2024-06-16 04:15:52

0001 /*
0002  * SPDX-FileCopyrightText: 2008 Cyrille Berger <cberger@cberger.net>
0003  * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
0004  * SPDX-FileCopyrightText: 2014 Wolthera van Hövell tot Westerflier <griffinvalley@gmail.com>
0005  * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
0006  *
0007  *  SPDX-License-Identifier: LGPL-2.0-or-later
0008  */
0009 
0010 #include "VanishingPointAssistant.h"
0011 
0012 #include "kis_debug.h"
0013 #include <klocalizedstring.h>
0014 
0015 #include <QPainter>
0016 #include <QPainterPath>
0017 #include <QLinearGradient>
0018 #include <QTransform>
0019 
0020 #include <kis_canvas2.h>
0021 #include <kis_coordinates_converter.h>
0022 #include <kis_algebra_2d.h>
0023 #include <kis_dom_utils.h>
0024 #include <math.h>
0025 
0026 VanishingPointAssistant::VanishingPointAssistant()
0027     : KisPaintingAssistant("vanishing point", i18n("Vanishing Point assistant"))
0028 {
0029 }
0030 
0031 VanishingPointAssistant::VanishingPointAssistant(const VanishingPointAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
0032     : KisPaintingAssistant(rhs, handleMap)
0033     , m_canvas(rhs.m_canvas)
0034     , m_referenceLineDensity(rhs.m_referenceLineDensity)
0035 {
0036 }
0037 
0038 KisPaintingAssistantSP VanishingPointAssistant::clone(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) const
0039 {
0040     return KisPaintingAssistantSP(new VanishingPointAssistant(*this, handleMap));
0041 }
0042 
0043 QPointF VanishingPointAssistant::project(const QPointF& pt, const QPointF& strokeBegin, qreal /*moveThresholdPt*/)
0044 {
0045     //Q_ASSERT(handles().size() == 1 || handles().size() == 5);
0046 
0047     if (isLocal() && isAssistantComplete()) {
0048         if (getLocalRect().contains(pt)) {
0049             m_hasBeenInsideLocalRect = true;
0050         } else if (!m_hasBeenInsideLocalRect) { // isn't inside and wasn't inside before
0051             return QPointF(qQNaN(), qQNaN());
0052         }
0053     }
0054 
0055     //dbgKrita<<strokeBegin<< ", " <<*handles()[0];
0056     QLineF snapLine = QLineF(*handles()[0], strokeBegin);
0057 
0058 
0059     qreal dx = snapLine.dx();
0060     qreal dy = snapLine.dy();
0061 
0062     const qreal dx2 = dx * dx;
0063     const qreal dy2 = dy * dy;
0064     const qreal invsqrlen = 1.0 / (dx2 + dy2);
0065 
0066     QPointF r(dx2 * pt.x() + dy2 * snapLine.x1() + dx * dy * (pt.y() - snapLine.y1()),
0067               dx2 * snapLine.y1() + dy2 * pt.y() + dx * dy * (pt.x() - snapLine.x1()));
0068 
0069     r *= invsqrlen;
0070     return r;
0071 }
0072 
0073 QPointF VanishingPointAssistant::adjustPosition(const QPointF& pt, const QPointF& strokeBegin, const bool /*snapToAny*/, qreal moveThresholdPt)
0074 {
0075     return project(pt, strokeBegin, moveThresholdPt);
0076 }
0077 
0078 void VanishingPointAssistant::adjustLine(QPointF &point, QPointF &strokeBegin)
0079 {
0080     point = project(point, strokeBegin, 0.0);
0081 }
0082 
0083 void VanishingPointAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
0084 {
0085     // HACK ALERT: side handles aren't saved in old krita versions
0086     // we need to just add a default position for now if we are loading a vanishing point
0087     if (sideHandles().isEmpty()) {
0088         QPointF vpPoint = *handles()[0]; // main vanishing point
0089         addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(-70,0)), HandleType::SIDE);
0090         addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(-140,0)), HandleType::SIDE);
0091         addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(70,0)), HandleType::SIDE);
0092         addHandle(new KisPaintingAssistantHandle(vpPoint + QPointF(140,0)), HandleType::SIDE);
0093     }
0094 
0095     gc.save();
0096     gc.resetTransform();
0097 
0098     QRect viewport= gc.viewport();
0099 
0100     QPolygonF viewportAndLocalPoly = (isLocal() && isAssistantComplete()) ?
0101                 QPolygonF(QRectF(viewport)).intersected(converter->documentToWidgetTransform().map(QPolygonF(QRectF(getLocalRect())))) : QPolygonF(QRectF(viewport));
0102 
0103     // draw controls when we are not editing
0104     if (canvas && canvas->paintingAssistantsDecoration()->isEditingAssistants() == false && isAssistantComplete()) {
0105 
0106         if (isSnappingActive() && previewVisible == true) {
0107             //don't draw if invalid.
0108 
0109             QTransform initialTransform = converter->documentToWidgetTransform();
0110             QPointF startPoint = initialTransform.map(*handles()[0]);
0111             QPointF mousePos = effectiveBrushPosition(converter, canvas);
0112 
0113             QLineF snapLine= QLineF(startPoint, mousePos);
0114 
0115             KisAlgebra2D::cropLineToConvexPolygon(snapLine, viewportAndLocalPoly, false, true);
0116 
0117             QPainterPath path;
0118 
0119             path.moveTo(snapLine.p2());
0120             path.lineTo(snapLine.p1());
0121 
0122             drawPreview(gc, path);//and we draw the preview.
0123 
0124         }
0125     }
0126 
0127 
0128 
0129 
0130     // editor specific controls display
0131     if (canvas && canvas->paintingAssistantsDecoration()->isEditingAssistants()) {
0132 
0133         // draws a circle around the vanishing point node while editing
0134         QTransform initialTransform = converter->documentToWidgetTransform();
0135         QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point
0136         QPointF p1 = initialTransform.map(*sideHandles()[0]);
0137         QPointF p2 = initialTransform.map(*sideHandles()[1]);
0138         QPointF p3 = initialTransform.map(*sideHandles()[2]);
0139         QPointF p4 = initialTransform.map(*sideHandles()[3]);
0140 
0141 
0142         QRectF ellipse = QRectF(QPointF(p0.x() -15, p0.y() -15), QSizeF(30, 30));
0143 
0144         QPainterPath pathCenter;
0145         pathCenter.addEllipse(ellipse);
0146         drawPath(gc, pathCenter, isSnappingActive());
0147 
0148         QColor paintingColor = effectiveAssistantColor();
0149 
0150 
0151         // draw the lines connecting the different nodes
0152         QPen penStyle(paintingColor, 2.0, Qt::SolidLine);
0153 
0154         if (!isSnappingActive()) {
0155             QColor snappingColor = paintingColor;
0156             snappingColor.setAlpha(snappingColor.alpha() * 0.2);
0157 
0158             penStyle.setColor(snappingColor);
0159         }
0160 
0161         gc.save();
0162         gc.setPen(penStyle);
0163         gc.drawLine(p0, p1);
0164         gc.drawLine(p0, p3);
0165         gc.drawLine(p1, p2);
0166         gc.drawLine(p3, p4);
0167         gc.restore();
0168     }
0169 
0170     QTransform initialTransform = converter->documentToWidgetTransform();
0171 
0172     // draw the local rectangle
0173     if (assistantVisible && isLocal() && isAssistantComplete()) {
0174         // limited area rectangle
0175         QPainterPath path;
0176         QPointF p1 = *handles()[(int)LocalFirstHandle];
0177         QPointF p3 = *handles()[(int)LocalSecondHandle];
0178         QPointF p2 = QPointF(p1.x(), p3.y());
0179         QPointF p4 = QPointF(p3.x(), p1.y());
0180 
0181         path.moveTo(initialTransform.map(p1));
0182 
0183         path.lineTo(initialTransform.map(p2));
0184         path.lineTo(initialTransform.map(p3));
0185         path.lineTo(initialTransform.map(p4));
0186         path.lineTo(initialTransform.map(p1));
0187         drawPath(gc, path, isSnappingActive());//and we draw the rectangle
0188     }
0189 
0190 
0191     // draw references guide for vanishing points at specified density
0192     if (assistantVisible && this->isSnappingActive() ) {
0193 
0194         // cycle through degrees from 0 to 180. We are doing an infinite line, so we don't need to go 360
0195         QPointF p0 = initialTransform.map(*handles()[0]); // main vanishing point
0196 
0197         for (int currentAngle=0; currentAngle <= 180; currentAngle = currentAngle + m_referenceLineDensity ) {
0198 
0199             // determine the correct angle based on the iteration
0200             float xPos = cos(currentAngle * M_PI / 180);
0201             float yPos = sin(currentAngle * M_PI / 180);
0202             QPointF unitAngle;
0203             unitAngle.setX(p0.x() + xPos);
0204             unitAngle.setY(p0.y() + yPos);
0205 
0206             // find point
0207             QLineF snapLine= QLineF(p0, unitAngle);
0208             KisAlgebra2D::intersectLineConvexPolygon(snapLine, viewportAndLocalPoly, true, true);
0209 
0210             // make a line from VP center to edge of canvas with that angle
0211             QPainterPath path;
0212             path.moveTo(snapLine.p1());
0213             path.lineTo(snapLine.p2());
0214             drawPreview(gc, path);//and we draw the preview.
0215         }
0216     }
0217 
0218 
0219     gc.restore();
0220 
0221     KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible);
0222 }
0223 
0224 void VanishingPointAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible)
0225 {
0226     if (!m_canvas || !isAssistantComplete()) {
0227         return;
0228     }
0229 
0230     if (assistantVisible == false ||   m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) {
0231         return;
0232     }
0233 
0234     QTransform initialTransform = converter->documentToWidgetTransform();
0235     QPointF p0 = initialTransform.map(*handles()[0]);
0236 
0237     // draws an "X"
0238     QPainterPath path;
0239     path.moveTo(QPointF(p0.x() - 10.0, p0.y() - 10.0));
0240     path.lineTo(QPointF(p0.x() + 10.0, p0.y() + 10.0));
0241 
0242     path.moveTo(QPointF(p0.x() - 10.0, p0.y() + 10.0));
0243     path.lineTo(QPointF(p0.x() + 10.0, p0.y() - 10.0));
0244 
0245 
0246     drawPath(gc, path, isSnappingActive());
0247 }
0248 
0249 KisPaintingAssistantHandleSP VanishingPointAssistant::firstLocalHandle() const
0250 {
0251     if (handles().size() > LocalFirstHandle) {
0252         return handles().at(LocalFirstHandle);
0253     } else {
0254         return nullptr;
0255     }
0256 }
0257 
0258 KisPaintingAssistantHandleSP VanishingPointAssistant::secondLocalHandle() const
0259 {
0260     if (handles().size() > LocalSecondHandle) {
0261         return handles().at(LocalSecondHandle);
0262     } else {
0263         return nullptr;
0264     }
0265 }
0266 
0267 QPointF VanishingPointAssistant::getDefaultEditorPosition() const
0268 {
0269     int pointHandle = 0;
0270     if (handles().size() > pointHandle) {
0271         return *handles().at(pointHandle);
0272     } else {
0273         KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(false, QPointF(0, 0));
0274         return QPointF(0, 0);
0275     }
0276 }
0277 
0278 void VanishingPointAssistant::setReferenceLineDensity(float value)
0279 {
0280     // cannot have less than 1 degree value
0281     if (value < 1.0) {
0282         value = 1.0;
0283     }
0284 
0285     m_referenceLineDensity = value;
0286 }
0287 
0288 float VanishingPointAssistant::referenceLineDensity()
0289 {
0290     return m_referenceLineDensity;
0291 }
0292 
0293 bool VanishingPointAssistant::isAssistantComplete() const
0294 {
0295     return handles().size() >= numHandles();
0296 }
0297 
0298 bool VanishingPointAssistant::canBeLocal() const
0299 {
0300     return true;
0301 }
0302 
0303 void VanishingPointAssistant::saveCustomXml(QXmlStreamWriter* xml)
0304 {
0305     xml->writeStartElement("angleDensity");
0306     xml->writeAttribute("value", KisDomUtils::toString( this->referenceLineDensity()));
0307     xml->writeEndElement();
0308     xml->writeStartElement("isLocal");
0309     xml->writeAttribute("value", KisDomUtils::toString( (int)this->isLocal()));
0310     xml->writeEndElement();
0311 }
0312 
0313 bool VanishingPointAssistant::loadCustomXml(QXmlStreamReader* xml)
0314 {
0315     if (xml && xml->name() == "angleDensity") {
0316         this->setReferenceLineDensity((float)KisDomUtils::toDouble(xml->attributes().value("value").toString()));
0317     }
0318     if (xml && xml->name() == "isLocal") {
0319         this->setLocal((bool)KisDomUtils::toInt(xml->attributes().value("value").toString()));
0320     }
0321 
0322     return true;
0323 }
0324 
0325 
0326 VanishingPointAssistantFactory::VanishingPointAssistantFactory()
0327 {
0328 }
0329 
0330 VanishingPointAssistantFactory::~VanishingPointAssistantFactory()
0331 {
0332 }
0333 
0334 QString VanishingPointAssistantFactory::id() const
0335 {
0336     return "vanishing point";
0337 }
0338 
0339 QString VanishingPointAssistantFactory::name() const
0340 {
0341     return i18n("Vanishing Point");
0342 }
0343 
0344 KisPaintingAssistant* VanishingPointAssistantFactory::createPaintingAssistant() const
0345 {
0346     return new VanishingPointAssistant;
0347 }