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 }