Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_painting_assistant.cc
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2008, 2011 Cyrille Berger <cberger@cberger.net>
3 * SPDX-FileCopyrightText: 2010 Geoffry Song <goffrie@gmail.com>
4 * SPDX-FileCopyrightText: 2017 Scott Petrovic <scottpetrovic@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9#include <QXmlStreamReader>
12#include "kis_debug.h"
13#include "kis_dom_utils.h"
14#include <kis_canvas2.h>
15#include "kis_tool.h"
16#include "kis_config.h"
17
18#include <KoStore.h>
20
21#include <QGlobalStatic>
22#include <QPen>
23#include <QPainter>
24#include <QPixmapCache>
25#include <QDomElement>
26#include <QDomDocument>
27#include <QPainterPath>
28#include <QDebug>
29#include <memory>
30
32
37
38KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private)
39{
40}
41
45
47 : QPointF(rhs)
48 , KisShared()
49 , d(new Private)
50{
51 dbgUI << "KisPaintingAssistantHandle ctor";
52}
53
55{
56 setX(pt.x());
57 setY(pt.y());
58 return *this;
59}
60
62{
63 d->handle_type = type;
64}
65
67{
68 return d->handle_type;
69}
70
72{
73 return !d->assistants.isEmpty() ? d->assistants.first() : 0;
74}
75
77{
78 Q_ASSERT(d->assistants.empty());
79 delete d;
80}
81
83{
84 Q_ASSERT(!d->assistants.contains(assistant));
85 d->assistants.append(assistant);
86}
87
89{
90 d->assistants.removeOne(assistant);
91 Q_ASSERT(!d->assistants.contains(assistant));
92}
93
95{
96 return d->assistants.contains(assistant);
97}
98
100{
101 if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) {
102 return;
103 }
104
105
106 Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) {
107 if (!assistant->handles().contains(this)) {
108 assistant->replaceHandle(handle, this);
109 }
110 }
111}
112
114{
115 Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) {
116 assistant->uncache();
117 }
118}
119
121 Private();
122 explicit Private(const Private &rhs);
123 KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant = true);
125
127
128 // share everything except handles between the clones
129 struct SharedData {
130 QString id;
131 QString name;
132 bool isSnappingActive {true};
133 bool outlineVisible {true};
134 bool isLocal {false};
135 bool isLocked {false};
136 //The isDuplicating flag only exists to draw the duplicate button depressed when pressed
137 bool isDuplicating {false};
141
143
144 QPointF editorWidgetOffset {QPointF(0, 0)};
145
146 QPixmapCache::Key cached;
147 QRect cachedRect; // relative to boundingRect().topLeft()
148
150 qreal m11 {0.0};
151 qreal m12 {0.0};
152 qreal m21 {0.0};
153 qreal m22 {0.0};
154
156 TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { }
158 return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22;
159 }
161
162 QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set
163
164 bool useCustomColor {false};
166 };
167
169
170
171 const int previewLineWidth {1};
172 const int mainLineWidth {2}; // for "drawPath" etc.
173 const int errorLineWidth {2};
174
176
177};
178
183
185 : s(rhs.s)
186{
187}
188
190{
191 // Clones do not get a copy of the shared data, so this function is necessary to copy
192 // the SharedData struct from the old assistant to this one. The function returns a
193 // reference to a new SharedData object copied from the original
196 *this->d->s = *sd;
197}
198
199KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant)
200{
201 KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle);
202 if (!mappedHandle) {
203 if (origHandle) {
204 dbgUI << "handle not found in the map, creating a new one...";
205 mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle));
206 dbgUI << "done";
207 mappedHandle->setType(origHandle->handleType());
208 handleMap.insert(origHandle, mappedHandle);
209 } else {
210 dbgUI << "orig handle is null, not doing anything";
211 mappedHandle = KisPaintingAssistantHandleSP();
212 }
213 }
214 if (mappedHandle && registerAssistant) {
215 mappedHandle->registerAssistant(q);
216 }
217 return mappedHandle;
218}
219
221{
222 return d->s->useCustomColor;
223}
224
226{
227 d->s->useCustomColor = useCustomColor;
228}
229
231{
232 d->s->assistantCustomColor = color;
233}
234
236{
237 return d->s->assistantCustomColor;
238}
239
241{
242 d->s->assistantGlobalColorCache = color;
243}
244
246{
247 return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache;
248}
249
250KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private)
251{
252 d->s->id = id;
253 d->s->name = name;
254 d->s->isSnappingActive = true;
255 d->s->outlineVisible = true;
256}
257
259 const KisPaintingAssistant &rhs,
260 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> &handleMap)
261 : m_hasBeenInsideLocalRect(rhs.m_hasBeenInsideLocalRect)
262 , d(new Private(*(rhs.d)))
263{
264 dbgUI << "creating handles...";
265 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) {
266 d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this);
267 }
268 Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) {
269 d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this);
270 }
271#define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, /* registerAssistant = */ false)
280#undef _REUSE_H
281 dbgUI << "done";
282}
283
285{
286 return d->s->isSnappingActive;
287}
288
290{
291 d->s->isSnappingActive = set;
292}
293
295{
296 d->s->adjustedPositionValid = false;
297 d->s->followBrushPosition = false;
299}
300
302{
303 d->s->adjustedBrushPosition = position;
304 d->s->adjustedPositionValid = true;
305}
306
308{
309 d->s->followBrushPosition = follow;
310}
311
313{
314 return getDefaultEditorPosition() + d->s->editorWidgetOffset;
315}
316
318{
319 return false;
320}
321
323{
324 return d->s->isLocal;
325}
326
328{
329 d->s->isLocal = value;
330}
331
333{
334 return d->s->isLocked;
335}
336
338{
339 d->s->isLocked = value;
340}
341
343{
344 d->s->isDuplicating = value;
345}
346
348{
349 return d->s->isDuplicating;
350}
351
353{
354 return d->s->editorWidgetOffset;
355}
356
358{
359 d->s->editorWidgetOffset = offset;
360}
361
362
363void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface, bool isSnappingOn)
364{
365
366 QColor paintingColor = displayRenderInterface->convertColorToDisplayColorSpace(KoColor(effectiveAssistantColor(), KoColorSpaceRegistry::instance()->rgb8()));;
367
368 if (!isSnappingOn) {
369 paintingColor.setAlpha(0.2 * paintingColor.alpha());
370 }
371
372 painter.save();
373 QPen pen_a(paintingColor, d->mainLineWidth * d->decorationThickness);
374 pen_a.setCosmetic(true);
375 painter.setPen(pen_a);
376 painter.drawPath(path);
377 painter.restore();
378}
379
380void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface)
381{
382 painter.save();
384 pen_a.setStyle(Qt::SolidLine);
385 pen_a.setCosmetic(true);
386 painter.setPen(pen_a);
387 painter.drawPath(path);
388 painter.restore();
389}
390
391void KisPaintingAssistant::drawError(QPainter &painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface)
392{
393 painter.save();
394 const QColor error = displayRenderInterface->convertColorToDisplayColorSpace(KoColor(QColor(255, 0, 0, 125), KoColorSpaceRegistry::instance()->rgb8()));
395 QPen pen_a(error, d->errorLineWidth * d->decorationThickness);
396 pen_a.setCosmetic(true);
397 painter.setPen(pen_a);
398 painter.drawPath(path);
399 painter.restore();
400}
401
402void KisPaintingAssistant::drawX(QPainter &painter, const QPointF &pt, const KoColorDisplayRendererInterface *displayRenderInterface)
403{
404 QPainterPath path;
405 path.moveTo(QPointF(pt.x() - 5.0, pt.y() - 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() + 5.0));
406 path.moveTo(QPointF(pt.x() - 5.0, pt.y() + 5.0)); path.lineTo(QPointF(pt.x() + 5.0, pt.y() - 5.0));
407 drawPath(painter, path, displayRenderInterface);
408}
409
411{
412 Q_ASSERT(d->handles.isEmpty());
413 d->handles = _handles;
414 Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) {
415 handle->registerAssistant(this);
416 }
417}
418
420{
421 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) {
422 handle->unregisterAssistant(this);
423 }
424 if(!d->sideHandles.isEmpty()) {
425 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
426 handle->unregisterAssistant(this);
427 }
428 }
429 delete d;
430}
431
432const QString& KisPaintingAssistant::id() const
433{
434 return d->s->id;
435}
436
437const QString& KisPaintingAssistant::name() const
438{
439 return d->s->name;
440}
441
443{
444 Q_ASSERT(d->handles.contains(_handle));
445 d->handles.replace(d->handles.indexOf(_handle), _with);
446 Q_ASSERT(!d->handles.contains(_handle));
447 _handle->unregisterAssistant(this);
448 _with->registerAssistant(this);
449}
450
452{
453 Q_ASSERT(!d->handles.contains(handle));
454 if (HandleType::SIDE == type) {
455 d->sideHandles.append(handle);
456 } else {
457 d->handles.append(handle);
458 }
459
460 handle->registerAssistant(this);
461 handle.data()->setType(type);
462}
463
465{
466 QPointF editorDocumentPos = getEditorPosition();
467 QPointF editorWidgetPos = converter->documentToWidgetTransform().map(editorDocumentPos);
468 QSizeF canvasSize = converter->getCanvasWidgetSize();
469 const int padding = 16;
470
471 editorWidgetPos.rx() = qBound(0.0,
472 editorWidgetPos.x(),
473 canvasSize.width() - (editorSize.width() + padding));
474 editorWidgetPos.ry() = qBound(0.0,
475 editorWidgetPos.y(),
476 canvasSize.height() - (editorSize.height() + padding));
477
478 return converter->widgetToDocument(editorWidgetPos);
479}
480
481void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible)
482{
483 Q_UNUSED(updateRect);
484
485 Q_UNUSED(previewVisible);
486
488
489 if (!useCache) {
490 gc.save();
491 drawCache(gc, converter, displayRenderInterface, assistantVisible);
492 gc.restore();
493 return;
494 }
495
496 const QRect bound = boundingRect();
497 if (bound.isEmpty()) {
498 return;
499 }
500
501 const QTransform transform = converter->documentToWidgetTransform();
502 const QRect widgetBound = transform.mapRect(bound);
503
504 const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport());
505 if (paintRect.isEmpty()) return;
506
507 QPixmap cached;
508 bool found = QPixmapCache::find(d->s->cached, &cached);
509
510 if (!(found &&
511 d->s->cachedTransform == transform &&
512 d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) {
513
514 const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound);
515 Q_ASSERT(!cacheRect.isEmpty());
516
517 if (cached.isNull() || cached.size() != cacheRect.size()) {
518 cached = QPixmap(cacheRect.size());
519 }
520
521 cached.fill(Qt::transparent);
522 QPainter painter(&cached);
523 painter.setRenderHint(QPainter::Antialiasing);
524 painter.setWindow(cacheRect);
525 drawCache(painter, converter, displayRenderInterface, assistantVisible);
526 painter.end();
527 d->s->cachedTransform = transform;
528 d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft());
529 d->s->cached = QPixmapCache::insert(cached);
530 }
531
532 gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft()));
533
534
535 if (canvas) {
536 d->s->m_canvas = canvas;
537 }
538}
539
541{
542 d->s->cached = QPixmapCache::Key();
543}
544
546{
547 QRectF r;
548 Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) {
549 r = r.united(QRectF(*h, QSizeF(1,1)));
550 }
551 return r.adjusted(-2, -2, 2, 2).toAlignedRect();
552}
553
555{
556 return true;
557}
558
559void KisPaintingAssistant::transform(const QTransform &transform)
560{
561 Q_FOREACH(KisPaintingAssistantHandleSP handle, handles()) {
562 if (handle->chiefAssistant() != this) continue;
563
564 *handle = transform.map(*handle);
565 }
566
567 Q_FOREACH(KisPaintingAssistantHandleSP handle, sideHandles()) {
568 if (handle->chiefAssistant() != this) continue;
569
570 *handle = transform.map(*handle);
571 }
572
573 uncache();
574}
575
576QByteArray KisPaintingAssistant::saveXml(QMap<KisPaintingAssistantHandleSP, int> &handleMap)
577{
578 QByteArray data;
579 QXmlStreamWriter xml(&data);
580 xml.writeStartDocument();
581 xml.writeStartElement("assistant");
582 xml.writeAttribute("type",d->s->id);
583 xml.writeAttribute("active", QString::number(d->s->isSnappingActive));
584 xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor));
585 xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor));
586 xml.writeAttribute("locked", QString::number(d->s->isLocked));
587 xml.writeAttribute("editorWidgetOffset_X", QString::number((double)(d->s->editorWidgetOffset.x()), 'f', 3));
588 xml.writeAttribute("editorWidgetOffset_Y", QString::number((double)(d->s->editorWidgetOffset.y()), 'f', 3));
589
590
591
592 saveCustomXml(&xml); // if any specific assistants have custom XML data to save to
593
594 // write individual handle data
595 xml.writeStartElement("handles");
596 Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) {
597 int id = handleMap.size();
598 if (!handleMap.contains(handle)){
599 handleMap.insert(handle, id);
600 }
601 id = handleMap.value(handle);
602 xml.writeStartElement("handle");
603 xml.writeAttribute("id", QString::number(id));
604 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
605 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
606 xml.writeEndElement();
607 }
608 xml.writeEndElement();
609 if (!d->sideHandles.isEmpty()) { // for vanishing points only
610 xml.writeStartElement("sidehandles");
611 QMap<KisPaintingAssistantHandleSP, int> sideHandleMap;
612 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
613 int id = sideHandleMap.size();
614 sideHandleMap.insert(handle, id);
615 xml.writeStartElement("sidehandle");
616 xml.writeAttribute("id", QString::number(id));
617 xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3));
618 xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3));
619 xml.writeEndElement();
620 }
621 }
622
623 xml.writeEndElement();
624 xml.writeEndDocument();
625 return data;
626}
627
628void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml)
629{
630 Q_UNUSED(xml);
631}
632
633void KisPaintingAssistant::loadXml(KoStore* store, QMap<int, KisPaintingAssistantHandleSP> &handleMap, QString path)
634{
635 int id = 0;
636 double x = 0.0, y = 0.0;
637 store->open(path);
638 QByteArray data = store->read(store->size());
639 QXmlStreamReader xml(data);
640 QMap<int, KisPaintingAssistantHandleSP> sideHandleMap;
641 while (!xml.atEnd()) {
642 switch (xml.readNext()) {
643 case QXmlStreamReader::StartElement:
644 if (xml.name() == "assistant") {
645
646 auto active = xml.attributes().value("active");
647 setSnappingActive( (active != "0") );
648
649 // load custom shared assistant properties
650 if ( xml.attributes().hasAttribute("useCustomColor")) {
651 auto useCustomColor = xml.attributes().value("useCustomColor");
652
653 bool usingColor = false;
654 if (useCustomColor.toString() == "1") {
655 usingColor = true;
656 }
657
658
659 setUseCustomColor(usingColor);
660 }
661
662 if (xml.attributes().hasAttribute("editorWidgetOffset_X") && xml.attributes().hasAttribute("editorWidgetOffset_Y")) {
663 setEditorWidgetOffset(QPointF(xml.attributes().value("editorWidgetOffset_X").toDouble(), xml.attributes().value("editorWidgetOffset_Y").toDouble()));
664 }
665
666 if ( xml.attributes().hasAttribute("customColor")) {
667 auto customColor = xml.attributes().value("customColor");
668 setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) );
669
670 }
671
672 if ( xml.attributes().hasAttribute("locked")) {
673 auto locked = xml.attributes().value("locked");
674 setLocked(locked == "1");
675 }
676
677 }
678
679 loadCustomXml(&xml);
680
681 if (xml.name() == "handle") {
682 QString strId = xml.attributes().value("id").toString(),
683 strX = xml.attributes().value("x").toString(),
684 strY = xml.attributes().value("y").toString();
685 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
686 id = strId.toInt();
687 x = strX.toDouble();
688 y = strY.toDouble();
689 if (!handleMap.contains(id)) {
690 handleMap.insert(id, new KisPaintingAssistantHandle(x, y));
691 }
692 }
693 addHandle(handleMap.value(id), HandleType::NORMAL);
694 } else if (xml.name() == "sidehandle") {
695 QString strId = xml.attributes().value("id").toString(),
696 strX = xml.attributes().value("x").toString(),
697 strY = xml.attributes().value("y").toString();
698 if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) {
699 id = strId.toInt();
700 x = strX.toDouble();
701 y = strY.toDouble();
702 if (!sideHandleMap.contains(id)) {
703 sideHandleMap.insert(id, new KisPaintingAssistantHandle(x, y));
704 }
705 }
706 addHandle(sideHandleMap.value(id), HandleType::SIDE);
707
708 }
709 break;
710 default:
711 break;
712 }
713 }
714 store->close();
715}
716
717bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml)
718{
719 Q_UNUSED(xml);
720 return true;
721}
722
723void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count)
724{
725 if (d->s->id == "ellipse"){
726 QDomElement assistantElement = doc.createElement("assistant");
727 assistantElement.setAttribute("type", "ellipse");
728 assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count));
729 assistantsElement.appendChild(assistantElement);
730 }
731 else if (d->s->id == "spline"){
732 QDomElement assistantElement = doc.createElement("assistant");
733 assistantElement.setAttribute("type", "spline");
734 assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count));
735 assistantsElement.appendChild(assistantElement);
736 }
737 else if (d->s->id == "perspective"){
738 QDomElement assistantElement = doc.createElement("assistant");
739 assistantElement.setAttribute("type", "perspective");
740 assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count));
741 assistantsElement.appendChild(assistantElement);
742 }
743 else if (d->s->id == "vanishing point"){
744 QDomElement assistantElement = doc.createElement("assistant");
745 assistantElement.setAttribute("type", "vanishing point");
746 assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count));
747 assistantsElement.appendChild(assistantElement);
748 }
749 else if (d->s->id == "infinite ruler"){
750 QDomElement assistantElement = doc.createElement("assistant");
751 assistantElement.setAttribute("type", "infinite ruler");
752 assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count));
753 assistantsElement.appendChild(assistantElement);
754 }
755 else if (d->s->id == "parallel ruler"){
756 QDomElement assistantElement = doc.createElement("assistant");
757 assistantElement.setAttribute("type", "parallel ruler");
758 assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count));
759 assistantsElement.appendChild(assistantElement);
760 }
761 else if (d->s->id == "concentric ellipse"){
762 QDomElement assistantElement = doc.createElement("assistant");
763 assistantElement.setAttribute("type", "concentric ellipse");
764 assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count));
765 assistantsElement.appendChild(assistantElement);
766 }
767 else if (d->s->id == "fisheye-point"){
768 QDomElement assistantElement = doc.createElement("assistant");
769 assistantElement.setAttribute("type", "fisheye-point");
770 assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count));
771 assistantsElement.appendChild(assistantElement);
772 }
773 else if (d->s->id == "ruler"){
774 QDomElement assistantElement = doc.createElement("assistant");
775 assistantElement.setAttribute("type", "ruler");
776 assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count));
777 assistantsElement.appendChild(assistantElement);
778 }
779 else if (d->s->id == "two point"){
780 QDomElement assistantElement = doc.createElement("assistant");
781 assistantElement.setAttribute("type", "two point");
782 assistantElement.setAttribute("filename", QString("two point%1.assistant").arg(count));
783 assistantsElement.appendChild(assistantElement);
784 }
785 else if (d->s->id == "perspective ellipse"){
786 QDomElement assistantElement = doc.createElement("assistant");
787 assistantElement.setAttribute("type", "perspective ellipse");
788 assistantElement.setAttribute("filename", QString("perspective ellipse%1.assistant").arg(count));
789 assistantsElement.appendChild(assistantElement);
790 }
791 else if (d->s->id == "curvilinear-perspective"){
792 QDomElement assistantElement = doc.createElement("assistant");
793 assistantElement.setAttribute("type", "curvilinear-perspective");
794 assistantElement.setAttribute("filename", QString("curvilinear-perspective%1.assistant").arg(count));
795 assistantsElement.appendChild(assistantElement);
796 }
797}
798
802 uint vHole = 0,hHole = 0;
804 if (d->handles.size() == 4 && d->s->id == "perspective") {
805 //get the handle opposite to the first handle
806 oppHandle = oppHandleOne();
807 //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively.
808 Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) {
809 hHandlesList.append(handle);
810 hHole = hHandlesList.size() - 1;
811 vHandlesList.append(handle);
812 vHole = vHandlesList.size() - 1;
813 /*
814 sort handles on the basis of X-coordinate
815 */
816 while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) {
817 hHandlesList.swapItemsAt(hHole - 1, hHole);
818 hHole = hHole - 1;
819 }
820 /*
821 sort handles on the basis of Y-coordinate
822 */
823 while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) {
824 vHandlesList.swapItemsAt(vHole-1, vHole);
825 vHole = vHole - 1;
826 }
827 }
828
829 /*
830 give the handles their respective positions
831 */
832 if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) {
833 d->topLeft = vHandlesList.at(1);
834 d->topRight= vHandlesList.at(0);
835 }
836 else {
837 d->topLeft = vHandlesList.at(0);
838 d->topRight = vHandlesList.at(1);
839 }
840 if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) {
841 d->bottomLeft = vHandlesList.at(3);
842 d->bottomRight = vHandlesList.at(2);
843 }
844 else {
845 d->bottomLeft= vHandlesList.at(2);
846 d->bottomRight = vHandlesList.at(3);
847 }
848
849 /*
850 find if the handles that should be opposite are actually oppositely positioned
851 */
852 if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) ||
853 (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) ||
854 (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) ||
855 (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) )
856 {}
857 else {
858 if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) {
859 d->topLeft = hHandlesList.at(1);
860 d->bottomLeft= hHandlesList.at(0);
861 }
862 else {
863 d->topLeft = hHandlesList.at(0);
864 d->bottomLeft = hHandlesList.at(1);
865 }
866 if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) {
867 d->topRight = hHandlesList.at(3);
868 d->bottomRight = hHandlesList.at(2);
869 }
870 else {
871 d->topRight= hHandlesList.at(2);
872 d->bottomRight = hHandlesList.at(3);
873 }
874
875 }
876 /*
877 Setting the middle handles as needed
878 */
879 if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) {
880
881 // Before re-adding the handles, clear old ones that have been
882 // potentially loaded from disk and not re-associated with the
883 // xxxMiddle pointers in d; otherwise those would stay in place.
884 if(!d->sideHandles.isEmpty()) {
885 Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) {
886 handle->unregisterAssistant(this);
887 }
888 d->sideHandles.clear();
889 }
890
892 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5);
893 d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
894 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5);
896 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5);
897 d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
898 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5);
899
904 }
905 else
906 {
907 d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5,
908 (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5));
909 d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5,
910 (d->topLeft.data()->y() + d->topRight.data()->y())*0.5));
911 d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5,
912 (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5));
913 d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5,
914 (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5));
915 }
916
917 }
918}
919
921{
922 QPointF intersection(0,0);
923 if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersects(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
924 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(1).data()->toPoint()).intersects(QLineF(d->handles.at(2).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
925 {
926 return d->handles.at(1);
927 }
928 else if((QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersects(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::NoIntersection)
929 && (QLineF(d->handles.at(0).data()->toPoint(),d->handles.at(2).data()->toPoint()).intersects(QLineF(d->handles.at(1).data()->toPoint(),d->handles.at(3).data()->toPoint()), &intersection) != QLineF::UnboundedIntersection))
930 {
931 return d->handles.at(2);
932 }
933 else
934 {
935 return d->handles.at(3);
936 }
937}
938
943
948
953
958
963
968
973
978
983
988
993
998
1003
1008
1013
1018
1023
1028
1033
1038
1039
1040
1041bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo)
1042{
1043 int m_handleSize = 16;
1044
1045 QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize));
1046 return handlerect.contains(pointOne);
1047}
1048
1050{
1051 if (!d->s->m_canvas) {
1052 return 0;
1053 }
1054
1055
1056 if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) {
1057 return topLeft();
1058 } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) {
1059 return topRight();
1060 } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) {
1061 return bottomLeft();
1062 } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) {
1063 return bottomRight();
1064 }
1065 return 0;
1066}
1067
1068
1069QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const
1070{
1071 QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords);
1072 return d->s->m_canvas->viewConverter()->documentToView(documentCoord);
1073}
1074
1076{
1077 QPointF mousePos;
1078
1079 if (d->s->followBrushPosition && d->s->adjustedPositionValid) {
1080 mousePos = converter->documentToWidget(d->s->adjustedBrushPosition);
1081 } else if (canvas) {
1082 // FIXME: this may be simple and cheap, but it's only integer precision!
1083 mousePos= canvas->canvasWidget()->mapFromGlobal(QCursor::pos());
1084 } else {
1085 //...of course, you need to have access to a canvas-widget for that.//
1086 mousePos = QCursor::pos(); //this'll give an offset//
1087 dbgUI << "no canvas given for assistant, you may have passed arguments incorrectly:";
1088 }
1089 return mousePos;
1090}
1091
1096
1101
1103{
1104 if (!isLocal() || !firstLocalHandle() || !secondLocalHandle()) {
1105 return QRectF();
1106 }
1107
1110
1111 QPointF topLeft = QPointF(qMin(first->x(), second->x()), qMin(first->y(), second->y()));
1112 QPointF bottomRight = QPointF(qMax(first->x(), second->x()), qMax(first->y(), second->y()));
1113
1114 QRectF rect(topLeft, bottomRight);
1115 return rect;
1116}
1117
1118double KisPaintingAssistant::norm2(const QPointF& p)
1119{
1120 return p.x() * p.x() + p.y() * p.y();
1121}
1122
1124{
1125 d->decorationThickness = thickness;
1126}
1127
1129{
1130 QMap<KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP> handleMap;
1132 for (auto i = list.begin(); i != list.end(); ++i) {
1133 clonedList << (*i)->clone(handleMap);
1134 }
1135 return clonedList;
1136}
1137
1138
1139
1140/*
1141 * KisPaintingAssistantFactory classes
1142*/
1143
1147
1151
1155
1157{
1158 Q_FOREACH (const QString &id, keys()) {
1159 delete get(id);
1160 }
1161 dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry ";
1162}
1163
1168
float value(const T *src, size_t ch)
const Params2D p
Q_GLOBAL_STATIC(KisStoragePluginRegistry, s_instance)
unsigned int uint
KisAbstractCanvasWidget * canvasWidget
QColor defaultAssistantsColor(bool defaultValue=false) const
_Private::Traits< T >::Result widgetToDocument(const T &obj) const
_Private::Traits< T >::Result documentToWidget(const T &obj) const
static KisPaintingAssistantFactoryRegistry * instance()
void mergeWith(KisPaintingAssistantHandleSP)
void registerAssistant(KisPaintingAssistant *)
void unregisterAssistant(KisPaintingAssistant *)
KisPaintingAssistant * chiefAssistant() const
KisPaintingAssistantHandle(double x, double y)
bool containsAssistant(KisPaintingAssistant *) const
KisPaintingAssistantHandle & operator=(const QPointF &)
virtual QRect boundingRect() const
virtual KisPaintingAssistantHandleSP secondLocalHandle() const
secondLocalHandle Note: this doesn't guarantee it will be the bottomRight corner! For that,...
void drawError(QPainter &painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface)
const KisPaintingAssistantHandleSP topMiddle() const
static QList< KisPaintingAssistantSP > cloneAssistantList(const QList< KisPaintingAssistantSP > &list)
QPointF effectiveBrushPosition(const KisCoordinatesConverter *converter, KisCanvas2 *canvas) const
Query the effective brush position to be used for preview lines. This is intended to be used for pain...
bool isDuplicating()
isDuplicating
void setDecorationThickness(int thickness)
void setLocked(bool value)
setLocked
void setDuplicating(bool value)
setDuplicating
virtual bool isAssistantComplete() const
void addHandle(KisPaintingAssistantHandleSP handle, HandleType type)
virtual QPointF getDefaultEditorPosition() const =0
void drawPreview(QPainter &painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface)
void drawX(QPainter &painter, const QPointF &pt, const KoColorDisplayRendererInterface *displayRenderInterface)
virtual void transform(const QTransform &transform)
virtual void drawAssistant(QPainter &gc, const QRectF &updateRect, const KisCoordinatesConverter *converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool cached, KisCanvas2 *canvas=0, bool assistantVisible=true, bool previewVisible=true)
const KisPaintingAssistantHandleSP bottomMiddle() const
virtual void saveCustomXml(QXmlStreamWriter *xml)
void drawPath(QPainter &painter, const QPainterPath &path, const KoColorDisplayRendererInterface *displayRenderInterface, bool drawActive=true)
void setAssistantGlobalColorCache(const QColor &color)
KisPaintingAssistantHandleSP topLeft()
const QString & name() const
void copySharedData(KisPaintingAssistantSP assistant)
const KisPaintingAssistantHandleSP bottomRight() const
QRectF getLocalRect() const
getLocalRect The function deals with local handles not being topLeft and bottomRight gracefully and r...
const KisPaintingAssistantHandleSP topLeft() const
virtual void setAdjustedBrushPosition(const QPointF position)
const QList< KisPaintingAssistantHandleSP > & sideHandles() const
KisPaintingAssistantHandleSP closestCornerHandleFromPoint(QPointF point)
virtual bool canBeLocal() const
canBeLocal
virtual void drawCache(QPainter &gc, const KisCoordinatesConverter *converter, const KoColorDisplayRendererInterface *displayRenderInterface, bool assistantVisible=true)=0
performance layer where the graphics can be drawn from a cache instead of generated every render upda...
void saveXmlList(QDomDocument &doc, QDomElement &assistantsElement, int count)
static double norm2(const QPointF &p)
void setEditorWidgetOffset(QPointF offset)
const KisPaintingAssistantHandleSP bottomLeft() const
void setAssistantCustomColor(QColor color)
QPointF pixelToView(const QPoint pixelCoords) const
virtual bool loadCustomXml(QXmlStreamReader *xml)
virtual QPointF getEditorPosition() const
virtual KisPaintingAssistantHandleSP firstLocalHandle() const
firstLocalHandle Note: this doesn't guarantee it will be the topleft corner! For that,...
const KisPaintingAssistantHandleSP rightMiddle() const
void setLocal(bool value)
setLocal
KisPaintingAssistant(const QString &id, const QString &name)
virtual void setFollowBrushPosition(bool follow)
const QString & id() const
void loadXml(KoStore *store, QMap< int, KisPaintingAssistantHandleSP > &handleMap, QString path)
QPointF viewportConstrainedEditorPosition(const KisCoordinatesConverter *converter, const QSize editorSize)
const QList< KisPaintingAssistantHandleSP > & handles() const
void setUseCustomColor(bool useCustomColor)
void replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with)
KisPaintingAssistantHandleSP oppHandleOne()
bool areTwoPointsClose(const QPointF &pointOne, const QPointF &pointTwo)
const KisPaintingAssistantHandleSP leftMiddle() const
QByteArray saveXml(QMap< KisPaintingAssistantHandleSP, int > &handleMap)
const KisPaintingAssistantHandleSP topRight() const
KisPaintingAssistantHandleSP bottomRight()
void initHandles(QList< KisPaintingAssistantHandleSP > _handles)
virtual QColor convertColorToDisplayColorSpace(const KoColor color) const =0
convertColorToDisplayColorSpace
KisPaintingAssistantFactory * get(const QString &id) const
bool close()
Definition KoStore.cpp:156
qint64 size() const
Definition KoStore.cpp:239
bool open(const QString &name)
Definition KoStore.cpp:109
QByteArray read(qint64 max)
Definition KoStore.cpp:181
#define dbgUI
Definition kis_debug.h:52
#define dbgRegistry
Definition kis_debug.h:47
#define _REUSE_H(name)
KisSharedPtr< KisPaintingAssistantHandle > KisPaintingAssistantHandleSP
QString qColorToQString(QColor color)
QColor qStringToQColor(QString colorString)
QList< KisPaintingAssistant * > assistants
struct KisPaintingAssistant::Private::SharedData::TranslationInvariantTransform cachedTransform
KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap< KisPaintingAssistantHandleSP, KisPaintingAssistantHandleSP > &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant=true)
QList< KisPaintingAssistantHandleSP > handles
KisPaintingAssistantHandleSP bottomLeft
QList< KisPaintingAssistantHandleSP > sideHandles
KisPaintingAssistantHandleSP topRight
KisPaintingAssistantHandleSP topMiddle
QSharedPointer< SharedData > s
KisPaintingAssistantHandleSP rightMiddle
KisPaintingAssistantHandleSP leftMiddle
KisPaintingAssistantHandleSP topLeft
KisPaintingAssistantHandleSP bottomRight
KisPaintingAssistantHandleSP bottomMiddle
static KoColorSpaceRegistry * instance()