File indexing completed on 2024-05-12 16:01:37
0001 /* 0002 * SPDX-FileCopyrightText: 2008, 2011 Cyrille Berger <cberger@cberger.net> 0003 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com> 0004 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include <QXmlStreamReader> 0010 #include "kis_painting_assistant.h" 0011 #include "kis_coordinates_converter.h" 0012 #include "kis_debug.h" 0013 #include "kis_dom_utils.h" 0014 #include <kis_canvas2.h> 0015 #include "kis_tool.h" 0016 #include "kis_config.h" 0017 0018 #include <KoStore.h> 0019 0020 #include <QGlobalStatic> 0021 #include <QPen> 0022 #include <QPainter> 0023 #include <QPixmapCache> 0024 #include <QDomElement> 0025 #include <QDomDocument> 0026 0027 Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) 0028 0029 struct KisPaintingAssistantHandle::Private { 0030 QList<KisPaintingAssistant*> assistants; 0031 char handle_type; 0032 }; 0033 0034 KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) 0035 { 0036 } 0037 0038 KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) 0039 { 0040 } 0041 0042 KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) 0043 : QPointF(rhs) 0044 , KisShared() 0045 , d(new Private) 0046 { 0047 dbgUI << "KisPaintingAssistantHandle ctor"; 0048 } 0049 0050 KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) 0051 { 0052 setX(pt.x()); 0053 setY(pt.y()); 0054 return *this; 0055 } 0056 0057 void KisPaintingAssistantHandle::setType(char type) 0058 { 0059 d->handle_type = type; 0060 } 0061 0062 char KisPaintingAssistantHandle::handleType() const 0063 { 0064 return d->handle_type; 0065 } 0066 0067 KisPaintingAssistant *KisPaintingAssistantHandle::chiefAssistant() const 0068 { 0069 return !d->assistants.isEmpty() ? d->assistants.first() : 0; 0070 } 0071 0072 KisPaintingAssistantHandle::~KisPaintingAssistantHandle() 0073 { 0074 Q_ASSERT(d->assistants.empty()); 0075 delete d; 0076 } 0077 0078 void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) 0079 { 0080 Q_ASSERT(!d->assistants.contains(assistant)); 0081 d->assistants.append(assistant); 0082 } 0083 0084 void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) 0085 { 0086 d->assistants.removeOne(assistant); 0087 Q_ASSERT(!d->assistants.contains(assistant)); 0088 } 0089 0090 bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) const 0091 { 0092 return d->assistants.contains(assistant); 0093 } 0094 0095 void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) 0096 { 0097 if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) { 0098 return; 0099 } 0100 0101 0102 Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { 0103 if (!assistant->handles().contains(this)) { 0104 assistant->replaceHandle(handle, this); 0105 } 0106 } 0107 } 0108 0109 void KisPaintingAssistantHandle::uncache() 0110 { 0111 Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { 0112 assistant->uncache(); 0113 } 0114 } 0115 0116 struct KisPaintingAssistant::Private { 0117 Private(); 0118 explicit Private(const Private &rhs); 0119 KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant = true); 0120 QList<KisPaintingAssistantHandleSP> handles, sideHandles; 0121 0122 KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; 0123 0124 // share everything except handles between the clones 0125 struct SharedData { 0126 QString id; 0127 QString name; 0128 bool isSnappingActive {true}; 0129 bool outlineVisible {true}; 0130 bool isLocal {false}; 0131 0132 KisCanvas2* m_canvas {nullptr}; 0133 0134 QPixmapCache::Key cached; 0135 QRect cachedRect; // relative to boundingRect().topLeft() 0136 0137 struct TranslationInvariantTransform { 0138 qreal m11 {0.0}; 0139 qreal m12 {0.0}; 0140 qreal m21 {0.0}; 0141 qreal m22 {0.0}; 0142 0143 TranslationInvariantTransform() { } 0144 TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } 0145 bool operator==(const TranslationInvariantTransform& b) { 0146 return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; 0147 } 0148 } cachedTransform; 0149 0150 QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set 0151 0152 bool useCustomColor {false}; 0153 QColor assistantCustomColor {KisConfig(true).defaultAssistantsColor()}; 0154 }; 0155 0156 QSharedPointer<SharedData> s; 0157 }; 0158 0159 KisPaintingAssistant::Private::Private() 0160 : s(new SharedData) 0161 { 0162 } 0163 0164 KisPaintingAssistant::Private::Private(const Private &rhs) 0165 : s(rhs.s) 0166 { 0167 } 0168 0169 KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant) 0170 { 0171 KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle); 0172 if (!mappedHandle) { 0173 if (origHandle) { 0174 dbgUI << "handle not found in the map, creating a new one..."; 0175 mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle)); 0176 dbgUI << "done"; 0177 mappedHandle->setType(origHandle->handleType()); 0178 handleMap.insert(origHandle, mappedHandle); 0179 } else { 0180 dbgUI << "orig handle is null, not doing anything"; 0181 mappedHandle = KisPaintingAssistantHandleSP(); 0182 } 0183 } 0184 if (mappedHandle && registerAssistant) { 0185 mappedHandle->registerAssistant(q); 0186 } 0187 return mappedHandle; 0188 } 0189 0190 bool KisPaintingAssistant::useCustomColor() 0191 { 0192 return d->s->useCustomColor; 0193 } 0194 0195 void KisPaintingAssistant::setUseCustomColor(bool useCustomColor) 0196 { 0197 d->s->useCustomColor = useCustomColor; 0198 } 0199 0200 void KisPaintingAssistant::setAssistantCustomColor(QColor color) 0201 { 0202 d->s->assistantCustomColor = color; 0203 } 0204 0205 QColor KisPaintingAssistant::assistantCustomColor() 0206 { 0207 return d->s->assistantCustomColor; 0208 } 0209 0210 void KisPaintingAssistant::setAssistantGlobalColorCache(const QColor &color) 0211 { 0212 d->s->assistantGlobalColorCache = color; 0213 } 0214 0215 QColor KisPaintingAssistant::effectiveAssistantColor() const 0216 { 0217 return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache; 0218 } 0219 0220 KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) 0221 { 0222 d->s->id = id; 0223 d->s->name = name; 0224 d->s->isSnappingActive = true; 0225 d->s->outlineVisible = true; 0226 } 0227 0228 KisPaintingAssistant::KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap) 0229 : d(new Private(*(rhs.d))) 0230 { 0231 dbgUI << "creating handles..."; 0232 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) { 0233 d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this); 0234 } 0235 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) { 0236 d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this); 0237 } 0238 #define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, /* registerAssistant = */ false) 0239 _REUSE_H(topLeft); 0240 _REUSE_H(bottomLeft); 0241 _REUSE_H(topRight); 0242 _REUSE_H(bottomRight); 0243 _REUSE_H(topMiddle); 0244 _REUSE_H(bottomMiddle); 0245 _REUSE_H(rightMiddle); 0246 _REUSE_H(leftMiddle); 0247 #undef _REUSE_H 0248 dbgUI << "done"; 0249 } 0250 0251 bool KisPaintingAssistant::isSnappingActive() const 0252 { 0253 return d->s->isSnappingActive; 0254 } 0255 0256 void KisPaintingAssistant::setSnappingActive(bool set) 0257 { 0258 d->s->isSnappingActive = set; 0259 } 0260 0261 bool KisPaintingAssistant::canBeLocal() const 0262 { 0263 return false; 0264 } 0265 0266 bool KisPaintingAssistant::isLocal() const 0267 { 0268 return d->s->isLocal; 0269 } 0270 0271 void KisPaintingAssistant::setLocal(bool value) 0272 { 0273 d->s->isLocal = value; 0274 } 0275 0276 0277 void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn) 0278 { 0279 0280 QColor paintingColor = effectiveAssistantColor(); 0281 0282 if (!isSnappingOn) { 0283 paintingColor.setAlpha(0.2 * paintingColor.alpha()); 0284 } 0285 0286 painter.save(); 0287 QPen pen_a(paintingColor, 2); 0288 pen_a.setCosmetic(true); 0289 painter.setPen(pen_a); 0290 painter.drawPath(path); 0291 painter.restore(); 0292 } 0293 0294 void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) 0295 { 0296 painter.save(); 0297 QPen pen_a(effectiveAssistantColor(), 1); 0298 pen_a.setStyle(Qt::SolidLine); 0299 pen_a.setCosmetic(true); 0300 painter.setPen(pen_a); 0301 painter.drawPath(path); 0302 painter.restore(); 0303 } 0304 0305 void KisPaintingAssistant::initHandles(QList<KisPaintingAssistantHandleSP> _handles) 0306 { 0307 Q_ASSERT(d->handles.isEmpty()); 0308 d->handles = _handles; 0309 Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { 0310 handle->registerAssistant(this); 0311 } 0312 } 0313 0314 KisPaintingAssistant::~KisPaintingAssistant() 0315 { 0316 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { 0317 handle->unregisterAssistant(this); 0318 } 0319 if(!d->sideHandles.isEmpty()) { 0320 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0321 handle->unregisterAssistant(this); 0322 } 0323 } 0324 delete d; 0325 } 0326 0327 const QString& KisPaintingAssistant::id() const 0328 { 0329 return d->s->id; 0330 } 0331 0332 const QString& KisPaintingAssistant::name() const 0333 { 0334 return d->s->name; 0335 } 0336 0337 void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) 0338 { 0339 Q_ASSERT(d->handles.contains(_handle)); 0340 d->handles.replace(d->handles.indexOf(_handle), _with); 0341 Q_ASSERT(!d->handles.contains(_handle)); 0342 _handle->unregisterAssistant(this); 0343 _with->registerAssistant(this); 0344 } 0345 0346 void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type) 0347 { 0348 Q_ASSERT(!d->handles.contains(handle)); 0349 if (HandleType::SIDE == type) { 0350 d->sideHandles.append(handle); 0351 } else { 0352 d->handles.append(handle); 0353 } 0354 0355 handle->registerAssistant(this); 0356 handle.data()->setType(type); 0357 } 0358 0359 QPointF KisPaintingAssistant::viewportConstrainedEditorPosition(const KisCoordinatesConverter* converter, const QSize editorSize) 0360 { 0361 QPointF editorDocumentPos = getEditorPosition(); 0362 QPointF editorWidgetPos = converter->documentToWidgetTransform().map(editorDocumentPos); 0363 QSizeF canvasSize = converter->getCanvasWidgetSize(); 0364 const int padding = 16; 0365 0366 editorWidgetPos.rx() = qBound(0.0, 0367 editorWidgetPos.x(), 0368 canvasSize.width() - (editorSize.width() + padding)); 0369 editorWidgetPos.ry() = qBound(0.0, 0370 editorWidgetPos.y(), 0371 canvasSize.height() - (editorSize.height() + padding)); 0372 0373 return converter->widgetToDocument(editorWidgetPos); 0374 } 0375 0376 void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) 0377 { 0378 Q_UNUSED(updateRect); 0379 0380 Q_UNUSED(previewVisible); 0381 0382 findPerspectiveAssistantHandleLocation(); 0383 0384 if (!useCache) { 0385 gc.save(); 0386 drawCache(gc, converter, assistantVisible); 0387 gc.restore(); 0388 return; 0389 } 0390 0391 const QRect bound = boundingRect(); 0392 if (bound.isEmpty()) { 0393 return; 0394 } 0395 0396 const QTransform transform = converter->documentToWidgetTransform(); 0397 const QRect widgetBound = transform.mapRect(bound); 0398 0399 const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); 0400 if (paintRect.isEmpty()) return; 0401 0402 QPixmap cached; 0403 bool found = QPixmapCache::find(d->s->cached, &cached); 0404 0405 if (!(found && 0406 d->s->cachedTransform == transform && 0407 d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { 0408 0409 const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); 0410 Q_ASSERT(!cacheRect.isEmpty()); 0411 0412 if (cached.isNull() || cached.size() != cacheRect.size()) { 0413 cached = QPixmap(cacheRect.size()); 0414 } 0415 0416 cached.fill(Qt::transparent); 0417 QPainter painter(&cached); 0418 painter.setRenderHint(QPainter::Antialiasing); 0419 painter.setWindow(cacheRect); 0420 drawCache(painter, converter, assistantVisible); 0421 painter.end(); 0422 d->s->cachedTransform = transform; 0423 d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft()); 0424 d->s->cached = QPixmapCache::insert(cached); 0425 } 0426 0427 gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft())); 0428 0429 0430 if (canvas) { 0431 d->s->m_canvas = canvas; 0432 } 0433 } 0434 0435 void KisPaintingAssistant::uncache() 0436 { 0437 d->s->cached = QPixmapCache::Key(); 0438 } 0439 0440 QRect KisPaintingAssistant::boundingRect() const 0441 { 0442 QRectF r; 0443 Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { 0444 r = r.united(QRectF(*h, QSizeF(1,1))); 0445 } 0446 return r.adjusted(-2, -2, 2, 2).toAlignedRect(); 0447 } 0448 0449 bool KisPaintingAssistant::isAssistantComplete() const 0450 { 0451 return true; 0452 } 0453 0454 void KisPaintingAssistant::transform(const QTransform &transform) 0455 { 0456 Q_FOREACH(KisPaintingAssistantHandleSP handle, handles()) { 0457 if (handle->chiefAssistant() != this) continue; 0458 0459 *handle = transform.map(*handle); 0460 } 0461 0462 Q_FOREACH(KisPaintingAssistantHandleSP handle, sideHandles()) { 0463 if (handle->chiefAssistant() != this) continue; 0464 0465 *handle = transform.map(*handle); 0466 } 0467 0468 uncache(); 0469 } 0470 0471 QByteArray KisPaintingAssistant::saveXml(QMap<KisPaintingAssistantHandleSP, int> &handleMap) 0472 { 0473 QByteArray data; 0474 QXmlStreamWriter xml(&data); 0475 xml.writeStartDocument(); 0476 xml.writeStartElement("assistant"); 0477 xml.writeAttribute("type",d->s->id); 0478 xml.writeAttribute("active", QString::number(d->s->isSnappingActive)); 0479 xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor)); 0480 xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor)); 0481 0482 0483 0484 saveCustomXml(&xml); // if any specific assistants have custom XML data to save to 0485 0486 // write individual handle data 0487 xml.writeStartElement("handles"); 0488 Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { 0489 int id = handleMap.size(); 0490 if (!handleMap.contains(handle)){ 0491 handleMap.insert(handle, id); 0492 } 0493 id = handleMap.value(handle); 0494 xml.writeStartElement("handle"); 0495 xml.writeAttribute("id", QString::number(id)); 0496 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); 0497 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); 0498 xml.writeEndElement(); 0499 } 0500 xml.writeEndElement(); 0501 if (!d->sideHandles.isEmpty()) { // for vanishing points only 0502 xml.writeStartElement("sidehandles"); 0503 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap; 0504 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0505 int id = sideHandleMap.size(); 0506 sideHandleMap.insert(handle, id); 0507 xml.writeStartElement("sidehandle"); 0508 xml.writeAttribute("id", QString::number(id)); 0509 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); 0510 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); 0511 xml.writeEndElement(); 0512 } 0513 } 0514 0515 xml.writeEndElement(); 0516 xml.writeEndDocument(); 0517 return data; 0518 } 0519 0520 void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml) 0521 { 0522 Q_UNUSED(xml); 0523 } 0524 0525 void KisPaintingAssistant::loadXml(KoStore* store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path) 0526 { 0527 int id = 0; 0528 double x = 0.0, y = 0.0; 0529 store->open(path); 0530 QByteArray data = store->read(store->size()); 0531 QXmlStreamReader xml(data); 0532 QMap<int, KisPaintingAssistantHandleSP> sideHandleMap; 0533 while (!xml.atEnd()) { 0534 switch (xml.readNext()) { 0535 case QXmlStreamReader::StartElement: 0536 if (xml.name() == "assistant") { 0537 0538 QStringRef active = xml.attributes().value("active"); 0539 setSnappingActive( (active != "0") ); 0540 0541 // load custom shared assistant properties 0542 if ( xml.attributes().hasAttribute("useCustomColor")) { 0543 QStringRef useCustomColor = xml.attributes().value("useCustomColor"); 0544 0545 bool usingColor = false; 0546 if (useCustomColor.toString() == "1") { 0547 usingColor = true; 0548 } 0549 0550 0551 setUseCustomColor(usingColor); 0552 } 0553 0554 if ( xml.attributes().hasAttribute("customColor")) { 0555 QStringRef customColor = xml.attributes().value("customColor"); 0556 setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); 0557 0558 } 0559 0560 } 0561 0562 loadCustomXml(&xml); 0563 0564 if (xml.name() == "handle") { 0565 QString strId = xml.attributes().value("id").toString(), 0566 strX = xml.attributes().value("x").toString(), 0567 strY = xml.attributes().value("y").toString(); 0568 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { 0569 id = strId.toInt(); 0570 x = strX.toDouble(); 0571 y = strY.toDouble(); 0572 if (!handleMap.contains(id)) { 0573 handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); 0574 } 0575 } 0576 addHandle(handleMap.value(id), HandleType::NORMAL); 0577 } else if (xml.name() == "sidehandle") { 0578 QString strId = xml.attributes().value("id").toString(), 0579 strX = xml.attributes().value("x").toString(), 0580 strY = xml.attributes().value("y").toString(); 0581 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { 0582 id = strId.toInt(); 0583 x = strX.toDouble(); 0584 y = strY.toDouble(); 0585 if (!sideHandleMap.contains(id)) { 0586 sideHandleMap.insert(id, new KisPaintingAssistantHandle(x, y)); 0587 } 0588 } 0589 addHandle(sideHandleMap.value(id), HandleType::SIDE); 0590 0591 } 0592 break; 0593 default: 0594 break; 0595 } 0596 } 0597 store->close(); 0598 } 0599 0600 bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml) 0601 { 0602 Q_UNUSED(xml); 0603 return true; 0604 } 0605 0606 void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) 0607 { 0608 if (d->s->id == "ellipse"){ 0609 QDomElement assistantElement = doc.createElement("assistant"); 0610 assistantElement.setAttribute("type", "ellipse"); 0611 assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); 0612 assistantsElement.appendChild(assistantElement); 0613 } 0614 else if (d->s->id == "spline"){ 0615 QDomElement assistantElement = doc.createElement("assistant"); 0616 assistantElement.setAttribute("type", "spline"); 0617 assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); 0618 assistantsElement.appendChild(assistantElement); 0619 } 0620 else if (d->s->id == "perspective"){ 0621 QDomElement assistantElement = doc.createElement("assistant"); 0622 assistantElement.setAttribute("type", "perspective"); 0623 assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); 0624 assistantsElement.appendChild(assistantElement); 0625 } 0626 else if (d->s->id == "vanishing point"){ 0627 QDomElement assistantElement = doc.createElement("assistant"); 0628 assistantElement.setAttribute("type", "vanishing point"); 0629 assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); 0630 assistantsElement.appendChild(assistantElement); 0631 } 0632 else if (d->s->id == "infinite ruler"){ 0633 QDomElement assistantElement = doc.createElement("assistant"); 0634 assistantElement.setAttribute("type", "infinite ruler"); 0635 assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); 0636 assistantsElement.appendChild(assistantElement); 0637 } 0638 else if (d->s->id == "parallel ruler"){ 0639 QDomElement assistantElement = doc.createElement("assistant"); 0640 assistantElement.setAttribute("type", "parallel ruler"); 0641 assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); 0642 assistantsElement.appendChild(assistantElement); 0643 } 0644 else if (d->s->id == "concentric ellipse"){ 0645 QDomElement assistantElement = doc.createElement("assistant"); 0646 assistantElement.setAttribute("type", "concentric ellipse"); 0647 assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); 0648 assistantsElement.appendChild(assistantElement); 0649 } 0650 else if (d->s->id == "fisheye-point"){ 0651 QDomElement assistantElement = doc.createElement("assistant"); 0652 assistantElement.setAttribute("type", "fisheye-point"); 0653 assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); 0654 assistantsElement.appendChild(assistantElement); 0655 } 0656 else if (d->s->id == "ruler"){ 0657 QDomElement assistantElement = doc.createElement("assistant"); 0658 assistantElement.setAttribute("type", "ruler"); 0659 assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); 0660 assistantsElement.appendChild(assistantElement); 0661 } 0662 else if (d->s->id == "two point"){ 0663 QDomElement assistantElement = doc.createElement("assistant"); 0664 assistantElement.setAttribute("type", "two point"); 0665 assistantElement.setAttribute("filename", QString("two point%1.assistant").arg(count)); 0666 assistantsElement.appendChild(assistantElement); 0667 } 0668 else if (d->s->id == "perspective ellipse"){ 0669 QDomElement assistantElement = doc.createElement("assistant"); 0670 assistantElement.setAttribute("type", "perspective ellipse"); 0671 assistantElement.setAttribute("filename", QString("perspective ellipse%1.assistant").arg(count)); 0672 assistantsElement.appendChild(assistantElement); 0673 } 0674 } 0675 0676 void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() { 0677 QList<KisPaintingAssistantHandleSP> hHandlesList; 0678 QList<KisPaintingAssistantHandleSP> vHandlesList; 0679 uint vHole = 0,hHole = 0; 0680 KisPaintingAssistantHandleSP oppHandle; 0681 if (d->handles.size() == 4 && d->s->id == "perspective") { 0682 //get the handle opposite to the first handle 0683 oppHandle = oppHandleOne(); 0684 //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. 0685 Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { 0686 hHandlesList.append(handle); 0687 hHole = hHandlesList.size() - 1; 0688 vHandlesList.append(handle); 0689 vHole = vHandlesList.size() - 1; 0690 /* 0691 sort handles on the basis of X-coordinate 0692 */ 0693 while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { 0694 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) 0695 hHandlesList.swapItemsAt(hHole - 1, hHole); 0696 #else 0697 hHandlesList.swap(hHole - 1, hHole); 0698 #endif 0699 hHole = hHole - 1; 0700 } 0701 /* 0702 sort handles on the basis of Y-coordinate 0703 */ 0704 while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { 0705 #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) 0706 vHandlesList.swapItemsAt(vHole-1, vHole); 0707 #else 0708 vHandlesList.swap(vHole-1, vHole); 0709 #endif 0710 vHole = vHole - 1; 0711 } 0712 } 0713 0714 /* 0715 give the handles their respective positions 0716 */ 0717 if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { 0718 d->topLeft = vHandlesList.at(1); 0719 d->topRight= vHandlesList.at(0); 0720 } 0721 else { 0722 d->topLeft = vHandlesList.at(0); 0723 d->topRight = vHandlesList.at(1); 0724 } 0725 if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { 0726 d->bottomLeft = vHandlesList.at(3); 0727 d->bottomRight = vHandlesList.at(2); 0728 } 0729 else { 0730 d->bottomLeft= vHandlesList.at(2); 0731 d->bottomRight = vHandlesList.at(3); 0732 } 0733 0734 /* 0735 find if the handles that should be opposite are actually oppositely positioned 0736 */ 0737 if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || 0738 (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || 0739 (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || 0740 (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) 0741 {} 0742 else { 0743 if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { 0744 d->topLeft = hHandlesList.at(1); 0745 d->bottomLeft= hHandlesList.at(0); 0746 } 0747 else { 0748 d->topLeft = hHandlesList.at(0); 0749 d->bottomLeft = hHandlesList.at(1); 0750 } 0751 if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { 0752 d->topRight = hHandlesList.at(3); 0753 d->bottomRight = hHandlesList.at(2); 0754 } 0755 else { 0756 d->topRight= hHandlesList.at(2); 0757 d->bottomRight = hHandlesList.at(3); 0758 } 0759 0760 } 0761 /* 0762 Setting the middle handles as needed 0763 */ 0764 if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { 0765 0766 // Before re-adding the handles, clear old ones that have been 0767 // potentially loaded from disk and not re-associated with the 0768 // xxxMiddle pointers in d; otherwise those would stay in place. 0769 if(!d->sideHandles.isEmpty()) { 0770 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { 0771 handle->unregisterAssistant(this); 0772 } 0773 d->sideHandles.clear(); 0774 } 0775 0776 d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, 0777 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); 0778 d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, 0779 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); 0780 d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, 0781 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); 0782 d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, 0783 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); 0784 0785 addHandle(d->rightMiddle, HandleType::SIDE); 0786 addHandle(d->leftMiddle, HandleType::SIDE); 0787 addHandle(d->bottomMiddle, HandleType::SIDE); 0788 addHandle(d->topMiddle, HandleType::SIDE); 0789 } 0790 else 0791 { 0792 d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, 0793 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); 0794 d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, 0795 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); 0796 d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, 0797 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); 0798 d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, 0799 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); 0800 } 0801 0802 } 0803 } 0804 0805 KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() 0806 { 0807 QPointF intersection(0,0); 0808 if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) 0809 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersect(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) 0810 { 0811 return d->handles.at(1); 0812 } 0813 else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection) 0814 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersect(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection)) 0815 { 0816 return d->handles.at(2); 0817 } 0818 else 0819 { 0820 return d->handles.at(3); 0821 } 0822 } 0823 0824 KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() 0825 { 0826 return d->topLeft; 0827 } 0828 0829 const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const 0830 { 0831 return d->topLeft; 0832 } 0833 0834 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() 0835 { 0836 return d->bottomLeft; 0837 } 0838 0839 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const 0840 { 0841 return d->bottomLeft; 0842 } 0843 0844 KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() 0845 { 0846 return d->topRight; 0847 } 0848 0849 const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const 0850 { 0851 return d->topRight; 0852 } 0853 0854 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() 0855 { 0856 return d->bottomRight; 0857 } 0858 0859 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const 0860 { 0861 return d->bottomRight; 0862 } 0863 0864 KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() 0865 { 0866 return d->topMiddle; 0867 } 0868 0869 const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const 0870 { 0871 return d->topMiddle; 0872 } 0873 0874 KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() 0875 { 0876 return d->bottomMiddle; 0877 } 0878 0879 const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const 0880 { 0881 return d->bottomMiddle; 0882 } 0883 0884 KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() 0885 { 0886 return d->rightMiddle; 0887 } 0888 0889 const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const 0890 { 0891 return d->rightMiddle; 0892 } 0893 0894 KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() 0895 { 0896 return d->leftMiddle; 0897 } 0898 0899 const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const 0900 { 0901 return d->leftMiddle; 0902 } 0903 0904 const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::handles() const 0905 { 0906 return d->handles; 0907 } 0908 0909 QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::handles() 0910 { 0911 return d->handles; 0912 } 0913 0914 const QList<KisPaintingAssistantHandleSP>& KisPaintingAssistant::sideHandles() const 0915 { 0916 return d->sideHandles; 0917 } 0918 0919 QList<KisPaintingAssistantHandleSP> KisPaintingAssistant::sideHandles() 0920 { 0921 return d->sideHandles; 0922 } 0923 0924 0925 0926 bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo) 0927 { 0928 int m_handleSize = 16; 0929 0930 QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); 0931 return handlerect.contains(pointOne); 0932 } 0933 0934 KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point) 0935 { 0936 if (!d->s->m_canvas) { 0937 return 0; 0938 } 0939 0940 0941 if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) { 0942 return topLeft(); 0943 } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) { 0944 return topRight(); 0945 } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) { 0946 return bottomLeft(); 0947 } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) { 0948 return bottomRight(); 0949 } 0950 return 0; 0951 } 0952 0953 0954 QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const 0955 { 0956 QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords); 0957 return d->s->m_canvas->viewConverter()->documentToView(documentCoord); 0958 } 0959 0960 KisPaintingAssistantHandleSP KisPaintingAssistant::firstLocalHandle() const 0961 { 0962 return 0; 0963 } 0964 0965 KisPaintingAssistantHandleSP KisPaintingAssistant::secondLocalHandle() const 0966 { 0967 return 0; 0968 } 0969 0970 QRectF KisPaintingAssistant::getLocalRect() const 0971 { 0972 if (!isLocal() || !firstLocalHandle() || !secondLocalHandle()) { 0973 return QRectF(); 0974 } 0975 0976 KisPaintingAssistantHandleSP first = firstLocalHandle(); 0977 KisPaintingAssistantHandleSP second = secondLocalHandle(); 0978 0979 QPointF topLeft = QPointF(qMin(first->x(), second->x()), qMin(first->y(), second->y())); 0980 QPointF bottomRight = QPointF(qMax(first->x(), second->x()), qMax(first->y(), second->y())); 0981 0982 QRectF rect(topLeft, bottomRight); 0983 return rect; 0984 } 0985 0986 double KisPaintingAssistant::norm2(const QPointF& p) 0987 { 0988 return p.x() * p.x() + p.y() * p.y(); 0989 } 0990 0991 QList<KisPaintingAssistantSP> KisPaintingAssistant::cloneAssistantList(const QList<KisPaintingAssistantSP> &list) 0992 { 0993 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap; 0994 QList<KisPaintingAssistantSP> clonedList; 0995 for (auto i = list.begin(); i != list.end(); ++i) { 0996 clonedList << (*i)->clone(handleMap); 0997 } 0998 return clonedList; 0999 } 1000 1001 1002 1003 /* 1004 * KisPaintingAssistantFactory classes 1005 */ 1006 1007 KisPaintingAssistantFactory::KisPaintingAssistantFactory() 1008 { 1009 } 1010 1011 KisPaintingAssistantFactory::~KisPaintingAssistantFactory() 1012 { 1013 } 1014 1015 KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() 1016 { 1017 } 1018 1019 KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() 1020 { 1021 Q_FOREACH (const QString &id, keys()) { 1022 delete get(id); 1023 } 1024 dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; 1025 } 1026 1027 KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() 1028 { 1029 return s_instance; 1030 } 1031