diff --git a/libs/ui/kis_painting_assistant.cc b/libs/ui/kis_painting_assistant.cc index 4bfc564bc4..ebadb81628 100644 --- a/libs/ui/kis_painting_assistant.cc +++ b/libs/ui/kis_painting_assistant.cc @@ -1,971 +1,977 @@ /* * Copyright (c) 2008,2011 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2017 Scott Petrovic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include "kis_painting_assistant.h" #include "kis_coordinates_converter.h" #include "kis_debug.h" #include "kis_dom_utils.h" #include #include "kis_tool.h" #include "kis_config.h" #include #include #include #include #include #include #include Q_GLOBAL_STATIC(KisPaintingAssistantFactoryRegistry, s_instance) struct KisPaintingAssistantHandle::Private { QList assistants; char handle_type; }; KisPaintingAssistantHandle::KisPaintingAssistantHandle(double x, double y) : QPointF(x, y), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(QPointF p) : QPointF(p), d(new Private) { } KisPaintingAssistantHandle::KisPaintingAssistantHandle(const KisPaintingAssistantHandle& rhs) : QPointF(rhs) , KisShared() , d(new Private) { dbgUI << "KisPaintingAssistantHandle ctor"; } KisPaintingAssistantHandle& KisPaintingAssistantHandle::operator=(const QPointF & pt) { setX(pt.x()); setY(pt.y()); return *this; } void KisPaintingAssistantHandle::setType(char type) { d->handle_type = type; } char KisPaintingAssistantHandle::handleType() const { return d->handle_type; } KisPaintingAssistant *KisPaintingAssistantHandle::chiefAssistant() const { return !d->assistants.isEmpty() ? d->assistants.first() : 0; } KisPaintingAssistantHandle::~KisPaintingAssistantHandle() { Q_ASSERT(d->assistants.empty()); delete d; } void KisPaintingAssistantHandle::registerAssistant(KisPaintingAssistant* assistant) { Q_ASSERT(!d->assistants.contains(assistant)); d->assistants.append(assistant); } void KisPaintingAssistantHandle::unregisterAssistant(KisPaintingAssistant* assistant) { d->assistants.removeOne(assistant); Q_ASSERT(!d->assistants.contains(assistant)); } bool KisPaintingAssistantHandle::containsAssistant(KisPaintingAssistant* assistant) const { return d->assistants.contains(assistant); } void KisPaintingAssistantHandle::mergeWith(KisPaintingAssistantHandleSP handle) { if(this->handleType()== HandleType::NORMAL || handle.data()->handleType()== HandleType::SIDE) { return; } Q_FOREACH (KisPaintingAssistant* assistant, handle->d->assistants) { if (!assistant->handles().contains(this)) { assistant->replaceHandle(handle, this); } } } void KisPaintingAssistantHandle::uncache() { Q_FOREACH (KisPaintingAssistant* assistant, d->assistants) { assistant->uncache(); } } struct KisPaintingAssistant::Private { Private(); explicit Private(const Private &rhs); KisPaintingAssistantHandleSP reuseOrCreateHandle(QMap &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant = true); QList handles, sideHandles; KisPaintingAssistantHandleSP topLeft, bottomLeft, topRight, bottomRight, topMiddle, bottomMiddle, rightMiddle, leftMiddle; // share everything except handles between the clones struct SharedData { QString id; QString name; bool isSnappingActive; bool outlineVisible; KisCanvas2* m_canvas = 0; QPixmapCache::Key cached; QRect cachedRect; // relative to boundingRect().topLeft() struct TranslationInvariantTransform { qreal m11, m12, m21, m22; TranslationInvariantTransform() { } TranslationInvariantTransform(const QTransform& t) : m11(t.m11()), m12(t.m12()), m21(t.m21()), m22(t.m22()) { } bool operator==(const TranslationInvariantTransform& b) { return m11 == b.m11 && m12 == b.m12 && m21 == b.m21 && m22 == b.m22; } } cachedTransform; QColor assistantGlobalColorCache = QColor(Qt::red); // color to paint with if a custom color is not set bool useCustomColor = false; QColor assistantCustomColor = KisConfig(true).defaultAssistantsColor(); }; QSharedPointer s; }; KisPaintingAssistant::Private::Private() : s(new SharedData) { } KisPaintingAssistant::Private::Private(const Private &rhs) : s(rhs.s) { } KisPaintingAssistantHandleSP KisPaintingAssistant::Private::reuseOrCreateHandle(QMap &handleMap, KisPaintingAssistantHandleSP origHandle, KisPaintingAssistant *q, bool registerAssistant) { KisPaintingAssistantHandleSP mappedHandle = handleMap.value(origHandle); if (!mappedHandle) { if (origHandle) { dbgUI << "handle not found in the map, creating a new one..."; mappedHandle = KisPaintingAssistantHandleSP(new KisPaintingAssistantHandle(*origHandle)); dbgUI << "done"; mappedHandle->setType(origHandle->handleType()); handleMap.insert(origHandle, mappedHandle); } else { dbgUI << "orig handle is null, not doing anything"; mappedHandle = KisPaintingAssistantHandleSP(); } } if (mappedHandle && registerAssistant) { mappedHandle->registerAssistant(q); } return mappedHandle; } bool KisPaintingAssistant::useCustomColor() { return d->s->useCustomColor; } void KisPaintingAssistant::setUseCustomColor(bool useCustomColor) { d->s->useCustomColor = useCustomColor; } void KisPaintingAssistant::setAssistantCustomColor(QColor color) { d->s->assistantCustomColor = color; } QColor KisPaintingAssistant::assistantCustomColor() { return d->s->assistantCustomColor; } void KisPaintingAssistant::setAssistantGlobalColorCache(const QColor &color) { d->s->assistantGlobalColorCache = color; } QColor KisPaintingAssistant::effectiveAssistantColor() const { return d->s->useCustomColor ? d->s->assistantCustomColor : d->s->assistantGlobalColorCache; } KisPaintingAssistant::KisPaintingAssistant(const QString& id, const QString& name) : d(new Private) { d->s->id = id; d->s->name = name; d->s->isSnappingActive = true; d->s->outlineVisible = true; } KisPaintingAssistant::KisPaintingAssistant(const KisPaintingAssistant &rhs, QMap &handleMap) : d(new Private(*(rhs.d))) { dbgUI << "creating handles..."; Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->handles) { d->handles << d->reuseOrCreateHandle(handleMap, origHandle, this); } Q_FOREACH (const KisPaintingAssistantHandleSP origHandle, rhs.d->sideHandles) { d->sideHandles << d->reuseOrCreateHandle(handleMap, origHandle, this); } #define _REUSE_H(name) d->name = d->reuseOrCreateHandle(handleMap, rhs.d->name, this, /* registerAssistant = */ false) _REUSE_H(topLeft); _REUSE_H(bottomLeft); _REUSE_H(topRight); _REUSE_H(bottomRight); _REUSE_H(topMiddle); _REUSE_H(bottomMiddle); _REUSE_H(rightMiddle); _REUSE_H(leftMiddle); #undef _REUSE_H dbgUI << "done"; } bool KisPaintingAssistant::isSnappingActive() const { return d->s->isSnappingActive; } void KisPaintingAssistant::setSnappingActive(bool set) { d->s->isSnappingActive = set; } void KisPaintingAssistant::drawPath(QPainter& painter, const QPainterPath &path, bool isSnappingOn) { QColor paintingColor = effectiveAssistantColor(); if (!isSnappingOn) { paintingColor.setAlpha(0.2 * paintingColor.alpha()); } painter.save(); QPen pen_a(paintingColor, 2); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::drawPreview(QPainter& painter, const QPainterPath &path) { painter.save(); QPen pen_a(effectiveAssistantColor(), 1); pen_a.setStyle(Qt::SolidLine); pen_a.setCosmetic(true); painter.setPen(pen_a); painter.drawPath(path); painter.restore(); } void KisPaintingAssistant::initHandles(QList _handles) { Q_ASSERT(d->handles.isEmpty()); d->handles = _handles; Q_FOREACH (KisPaintingAssistantHandleSP handle, _handles) { handle->registerAssistant(this); } } KisPaintingAssistant::~KisPaintingAssistant() { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->handles) { handle->unregisterAssistant(this); } if(!d->sideHandles.isEmpty()) { Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { handle->unregisterAssistant(this); } } delete d; } const QString& KisPaintingAssistant::id() const { return d->s->id; } const QString& KisPaintingAssistant::name() const { return d->s->name; } void KisPaintingAssistant::replaceHandle(KisPaintingAssistantHandleSP _handle, KisPaintingAssistantHandleSP _with) { Q_ASSERT(d->handles.contains(_handle)); d->handles.replace(d->handles.indexOf(_handle), _with); Q_ASSERT(!d->handles.contains(_handle)); _handle->unregisterAssistant(this); _with->registerAssistant(this); } void KisPaintingAssistant::addHandle(KisPaintingAssistantHandleSP handle, HandleType type) { Q_ASSERT(!d->handles.contains(handle)); if (HandleType::SIDE == type) { d->sideHandles.append(handle); } else { d->handles.append(handle); } handle->registerAssistant(this); handle.data()->setType(type); } QPointF KisPaintingAssistant::viewportConstrainedEditorPosition(const KisCoordinatesConverter* converter, const QSize editorSize) { QPointF editorDocumentPos = getEditorPosition(); QPointF editorWidgetPos = converter->documentToWidgetTransform().map(editorDocumentPos); QSizeF canvasSize = converter->getCanvasWidgetSize(); const int padding = 16; editorWidgetPos.rx() = qBound(0.0, editorWidgetPos.x(), canvasSize.width() - (editorSize.width() + padding)); editorWidgetPos.ry() = qBound(0.0, editorWidgetPos.y(), canvasSize.height() - (editorSize.height() + padding)); return converter->widgetToDocument(editorWidgetPos); } void KisPaintingAssistant::drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool useCache, KisCanvas2* canvas, bool assistantVisible, bool previewVisible) { Q_UNUSED(updateRect); Q_UNUSED(previewVisible); findPerspectiveAssistantHandleLocation(); if (!useCache) { gc.save(); drawCache(gc, converter, assistantVisible); gc.restore(); return; } const QRect bound = boundingRect(); if (bound.isEmpty()) { return; } const QTransform transform = converter->documentToWidgetTransform(); const QRect widgetBound = transform.mapRect(bound); const QRect paintRect = transform.mapRect(bound).intersected(gc.viewport()); if (paintRect.isEmpty()) return; QPixmap cached; bool found = QPixmapCache::find(d->s->cached, &cached); if (!(found && d->s->cachedTransform == transform && d->s->cachedRect.translated(widgetBound.topLeft()).contains(paintRect))) { const QRect cacheRect = gc.viewport().adjusted(-100, -100, 100, 100).intersected(widgetBound); Q_ASSERT(!cacheRect.isEmpty()); if (cached.isNull() || cached.size() != cacheRect.size()) { cached = QPixmap(cacheRect.size()); } cached.fill(Qt::transparent); QPainter painter(&cached); painter.setRenderHint(QPainter::Antialiasing); painter.setWindow(cacheRect); drawCache(painter, converter, assistantVisible); painter.end(); d->s->cachedTransform = transform; d->s->cachedRect = cacheRect.translated(-widgetBound.topLeft()); d->s->cached = QPixmapCache::insert(cached); } gc.drawPixmap(paintRect, cached, paintRect.translated(-widgetBound.topLeft() - d->s->cachedRect.topLeft())); if (canvas) { d->s->m_canvas = canvas; } } void KisPaintingAssistant::uncache() { d->s->cached = QPixmapCache::Key(); } QRect KisPaintingAssistant::boundingRect() const { QRectF r; Q_FOREACH (KisPaintingAssistantHandleSP h, handles()) { r = r.united(QRectF(*h, QSizeF(1,1))); } return r.adjusted(-2, -2, 2, 2).toAlignedRect(); } bool KisPaintingAssistant::isAssistantComplete() const { return true; } void KisPaintingAssistant::transform(const QTransform &transform) { Q_FOREACH(KisPaintingAssistantHandleSP handle, handles()) { if (handle->chiefAssistant() != this) continue; *handle = transform.map(*handle); } Q_FOREACH(KisPaintingAssistantHandleSP handle, sideHandles()) { if (handle->chiefAssistant() != this) continue; *handle = transform.map(*handle); } uncache(); } QByteArray KisPaintingAssistant::saveXml(QMap &handleMap) { QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("assistant"); xml.writeAttribute("type",d->s->id); xml.writeAttribute("active", QString::number(d->s->isSnappingActive)); xml.writeAttribute("useCustomColor", QString::number(d->s->useCustomColor)); xml.writeAttribute("customColor", KisDomUtils::qColorToQString(d->s->assistantCustomColor)); saveCustomXml(&xml); // if any specific assistants have custom XML data to save to // write individual handle data xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, d->handles) { int id = handleMap.size(); if (!handleMap.contains(handle)){ handleMap.insert(handle, id); } id = handleMap.value(handle); xml.writeStartElement("handle"); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); if (!d->sideHandles.isEmpty()) { // for vanishing points only xml.writeStartElement("sidehandles"); QMap sideHandleMap; Q_FOREACH (KisPaintingAssistantHandleSP handle, d->sideHandles) { int id = sideHandleMap.size(); sideHandleMap.insert(handle, id); xml.writeStartElement("sidehandle"); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } } xml.writeEndElement(); xml.writeEndDocument(); return data; } void KisPaintingAssistant::saveCustomXml(QXmlStreamWriter* xml) { Q_UNUSED(xml); } void KisPaintingAssistant::loadXml(KoStore* store, QMap &handleMap, QString path) { int id = 0; double x = 0.0, y = 0.0; store->open(path); QByteArray data = store->read(store->size()); QXmlStreamReader xml(data); QMap sideHandleMap; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "assistant") { QStringRef active = xml.attributes().value("active"); setSnappingActive( (active != "0") ); // load custom shared assistant properties if ( xml.attributes().hasAttribute("useCustomColor")) { QStringRef useCustomColor = xml.attributes().value("useCustomColor"); bool usingColor = false; if (useCustomColor.toString() == "1") { usingColor = true; } setUseCustomColor(usingColor); } if ( xml.attributes().hasAttribute("customColor")) { QStringRef customColor = xml.attributes().value("customColor"); setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); } } loadCustomXml(&xml); if (xml.name() == "handle") { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { id = strId.toInt(); x = strX.toDouble(); y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } } addHandle(handleMap.value(id), HandleType::NORMAL); } else if (xml.name() == "sidehandle") { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { id = strId.toInt(); x = strX.toDouble(); y = strY.toDouble(); if (!sideHandleMap.contains(id)) { sideHandleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } } addHandle(sideHandleMap.value(id), HandleType::SIDE); } break; default: break; } } store->close(); } bool KisPaintingAssistant::loadCustomXml(QXmlStreamReader* xml) { Q_UNUSED(xml); return true; } void KisPaintingAssistant::saveXmlList(QDomDocument& doc, QDomElement& assistantsElement,int count) { if (d->s->id == "ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ellipse"); assistantElement.setAttribute("filename", QString("ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "spline"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "spline"); assistantElement.setAttribute("filename", QString("spline%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "perspective"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "perspective"); assistantElement.setAttribute("filename", QString("perspective%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "vanishing point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "vanishing point"); assistantElement.setAttribute("filename", QString("vanishing point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "infinite ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "infinite ruler"); assistantElement.setAttribute("filename", QString("infinite ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "parallel ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "parallel ruler"); assistantElement.setAttribute("filename", QString("parallel ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "concentric ellipse"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "concentric ellipse"); assistantElement.setAttribute("filename", QString("concentric ellipse%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "fisheye-point"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "fisheye-point"); assistantElement.setAttribute("filename", QString("fisheye-point%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } else if (d->s->id == "ruler"){ QDomElement assistantElement = doc.createElement("assistant"); assistantElement.setAttribute("type", "ruler"); assistantElement.setAttribute("filename", QString("ruler%1.assistant").arg(count)); assistantsElement.appendChild(assistantElement); } + else if (d->s->id == "conjugate"){ + QDomElement assistantElement = doc.createElement("assistant"); + assistantElement.setAttribute("type", "conjugate"); + assistantElement.setAttribute("filename", QString("conjugate%1.assistant").arg(count)); + assistantsElement.appendChild(assistantElement); + } } void KisPaintingAssistant::findPerspectiveAssistantHandleLocation() { QList hHandlesList; QList vHandlesList; uint vHole = 0,hHole = 0; KisPaintingAssistantHandleSP oppHandle; if (d->handles.size() == 4 && d->s->id == "perspective") { //get the handle opposite to the first handle oppHandle = oppHandleOne(); //Sorting handles into two list, X sorted and Y sorted into hHandlesList and vHandlesList respectively. Q_FOREACH (const KisPaintingAssistantHandleSP handle,d->handles) { hHandlesList.append(handle); hHole = hHandlesList.size() - 1; vHandlesList.append(handle); vHole = vHandlesList.size() - 1; /* sort handles on the basis of X-coordinate */ while(hHole > 0 && hHandlesList.at(hHole -1).data()->x() > handle.data()->x()) { #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) hHandlesList.swapItemsAt(hHole - 1, hHole); #else hHandlesList.swap(hHole - 1, hHole); #endif hHole = hHole - 1; } /* sort handles on the basis of Y-coordinate */ while(vHole > 0 && vHandlesList.at(vHole -1).data()->y() > handle.data()->y()) { #if QT_VERSION >= QT_VERSION_CHECK(5,13,0) vHandlesList.swapItemsAt(vHole-1, vHole); #else vHandlesList.swap(vHole-1, vHole); #endif vHole = vHole - 1; } } /* give the handles their respective positions */ if(vHandlesList.at(0).data()->x() > vHandlesList.at(1).data()->x()) { d->topLeft = vHandlesList.at(1); d->topRight= vHandlesList.at(0); } else { d->topLeft = vHandlesList.at(0); d->topRight = vHandlesList.at(1); } if(vHandlesList.at(2).data()->x() > vHandlesList.at(3).data()->x()) { d->bottomLeft = vHandlesList.at(3); d->bottomRight = vHandlesList.at(2); } else { d->bottomLeft= vHandlesList.at(2); d->bottomRight = vHandlesList.at(3); } /* find if the handles that should be opposite are actually oppositely positioned */ if (( (d->topLeft == d->handles.at(0).data() && d->bottomRight == oppHandle) || (d->topLeft == oppHandle && d->bottomRight == d->handles.at(0).data()) || (d->topRight == d->handles.at(0).data() && d->bottomLeft == oppHandle) || (d->topRight == oppHandle && d->bottomLeft == d->handles.at(0).data()) ) ) {} else { if(hHandlesList.at(0).data()->y() > hHandlesList.at(1).data()->y()) { d->topLeft = hHandlesList.at(1); d->bottomLeft= hHandlesList.at(0); } else { d->topLeft = hHandlesList.at(0); d->bottomLeft = hHandlesList.at(1); } if(hHandlesList.at(2).data()->y() > hHandlesList.at(3).data()->y()) { d->topRight = hHandlesList.at(3); d->bottomRight = hHandlesList.at(2); } else { d->topRight= hHandlesList.at(2); d->bottomRight = hHandlesList.at(3); } } /* Setting the middle handles as needed */ if(!d->bottomMiddle && !d->topMiddle && !d->leftMiddle && !d->rightMiddle) { d->bottomMiddle = new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5); d->topMiddle = new KisPaintingAssistantHandle((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5); d->rightMiddle= new KisPaintingAssistantHandle((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5); d->leftMiddle= new KisPaintingAssistantHandle((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5); addHandle(d->rightMiddle.data(), HandleType::SIDE); addHandle(d->leftMiddle.data(), HandleType::SIDE); addHandle(d->bottomMiddle.data(), HandleType::SIDE); addHandle(d->topMiddle.data(), HandleType::SIDE); } else { d->bottomMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->bottomRight.data()->x())*0.5, (d->bottomLeft.data()->y() + d->bottomRight.data()->y())*0.5)); d->topMiddle.data()->operator =(QPointF((d->topLeft.data()->x() + d->topRight.data()->x())*0.5, (d->topLeft.data()->y() + d->topRight.data()->y())*0.5)); d->rightMiddle.data()->operator =(QPointF((d->topRight.data()->x() + d->bottomRight.data()->x())*0.5, (d->topRight.data()->y() + d->bottomRight.data()->y())*0.5)); d->leftMiddle.data()->operator =(QPointF((d->bottomLeft.data()->x() + d->topLeft.data()->x())*0.5, (d->bottomLeft.data()->y() + d->topLeft.data()->y())*0.5)); } } } KisPaintingAssistantHandleSP KisPaintingAssistant::oppHandleOne() { QPointF intersection(0,0); 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) && (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)) { return d->handles.at(1); } 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) && (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)) { return d->handles.at(2); } else { return d->handles.at(3); } } KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() { return d->topLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topLeft() const { return d->topLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() { return d->bottomLeft; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomLeft() const { return d->bottomLeft; } KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() { return d->topRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topRight() const { return d->topRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() { return d->bottomRight; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomRight() const { return d->bottomRight; } KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() { return d->topMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::topMiddle() const { return d->topMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() { return d->bottomMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::bottomMiddle() const { return d->bottomMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() { return d->rightMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::rightMiddle() const { return d->rightMiddle; } KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() { return d->leftMiddle; } const KisPaintingAssistantHandleSP KisPaintingAssistant::leftMiddle() const { return d->leftMiddle; } const QList& KisPaintingAssistant::handles() const { return d->handles; } QList KisPaintingAssistant::handles() { return d->handles; } const QList& KisPaintingAssistant::sideHandles() const { return d->sideHandles; } QList KisPaintingAssistant::sideHandles() { return d->sideHandles; } bool KisPaintingAssistant::areTwoPointsClose(const QPointF& pointOne, const QPointF& pointTwo) { int m_handleSize = 16; QRectF handlerect(pointTwo - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); return handlerect.contains(pointOne); } KisPaintingAssistantHandleSP KisPaintingAssistant::closestCornerHandleFromPoint(QPointF point) { if (!d->s->m_canvas) { return 0; } if (areTwoPointsClose(point, pixelToView(topLeft()->toPoint()))) { return topLeft(); } else if (areTwoPointsClose(point, pixelToView(topRight()->toPoint()))) { return topRight(); } else if (areTwoPointsClose(point, pixelToView(bottomLeft()->toPoint()))) { return bottomLeft(); } else if (areTwoPointsClose(point, pixelToView(bottomRight()->toPoint()))) { return bottomRight(); } return 0; } QPointF KisPaintingAssistant::pixelToView(const QPoint pixelCoords) const { QPointF documentCoord = d->s->m_canvas->image()->pixelToDocument(pixelCoords); return d->s->m_canvas->viewConverter()->documentToView(documentCoord); } double KisPaintingAssistant::norm2(const QPointF& p) { return p.x() * p.x() + p.y() * p.y(); } QList KisPaintingAssistant::cloneAssistantList(const QList &list) { QMap handleMap; QList clonedList; for (auto i = list.begin(); i != list.end(); ++i) { clonedList << (*i)->clone(handleMap); } return clonedList; } /* * KisPaintingAssistantFactory classes */ KisPaintingAssistantFactory::KisPaintingAssistantFactory() { } KisPaintingAssistantFactory::~KisPaintingAssistantFactory() { } KisPaintingAssistantFactoryRegistry::KisPaintingAssistantFactoryRegistry() { } KisPaintingAssistantFactoryRegistry::~KisPaintingAssistantFactoryRegistry() { Q_FOREACH (const QString &id, keys()) { delete get(id); } dbgRegistry << "deleting KisPaintingAssistantFactoryRegistry "; } KisPaintingAssistantFactoryRegistry* KisPaintingAssistantFactoryRegistry::instance() { return s_instance; } diff --git a/plugins/assistants/Assistants/CMakeLists.txt b/plugins/assistants/Assistants/CMakeLists.txt index 7ac242b6f1..e29630e90b 100644 --- a/plugins/assistants/Assistants/CMakeLists.txt +++ b/plugins/assistants/Assistants/CMakeLists.txt @@ -1,26 +1,27 @@ set(kritaassistanttool_SOURCES assistant_tool.cc ConcentricEllipseAssistant.cc Ellipse.cc EllipseAssistant.cc FisheyePointAssistant.cc InfiniteRulerAssistant.cc kis_assistant_tool.cc ParallelRulerAssistant.cc PerspectiveAssistant.cc Ruler.cc RulerAssistant.cc SplineAssistant.cc VanishingPointAssistant.cc EditAssistantsCommand.cpp + ConjugateAssistant.cc ) ki18n_wrap_ui(kritaassistanttool_SOURCES AssistantsToolOptions.ui ) add_library(kritaassistanttool MODULE ${kritaassistanttool_SOURCES}) target_link_libraries(kritaassistanttool kritaui kritaflake ) install(TARGETS kritaassistanttool DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( FILES krita_tool_assistant.png dark_krita_tool_assistant.png light_krita_tool_assistant.png DESTINATION ${DATA_INSTALL_DIR}/krita/pics) diff --git a/plugins/assistants/Assistants/ConjugateAssistant.cc b/plugins/assistants/Assistants/ConjugateAssistant.cc new file mode 100644 index 0000000000..55cdb0593f --- /dev/null +++ b/plugins/assistants/Assistants/ConjugateAssistant.cc @@ -0,0 +1,366 @@ +#include "ConjugateAssistant.h" +#include "kis_debug.h" +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +ConjugateAssistant::ConjugateAssistant() + : KisPaintingAssistant("conjugate", i18n("Conjugate assistant")) +{ +} + +ConjugateAssistant::ConjugateAssistant(const ConjugateAssistant &rhs, QMap &handleMap) + : KisPaintingAssistant(rhs, handleMap) + , m_canvas(rhs.m_canvas) + , m_referenceLineDensity(rhs.m_referenceLineDensity) + , m_snapLine(rhs.m_snapLine) +{ +} + +KisPaintingAssistantSP ConjugateAssistant::clone(QMap &handleMap) const +{ + return KisPaintingAssistantSP(new ConjugateAssistant(*this, handleMap)); +} + +QPointF ConjugateAssistant::project(const QPointF& pt, const QPointF& strokeBegin) +{ + //Q_ASSERT(handles().size() == 1 || handles().size() == 5); + //code nicked from the perspective ruler. + qreal dx = pt.x() - strokeBegin.x(); + qreal dy = pt.y() - strokeBegin.y(); + + if (dx * dx + dy * dy < 4.0) { + // allow some movement before snapping + return strokeBegin; + } + + QLineF first = QLineF(strokeBegin, *handles()[0]); + QLineF second = QLineF(strokeBegin, *handles()[1]); + + QList angles = QList({first.angleTo(QLineF(strokeBegin,pt)), + second.angleTo(QLineF(strokeBegin,pt)), + }); + + // for (int i = 0; i < 2; i++) { + // ENTER_FUNCTION() << "angle: " << angles[i]; + // if (angles[i] > 180) { + // angles[i] = 360 - angles[i] ; + // } + // } + + for (int i = 0; i < angles.length(); i++) { + ENTER_FUNCTION() << i << angles[i]; + + if (90 < angles[i] && angles[i] < 270) { + angles[i] = qAbs(angles[i] - 180); + } + + if (angles[i] > 270) { + angles[i] = 360 - angles[i]; + } + ENTER_FUNCTION() << i << angles[i]; + } + + //dbgKrita<canvasWidget()->mapFromGlobal(QCursor::pos()); + m_canvas = canvas; + } + else { + mousePos = QCursor::pos();//this'll give an offset// + dbgFile<<"canvas does not exist in ruler, you may have passed arguments incorrectly:"<documentToWidgetTransform(); + bool isEditing = canvas->paintingAssistantsDecoration()->isEditingAssistants(); + QLineF hl; // objective horizon line + + if (isEditing){ + Q_FOREACH (const QPointF* handle, handles()) { + QPointF h = initialTransform.map(*handle); + QRectF ellipse = QRectF(QPointF(h.x() -15, h.y() -15), QSizeF(30, 30)); + + QPainterPath pathCenter; + pathCenter.addEllipse(ellipse); + drawPath(gc, pathCenter, isSnappingActive()); + }} + + if (handles().size() >= 2) { + const QPointF p1 = *handles()[0]; + const QPointF p2 = *handles()[1]; + const QRect viewport= gc.viewport(); + + hl = QLineF(p1,p2); + QLineF horizonLine = initialTransform.map(hl); // The apparent, SUBJECTIVE horizon line to draw + KisAlgebra2D::intersectLineRect(horizonLine, viewport); + + QPainterPath path; + + // draw the horizon + if (assistantVisible == true || isEditing == true) { + path.moveTo(horizonLine.p1()); + path.lineTo(horizonLine.p2()); + drawPath(gc, path, isSnappingActive()); + } + + // draw the VP-->mousePos lines + if (isEditing == false && previewVisible == true && isSnappingActive() == true) { + QLineF snapMouse1 = QLineF(initialTransform.map(p1), mousePos); + QLineF snapMouse2 = QLineF(initialTransform.map(p2), mousePos); + KisAlgebra2D::intersectLineRect(snapMouse1, viewport); + KisAlgebra2D::intersectLineRect(snapMouse2, viewport); + path.moveTo(initialTransform.map(p1)); + path.lineTo(snapMouse1.p1()); + path.moveTo(initialTransform.map(p2)); + path.lineTo(snapMouse2.p1()); + drawPreview(gc, path); + } + + QPointF p3; + QPointF cov; + if (handles().size() >= 3) { + p3 = *handles()[2]; + QLineF norm = hl.normalVector(); + norm.translate(-norm.p1()+p3); + hl.intersect(norm,&cov); + + // p3 is invalid if cov doesnt lie somewhere between p1 and p2, so set a valid cov + if(!((p1.y() <= p2.y() && cov.y() >= p1.y() && cov.y() <= p2.y()) || + (p1.y() >= p2.y() && cov.y() <= p1.y() && cov.y() >= p2.y()) || + (p1.x() <= p2.x() && cov.x() >= p1.x() && cov.x() <= p2.x()) || + (p1.x() >= p2.x() && cov.x() <= p1.x() && cov.x() >= p2.x()))) { + cov = QLineF(p1,p2).center(); + p3 = QLineF(p1,p2).center(); + norm.translate(-norm.p1()+p3); + *handles()[2] = norm.p2(); + } + + // draw the vertical normal line + if (isEditing == true) { + QLineF normalLine = initialTransform.map(norm); + KisAlgebra2D::intersectLineRect(normalLine, viewport); + path.moveTo(normalLine.p1()); + path.lineTo(normalLine.p2()); + drawPath(gc, path, isSnappingActive()); + } + } + + // draw the side handle bars + if (isEditing == true && !sideHandles().isEmpty()) { + path.moveTo(initialTransform.map(p1)); + path.lineTo(initialTransform.map(*sideHandles()[0])); + path.lineTo(initialTransform.map(*sideHandles()[1])); + path.moveTo(initialTransform.map(p2)); + path.lineTo(initialTransform.map(*sideHandles()[2])); + path.lineTo(initialTransform.map(*sideHandles()[3])); + path.moveTo(initialTransform.map(p1)); + path.lineTo(initialTransform.map(*sideHandles()[4])); + path.lineTo(initialTransform.map(*sideHandles()[5])); + path.moveTo(initialTransform.map(p2)); + path.lineTo(initialTransform.map(*sideHandles()[6])); + path.lineTo(initialTransform.map(*sideHandles()[7])); + drawPreview(gc,path); + } + + if (handles().size() >= 3) { + const QLineF hl = QLineF(*handles()[0], *handles()[1]); + const qreal radius = hl.length() / 2.0; + const qreal gap = QLineF(hl.center(),cov).length(); + QLineF vertical; + vertical = hl.normalVector(); + vertical.translate(cov - vertical.p1()); + vertical.setLength(sqrt((radius*radius) - (gap*gap))); + const QPointF sp = vertical.p2(); + + qreal hl_angle = hl.angle(); + qreal interval = QLineF(sp,cov).length(); + hl_angle > 180 ? hl_angle = hl_angle - 180: hl_angle = hl_angle; + + QList points = QList({p1,p2}); + + Q_FOREACH (QPointF pt, points) { + + QPointF point = initialTransform.map(pt); + qreal arm = QLineF(pt,cov).length(); + QLineF gridline = QLineF(pt,sp); + const QLineF sp_line = QLineF(pt,sp); + QLineF ray = initialTransform.map(gridline); + KisAlgebra2D::intersectLineRect(ray, viewport); + + QLineF other_gridline; + pt == p1 ? other_gridline = QLineF(p2,sp) : other_gridline = QLineF(p1,sp); + + qreal actual_interval = abs( interval / cos((other_gridline.angle()-hl_angle) * M_PI / 180) ); + + qreal new_angle; + qreal actual_angle; + qreal mirroring_adjustment; + + for (int i = -50; i!=50; i++) { + new_angle = atan2(interval,arm + actual_interval*i) * 180 / M_PI; + + if (sp_line.angle() - hl_angle < 90) { + actual_angle = new_angle ; + mirroring_adjustment = 360 - new_angle ;} + + if (sp_line.angle() - hl_angle > 90) { + actual_angle = 180 - new_angle; + mirroring_adjustment = new_angle + 180 ;} + + if (sp_line.angle() - hl_angle > 180) { + actual_angle = new_angle + 180 ; + mirroring_adjustment = 180 - new_angle;} + + if (sp_line.angle() - hl_angle > 270) { + actual_angle = 360 - new_angle; + mirroring_adjustment = new_angle ;} + + gridline.setAngle(actual_angle + hl_angle); + ray = initialTransform.map(gridline); + KisAlgebra2D::intersectLineRect(ray, viewport); + path.moveTo(point); + path.lineTo(ray.p2()); + + gridline.setAngle(mirroring_adjustment + hl_angle); + ray = initialTransform.map(gridline); + KisAlgebra2D::intersectLineRect(ray, viewport); + path.moveTo(point); + path.lineTo(ray.p2()); + } + } + + drawPreview(gc,path); + } + } + + gc.restore(); + //KisPaintingAssistant::drawAssistant(gc, updateRect, converter, cached, canvas, assistantVisible, previewVisible); +} + +void ConjugateAssistant::drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible) +{ + if (!m_canvas || !isAssistantComplete()) { + return; + } + + if (assistantVisible == false || m_canvas->paintingAssistantsDecoration()->isEditingAssistants()) { + return; + } + + QTransform initialTransform = converter->documentToWidgetTransform(); + QPainterPath path; + + if (isAssistantComplete()){ + QPointF cov; + QLineF hl = QLineF(*handles()[0],*handles()[1]); + QLineF vertical = hl.normalVector(); + vertical.translate(*handles()[2] - vertical.p1()); + vertical.intersect(hl, &cov); + QPointF centerOfVision = initialTransform.map(cov); + + path.moveTo(QPointF(centerOfVision.x() - 10.0, centerOfVision.y() - 10.0)); + path.lineTo(QPointF(centerOfVision.x() + 10.0, centerOfVision.y() + 10.0)); + + path.moveTo(QPointF(centerOfVision.x() - 10.0, centerOfVision.y() + 10.0)); + path.lineTo(QPointF(centerOfVision.x() + 10.0, centerOfVision.y() - 10.0)); + + drawPath(gc, path, isSnappingActive()); + } +} + +QPointF ConjugateAssistant::getEditorPosition() const +{ + return *handles()[2]; +} + +void ConjugateAssistant::setReferenceLineDensity(float value) +{ +} + +float ConjugateAssistant::referenceLineDensity() +{ + float f = 0.0; + return f; +} + +bool ConjugateAssistant::isAssistantComplete() const +{ + return handles().size() >= 3; +} + +void ConjugateAssistant::saveCustomXml(QXmlStreamWriter* xml) +{ +} + +bool ConjugateAssistant::loadCustomXml(QXmlStreamReader* xml) +{ + bool b = true; + return b; +} + +ConjugateAssistantFactory::ConjugateAssistantFactory() +{ +} + +ConjugateAssistantFactory::~ConjugateAssistantFactory() +{ +} + +QString ConjugateAssistantFactory::id() const +{ + return "conjugate"; +} + +QString ConjugateAssistantFactory::name() const +{ + return i18n("Conjugate"); +} + +KisPaintingAssistant* ConjugateAssistantFactory::createPaintingAssistant() const +{ + return new ConjugateAssistant; +} diff --git a/plugins/assistants/Assistants/ConjugateAssistant.h b/plugins/assistants/Assistants/ConjugateAssistant.h new file mode 100644 index 0000000000..2dd6e69dcc --- /dev/null +++ b/plugins/assistants/Assistants/ConjugateAssistant.h @@ -0,0 +1,52 @@ +#ifndef _CONJUGATE_ASSISTANT_H_ +#define _CONJUGATE_ASSISTANT_H_ + +#include "kis_painting_assistant.h" +#include +#include +#include +#include + +class ConjugateAssistant : public KisPaintingAssistant +{ +public: + ConjugateAssistant(); + QPointF adjustPosition(const QPointF& point, const QPointF& strokeBegin) override; + KisPaintingAssistantSP clone(QMap &handleMap) const override; + //virtual void endStroke(); + QPointF getEditorPosition() const override; + int numHandles() const override { return 3; } + + float referenceLineDensity(); + void setReferenceLineDensity(float value); + + bool isAssistantComplete() const override; + + void saveCustomXml(QXmlStreamWriter* xml) override; + bool loadCustomXml(QXmlStreamReader* xml) override; + +protected: + void drawAssistant(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter* converter, bool cached = true,KisCanvas2* canvas=0, bool assistantVisible=true, bool previewVisible=true) override; + void drawCache(QPainter& gc, const KisCoordinatesConverter *converter, bool assistantVisible=true) override; +private: + QPointF project(const QPointF& pt, const QPointF& strokeBegin); + explicit ConjugateAssistant(const ConjugateAssistant &rhs, QMap &handleMap); + KisCanvas2 *m_canvas; + + float m_referenceLineDensity = 15.0; + QLineF m_snapLine; + + +}; + +class ConjugateAssistantFactory : public KisPaintingAssistantFactory +{ +public: + ConjugateAssistantFactory(); + ~ConjugateAssistantFactory() override; + QString id() const override; + QString name() const override; + KisPaintingAssistant* createPaintingAssistant() const override; +}; + +#endif diff --git a/plugins/assistants/Assistants/assistant_tool.cc b/plugins/assistants/Assistants/assistant_tool.cc index 69d87c8ffe..2f76b96923 100644 --- a/plugins/assistants/Assistants/assistant_tool.cc +++ b/plugins/assistants/Assistants/assistant_tool.cc @@ -1,61 +1,63 @@ /* * Copyright (c) 2008 Cyrille Berger * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "assistant_tool.h" #include "kis_assistant_tool.h" #include #include #include #include #include #include "RulerAssistant.h" #include "EllipseAssistant.h" #include "SplineAssistant.h" #include "PerspectiveAssistant.h" #include "VanishingPointAssistant.h" #include "InfiniteRulerAssistant.h" #include "ParallelRulerAssistant.h" #include "ConcentricEllipseAssistant.h" #include "FisheyePointAssistant.h" +#include "ConjugateAssistant.h" //#include "mesh_assistant.h" K_PLUGIN_FACTORY_WITH_JSON(AssistantToolFactory, "kritaassistanttool.json", registerPlugin();) AssistantToolPlugin::AssistantToolPlugin(QObject *parent, const QVariantList &) : QObject(parent) { KoToolRegistry::instance()->add(new KisAssistantToolFactory()); KisPaintingAssistantFactoryRegistry::instance()->add(new RulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new EllipseAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new SplineAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new PerspectiveAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new VanishingPointAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new InfiniteRulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new ParallelRulerAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new ConcentricEllipseAssistantFactory); KisPaintingAssistantFactoryRegistry::instance()->add(new FisheyePointAssistantFactory); + KisPaintingAssistantFactoryRegistry::instance()->add(new ConjugateAssistantFactory); // KisPaintingAssistantFactoryRegistry::instance()->add(new MeshAssistantFactory); } AssistantToolPlugin::~AssistantToolPlugin() { } #include "assistant_tool.moc" diff --git a/plugins/assistants/Assistants/kis_assistant_tool.cc b/plugins/assistants/Assistants/kis_assistant_tool.cc index af8cd93b57..be50aa14cb 100644 --- a/plugins/assistants/Assistants/kis_assistant_tool.cc +++ b/plugins/assistants/Assistants/kis_assistant_tool.cc @@ -1,1121 +1,1294 @@ /* * Copyright (c) 2008 Cyrille Berger * Copyright (c) 2010 Geoffry Song * Copyright (c) 2017 Scott Petrovic * * This library is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; version 2 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "kis_dom_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_global.h" #include "VanishingPointAssistant.h" #include "EditAssistantsCommand.h" #include +#include "ConjugateAssistant.h" #include +#include KisAssistantTool::KisAssistantTool(KoCanvasBase * canvas) : KisTool(canvas, KisCursor::arrowCursor()), m_canvas(dynamic_cast(canvas)), m_assistantDrag(0), m_newAssistant(0), m_optionsWidget(0) { Q_ASSERT(m_canvas); setObjectName("tool_assistanttool"); } KisAssistantTool::~KisAssistantTool() { } void KisAssistantTool::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_canvas->paintingAssistantsDecoration()->activateAssistantsEditor(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_handleDrag = 0; m_internalMode = MODE_CREATION; m_assistantHelperYOffset = 10; m_handleSize = 17; m_canvas->paintingAssistantsDecoration()->setHandleSize(m_handleSize); if (m_optionsWidget) { m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } m_canvas->updateCanvas(); } void KisAssistantTool::deactivate() { m_canvas->paintingAssistantsDecoration()->deactivateAssistantsEditor(); m_canvas->updateCanvas(); KisTool::deactivate(); } void KisAssistantTool::beginPrimaryAction(KoPointerEvent *event) { setMode(KisTool::PAINT_MODE); m_origAssistantList = KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()); bool newAssistantAllowed = true; KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_newAssistant) { m_internalMode = MODE_CREATION; *m_newAssistant->handles().back() = canvasDecoration->snapToGuide(event, QPointF(), false); + + // give conjugate assistant side handles once it is completed + if (m_newAssistant->id() == "conjugate" && m_newAssistant->handles().size() == m_newAssistant->numHandles() && m_newAssistant->sideHandles().isEmpty()){ + QList handles = m_newAssistant->handles(); + const QLineF horizon = QLineF(*handles[0],*handles[1]); + QLineF vertical = horizon.normalVector(); + vertical.translate(*handles[2] - vertical.p1()) ; + QPointF cov; + horizon.intersect(vertical, &cov); + QLineF distance = QLineF(*handles[2],cov); + distance.setLength(distance.length() * 2.0); // Double it, we will ise both p1() and p2() + const qreal length = QLineF(*handles[2],cov).length(); + + QLineF bar; + bar= QLineF(distance.p1(), *handles[0]); + bar.setLength(length); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar.setLength(length * 0.5); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar= QLineF(distance.p1(), *handles[1]); + bar.setLength(length); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar.setLength(length * 0.5); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + + bar= QLineF(distance.p2(), *handles[0]); + bar.setLength(length); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar.setLength(length * 0.5); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar= QLineF(distance.p2(), *handles[1]); + bar.setLength(length); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + bar.setLength(length * 0.5); + m_newAssistant->addHandle(new KisPaintingAssistantHandle(bar.p2()), HandleType::SIDE); + } + if (m_newAssistant->handles().size() == m_newAssistant->numHandles()) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } m_canvas->updateCanvas(); return; } m_handleDrag = 0; double minDist = 81.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(canvasDecoration->snapToGuide(event, QPointF(), false));//m_canvas->viewConverter()->documentToView(event->point); // syncs the assistant handles to the handles reference we store in this tool // they can get out of sync with the way the actions and paintevents occur // we probably need to stop storing a reference in m_handles and call the assistants directly m_handles = m_canvas->paintingAssistantsDecoration()->handles(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { // find out which handle on all assistants is closest to the mouse position // vanishing points have "side handles", so make sure to include that { QList allAssistantHandles; allAssistantHandles.append(assistant->handles()); allAssistantHandles.append(assistant->sideHandles()); Q_FOREACH (const KisPaintingAssistantHandleSP handle, allAssistantHandles) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleDrag = handle; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } } if(m_handleDrag && assistant->id() == "perspective") { // Look for the handle which was pressed if (m_handleDrag == assistant->topLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_dragStart = QPointF(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_internalMode = MODE_DRAGGING_NODE; } else if (m_handleDrag == assistant->topRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); } else if (m_handleDrag == assistant->bottomLeft()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); } else if (m_handleDrag == assistant->bottomRight()) { double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*m_handleDrag)); if (dist < minDist) { minDist = dist; } m_internalMode = MODE_DRAGGING_NODE; m_dragStart = QPointF(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); } else if (m_handleDrag == assistant->leftMiddle()) { m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->topLeft().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->topLeft().data()->y())*0.5); m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL ); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->rightMiddle()) { m_dragStart = QPointF((assistant->topRight().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->topRight().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->topMiddle()) { m_dragStart = QPointF((assistant->topLeft().data()->x()+assistant->topRight().data()->x())*0.5, (assistant->topLeft().data()->y()+assistant->topRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->topLeft().data()->x(),assistant->topLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->topRight().data()->x(),assistant->topRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(assistant->topRight(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->topLeft(), HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } else if (m_handleDrag == assistant->bottomMiddle()) { m_dragStart = QPointF((assistant->bottomLeft().data()->x()+assistant->bottomRight().data()->x())*0.5, (assistant->bottomLeft().data()->y()+assistant->bottomRight().data()->y())*0.5); m_internalMode = MODE_DRAGGING_TRANSLATING_TWONODES; m_selectedNode1 = new KisPaintingAssistantHandle(assistant->bottomLeft().data()->x(),assistant->bottomLeft().data()->y()); m_selectedNode2 = new KisPaintingAssistantHandle(assistant->bottomRight().data()->x(),assistant->bottomRight().data()->y()); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get("perspective")->createPaintingAssistant()); m_newAssistant->addHandle(assistant->bottomLeft(), HandleType::NORMAL); m_newAssistant->addHandle(assistant->bottomRight(), HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode2, HandleType::NORMAL); m_newAssistant->addHandle(m_selectedNode1, HandleType::NORMAL); m_dragEnd = event->point; m_handleDrag = 0; m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>1 && (assistant->id() == "ruler" || assistant->id() == "parallel ruler" || assistant->id() == "infinite ruler" || assistant->id() == "spline")){ if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if(assistant->handles().size()==4){ if (m_handleDrag == assistant->handles()[2]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[3]) { m_dragStart = *assistant->handles()[1]; } } m_snapIsRadial = false; } else if (m_handleDrag && assistant->handles().size()>2 && (assistant->id() == "ellipse" || - assistant->id() == "concentric ellipse" || - assistant->id() == "fisheye-point")){ + assistant->id() == "concentric ellipse" || + assistant->id() == "fisheye-point" || + assistant->id() == "conjugate")){ m_snapIsRadial = false; if (m_handleDrag == assistant->handles()[0]) { m_dragStart = *assistant->handles()[1]; } else if (m_handleDrag == assistant->handles()[1]) { m_dragStart = *assistant->handles()[0]; } else if (m_handleDrag == assistant->handles()[2]) { m_dragStart = assistant->getEditorPosition(); m_radius = QLineF(m_dragStart, *assistant->handles()[0]); m_snapIsRadial = true; } } else { m_dragStart = assistant->getEditorPosition(); m_snapIsRadial = false; } } if (m_handleDrag) { // TODO: Shift-press should now be handled using the alternate actions // if (event->modifiers() & Qt::ShiftModifier) { // m_handleDrag->uncache(); // m_handleDrag = m_handleDrag->split()[0]; // m_handles = m_canvas->view()->paintingAssistantsDecoration()->handles(); // } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas return; } m_assistantDrag.clear(); Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { AssistantEditorData editorShared; // shared position data between assistant tool and decoration const KisCoordinatesConverter *converter = m_canvas->coordinatesConverter(); // This code contains the click event behavior. QTransform initialTransform = converter->documentToWidgetTransform(); QPointF actionsPosition = initialTransform.map(assistant->viewportConstrainedEditorPosition(converter, editorShared.boundingSize)); // for UI editor widget controls with move, show, and delete -- disregard document transforms like rotating and mirroring. // otherwise the UI controls get awkward to use when they are at 45 degree angles or the order of controls gets flipped backwards QPointF uiMousePosition = initialTransform.map(canvasDecoration->snapToGuide(event, QPointF(), false)); QPointF iconMovePosition(actionsPosition + editorShared.moveIconPosition); QPointF iconSnapPosition(actionsPosition + editorShared.snapIconPosition); QPointF iconDeletePosition(actionsPosition + editorShared.deleteIconPosition); QRectF deleteRect(iconDeletePosition, QSizeF(editorShared.deleteIconSize, editorShared.deleteIconSize)); QRectF visibleRect(iconSnapPosition, QSizeF(editorShared.snapIconSize, editorShared.snapIconSize)); QRectF moveRect(iconMovePosition, QSizeF(editorShared.moveIconSize, editorShared.moveIconSize)); if (moveRect.contains(uiMousePosition)) { m_assistantDrag = assistant; m_cursorStart = event->point; m_currentAdjustment = QPointF(); m_internalMode = MODE_EDITING; assistantSelected(assistant); // whatever handle is the closest contains the selected assistant return; } if (deleteRect.contains(uiMousePosition)) { removeAssistant(assistant); if(m_canvas->paintingAssistantsDecoration()->assistants().isEmpty()) { m_internalMode = MODE_CREATION; } else m_internalMode = MODE_EDITING; m_canvas->updateCanvas(); return; } if (visibleRect.contains(uiMousePosition)) { newAssistantAllowed = false; assistant->setSnappingActive(!assistant->isSnappingActive()); // toggle assistant->uncache();//this updates the chache of the assistant, very important. assistantSelected(assistant); // whatever handle is the closest contains the selected assistant } } if (newAssistantAllowed==true){//don't make a new assistant when I'm just toogling visibility// QString key = m_options.availableAssistantsComboBox->model()->index( m_options.availableAssistantsComboBox->currentIndex(), 0 ).data(Qt::UserRole).toString(); m_newAssistant = toQShared(KisPaintingAssistantFactoryRegistry::instance()->get(key)->createPaintingAssistant()); m_internalMode = MODE_CREATION; m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); if (m_newAssistant->numHandles() <= 1) { addAssistant(); } else { m_newAssistant->addHandle(new KisPaintingAssistantHandle(canvasDecoration->snapToGuide(event, QPointF(), false)), HandleType::NORMAL); } } if (m_newAssistant) { m_newAssistant->setAssistantGlobalColorCache(m_canvas->paintingAssistantsDecoration()->globalAssistantsColor()); } m_canvas->updateCanvas(); } void KisAssistantTool::continuePrimaryAction(KoPointerEvent *event) { KisPaintingAssistantsDecorationSP canvasDecoration = m_canvas->paintingAssistantsDecoration(); if (m_handleDrag) { *m_handleDrag = event->point; //ported from the gradient tool... we need to think about this more in the future. if (event->modifiers() == Qt::ShiftModifier && m_snapIsRadial) { QLineF dragRadius = QLineF(m_dragStart, event->point); dragRadius.setLength(m_radius.length()); *m_handleDrag = dragRadius.p2(); } else if (event->modifiers() == Qt::ShiftModifier ) { QPointF move = snapToClosestAxis(event->point - m_dragStart); *m_handleDrag = m_dragStart + move; } else { *m_handleDrag = canvasDecoration->snapToGuide(event, QPointF(), false); } m_handleDrag->uncache(); m_handleCombine = 0; if (!(event->modifiers() & Qt::ShiftModifier)) { double minDist = 49.0; QPointF mousePos = m_canvas->viewConverter()->documentToView(event->point); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { if (handle == m_handleDrag) continue; double dist = KisPaintingAssistant::norm2(mousePos - m_canvas->viewConverter()->documentToView(*handle)); if (dist < minDist) { minDist = dist; m_handleCombine = handle; } } } m_canvas->updateCanvas(); } else if (m_assistantDrag) { QPointF newAdjustment = canvasDecoration->snapToGuide(event, QPointF(), false) - m_cursorStart; if (event->modifiers() == Qt::ShiftModifier ) { newAdjustment = snapToClosestAxis(newAdjustment); } Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->handles()) { *handle += (newAdjustment - m_currentAdjustment); } - if (m_assistantDrag->id()== "vanishing point"){ + if (m_assistantDrag->id()== "vanishing point" || m_assistantDrag->id()== "conjugate"){ Q_FOREACH (KisPaintingAssistantHandleSP handle, m_assistantDrag->sideHandles()) { *handle += (newAdjustment - m_currentAdjustment); } } m_assistantDrag->uncache(); m_currentAdjustment = newAdjustment; m_canvas->updateCanvas(); } else { event->ignore(); } bool wasHiglightedNode = m_higlightedNode != 0; QPointF mousep = m_canvas->viewConverter()->documentToView(event->point); QList pAssistant= m_canvas->paintingAssistantsDecoration()->assistants(); Q_FOREACH (KisPaintingAssistantSP assistant, pAssistant) { if(assistant->id() == "perspective") { if ((m_higlightedNode = assistant->closestCornerHandleFromPoint(mousep))) { if (m_higlightedNode == m_selectedNode1 || m_higlightedNode == m_selectedNode2) { m_higlightedNode = 0; } else { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas break; } } } //this following bit sets the translations for the vanishing-point handles. if(m_handleDrag && assistant->id() == "vanishing point" && assistant->sideHandles().size()==4) { //for inner handles, the outer handle gets translated. if (m_handleDrag == assistant->sideHandles()[0]) { QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); if (length < 2.0){ length = 2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[1] = perspectiveline.p2(); } else if (m_handleDrag == assistant->sideHandles()[2]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length<2.0){ length=2.0; } length += perspectiveline.length(); perspectiveline.setLength(length); *assistant->sideHandles()[3] = perspectiveline.p2(); } // for outer handles, only the vanishing point is translated, but only if there's an intersection. else if (m_handleDrag == assistant->sideHandles()[1]|| m_handleDrag == assistant->sideHandles()[3]){ QPointF vanishingpoint(0,0); QLineF perspectiveline = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]); QLineF perspectiveline2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]); if (QLineF(perspectiveline2).intersect(QLineF(perspectiveline), &vanishingpoint) != QLineF::NoIntersection){ *assistant->handles()[0] = vanishingpoint; } }// and for the vanishing point itself, only the outer handles get translated. else if (m_handleDrag == assistant->handles()[0]){ QLineF perspectiveline = QLineF(*assistant->handles()[0], *assistant->sideHandles()[0]); QLineF perspectiveline2 = QLineF(*assistant->handles()[0], *assistant->sideHandles()[2]); qreal length = QLineF(*assistant->sideHandles()[0], *assistant->sideHandles()[1]).length(); qreal length2 = QLineF(*assistant->sideHandles()[2], *assistant->sideHandles()[3]).length(); if (length < 2.0) { length = 2.0; } if (length2 < 2.0) { length2=2.0; } length += perspectiveline.length(); length2 += perspectiveline2.length(); perspectiveline.setLength(length); perspectiveline2.setLength(length2); *assistant->sideHandles()[1] = perspectiveline.p2(); *assistant->sideHandles()[3] = perspectiveline2.p2(); } } + if (m_handleDrag && assistant->id() == "conjugate" && assistant->handles().size() >= 3 && + assistant->sideHandles().size() == 8) { + + QSharedPointer assis = qSharedPointerCast(assistant); + QList hndl = assistant->handles(); + QList side_hndl = assistant->sideHandles(); + KisPaintingAssistantHandleSP vp_dragged; + KisPaintingAssistantHandleSP vp_opp; + QPointF vp_dragged_original_pos; + + bool vp_is_dragged = m_handleDrag == hndl[0] || m_handleDrag == hndl[1]; + bool near_handle_is_dragged = m_handleDrag == side_hndl[0] || m_handleDrag == side_hndl[2] || + m_handleDrag == side_hndl[4] || m_handleDrag == side_hndl[6] ; + bool far_handle_is_dragged = m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[3] || + m_handleDrag == side_hndl[5] || m_handleDrag == side_hndl[7] ; + + // A valid horizon line will be needed for later + QLineF horizon; + + // Here we will attempt to get a valid horizon + if (vp_is_dragged) { + // If either VP got dragged, we need its original location to get a correct horizon + // The side handle bars can be used to derive the original VP position + QLineF arm_a, arm_b; + if (m_handleDrag == hndl[0]) { + vp_dragged = hndl[0]; vp_opp = hndl[1]; + arm_a = QLineF(*side_hndl[0],*side_hndl[1]); + arm_b = QLineF(*side_hndl[4],*side_hndl[5]); + } else if (m_handleDrag == hndl[1]){ + vp_dragged = hndl[1]; vp_opp = hndl[0]; + arm_a = QLineF(*side_hndl[3],*side_hndl[2]); + arm_b = QLineF(*side_hndl[6],*side_hndl[7]); + } + arm_a.intersect(arm_b, &vp_dragged_original_pos); + horizon = QLineF(*vp_opp, vp_dragged_original_pos); + } else { + // First calcuate relevant VP's new position if any far handles got dragged + if (far_handle_is_dragged ){ + QLineF arm_a, arm_b; + QPointF vp_new_pos; + KisPaintingAssistantHandleSP vp_moved; + if (m_handleDrag == side_hndl[1] || m_handleDrag == side_hndl[5]) { + vp_moved = hndl[0]; + arm_a = QLineF(*side_hndl[0],*side_hndl[1]); + arm_b = QLineF(*side_hndl[4],*side_hndl[5]); + } else if (m_handleDrag == side_hndl[3] || m_handleDrag == side_hndl[7]) { + vp_moved = hndl[1]; + arm_a = QLineF(*side_hndl[3],*side_hndl[2]); + arm_b = QLineF(*side_hndl[6],*side_hndl[7]); + } + arm_a.intersect(arm_b, &vp_new_pos); + *vp_moved = vp_new_pos; + } + horizon = QLineF(*hndl[0],*hndl[1]); + } + + QLineF vertical; + QPointF cov; + vertical = horizon.normalVector(); + vertical.translate(*hndl[2] - vertical.p1()); + horizon.intersect(vertical,&cov); + + const qreal radius = horizon.length() / 2.0; + const qreal gap = QLineF(horizon.center(),cov).length(); + vertical.translate(cov - vertical.p1()); + vertical.setLength(sqrt((radius*radius) - (gap*gap))); + const QPointF sp = vertical.p2(); + + // Set both VP's new locations + if (vp_is_dragged) { + QPointF new_dragged_vp; + QPointF new_opp_vp; + QLineF dragged_arm; + QLineF opp_arm; + + dragged_arm = QLineF(sp,*vp_dragged); + dragged_arm.intersect(horizon, &new_dragged_vp); + opp_arm = dragged_arm.normalVector(); + opp_arm.translate(sp - opp_arm.p1()); + opp_arm.intersect(horizon, &new_opp_vp); + + if (new_dragged_vp == new_opp_vp) { + new_opp_vp = *vp_opp; + } + + // Several contingencies to avoid edge-case + if (new_dragged_vp == cov) { + opp_arm = QLineF(sp,*vp_opp); + opp_arm.intersect(horizon, &new_opp_vp); + dragged_arm = opp_arm.normalVector(); + dragged_arm.translate(sp - dragged_arm.p1()); + dragged_arm.intersect(horizon, &new_dragged_vp); + if (new_dragged_vp == cov) { + new_dragged_vp = vp_dragged_original_pos; + if (new_dragged_vp == cov) { + QLineF dst = QLineF(cov,*vp_dragged); + QLineF hor = QLineF(*vp_opp,cov); + hor.setLength(dst.length()); + hor.translate(cov-hor.p1()); + new_dragged_vp = hor.p2(); + } + } + } + *vp_dragged = new_dragged_vp; + *vp_opp = new_opp_vp; + } + + // recalculate side handle bars + if (vp_is_dragged || near_handle_is_dragged) { + QLineF arm_a1; + QLineF arm_b1; + QLineF arm_a2; + QLineF arm_b2; + + arm_a1 = QLineF(*hndl[0], *side_hndl[0]); + arm_a1.setLength(QLineF(*side_hndl[0],*side_hndl[1]).length()); + arm_a1.translate(*side_hndl[0] - arm_a1.p1()); + *side_hndl[1] = arm_a1.p2(); + arm_b1 = QLineF(*hndl[0], *side_hndl[4]); + arm_b1.setLength(QLineF(*side_hndl[4],*side_hndl[5]).length()); + arm_b1.translate(*side_hndl[4] - arm_b1.p1()); + *side_hndl[5] = arm_b1.p2(); + + arm_a2 = QLineF(*hndl[1], *side_hndl[2]); + arm_a2.setLength(QLineF(*side_hndl[2],*side_hndl[3]).length()); + arm_a2.translate(*side_hndl[2] - arm_a2.p1()); + *side_hndl[3] = arm_a2.p2(); + arm_b2 = QLineF(*hndl[1], *side_hndl[6]); + arm_b2.setLength(QLineF(*side_hndl[6],*side_hndl[7]).length()); + arm_b2.translate(*side_hndl[6] - arm_b2.p1()); + *side_hndl[7] = arm_b2.p2(); + } + } } if (wasHiglightedNode && !m_higlightedNode) { m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } } void KisAssistantTool::endPrimaryAction(KoPointerEvent *event) { setMode(KisTool::HOVER_MODE); if (m_handleDrag || m_assistantDrag) { if (m_handleDrag) { if (!(event->modifiers() & Qt::ShiftModifier) && m_handleCombine) { m_handleCombine->mergeWith(m_handleDrag); m_handleCombine->uncache(); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); } m_handleDrag = m_handleCombine = 0; } else { m_assistantDrag.clear(); } dbgUI << "creating undo command..."; KUndo2Command *command = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants())); m_canvas->viewManager()->undoAdapter()->addCommand(command); dbgUI << "done"; } else if(m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { addAssistant(); m_internalMode = MODE_CREATION; } else { event->ignore(); } m_canvas->updateCanvas(); // TODO update only the relevant part of the canvas } void KisAssistantTool::addAssistant() { m_canvas->paintingAssistantsDecoration()->addAssistant(m_newAssistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(m_newAssistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid); } QList assistants = m_canvas->paintingAssistantsDecoration()->assistants(); KUndo2Command *addAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(assistants), EditAssistantsCommand::ADD, assistants.indexOf(m_newAssistant)); m_canvas->viewManager()->undoAdapter()->addCommand(addAssistantCmd); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(m_newAssistant); updateToolOptionsUI(); // vanishing point assistant will get an extra option m_newAssistant.clear(); } void KisAssistantTool::removeAssistant(KisPaintingAssistantSP assistant) { QList assistants = m_canvas->paintingAssistantsDecoration()->assistants(); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->removePerspectiveGrid(grid); } m_canvas->paintingAssistantsDecoration()->removeAssistant(assistant); KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants()), EditAssistantsCommand::REMOVE, assistants.indexOf(assistant)); m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::assistantSelected(KisPaintingAssistantSP assistant) { m_canvas->paintingAssistantsDecoration()->setSelectedAssistant(assistant); updateToolOptionsUI(); } void KisAssistantTool::updateToolOptionsUI() { KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); bool hasActiveAssistant = m_selectedAssistant ? true : false; if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; m_options.vanishingPointAngleSpinbox->setVisible(isVanishingPointAssistant); if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); m_options.vanishingPointAngleSpinbox->setValue(assis->referenceLineDensity()); } // load custom color settings from assistant (this happens when changing assistant m_options.useCustomAssistantColor->setChecked(m_selectedAssistant->useCustomColor()); m_options.customAssistantColorButton->setColor(m_selectedAssistant->assistantCustomColor()); double opacity = (double)m_selectedAssistant->assistantCustomColor().alpha()/(double)255.00 * (double)100.00 ; opacity = ceil(opacity); // helps keep the 0-100% slider from shifting m_options.customColorOpacitySlider->blockSignals(true); m_options.customColorOpacitySlider->setValue((double)opacity); m_options.customColorOpacitySlider->blockSignals(false); } else { m_options.vanishingPointAngleSpinbox->setVisible(false); // } // show/hide elements if an assistant is selected or not m_options.assistantsGlobalOpacitySlider->setVisible(hasActiveAssistant); m_options.assistantsColor->setVisible(hasActiveAssistant); m_options.globalColorLabel->setVisible(hasActiveAssistant); m_options.useCustomAssistantColor->setVisible(hasActiveAssistant); // hide custom color options if use custom color is not selected bool showCustomColorSettings = m_options.useCustomAssistantColor->isChecked() && hasActiveAssistant; m_options.customColorOpacitySlider->setVisible(showCustomColorSettings); m_options.customAssistantColorButton->setVisible(showCustomColorSettings); // disable global color settings if we are using the custom color m_options.assistantsGlobalOpacitySlider->setEnabled(!showCustomColorSettings); m_options.assistantsColor->setEnabled(!showCustomColorSettings); m_options.globalColorLabel->setEnabled(!showCustomColorSettings); } void KisAssistantTool::slotChangeVanishingPointAngle(double value) { if ( m_canvas->paintingAssistantsDecoration()->assistants().length() == 0) { return; } // get the selected assistant and change the angle value KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { bool isVanishingPointAssistant = m_selectedAssistant->id() == "vanishing point"; if (isVanishingPointAssistant) { QSharedPointer assis = qSharedPointerCast(m_selectedAssistant); assis->setReferenceLineDensity((float)value); } } m_canvas->canvasWidget()->update(); } void KisAssistantTool::mouseMoveEvent(KoPointerEvent *event) { if (m_newAssistant && m_internalMode == MODE_CREATION) { *m_newAssistant->handles().back() = event->point; } else if (m_newAssistant && m_internalMode == MODE_DRAGGING_TRANSLATING_TWONODES) { QPointF translate = event->point - m_dragEnd; m_dragEnd = event->point; m_selectedNode1.data()->operator = (QPointF(m_selectedNode1.data()->x(),m_selectedNode1.data()->y()) + translate); m_selectedNode2.data()->operator = (QPointF(m_selectedNode2.data()->x(),m_selectedNode2.data()->y()) + translate); } m_canvas->updateCanvas(); } void KisAssistantTool::paint(QPainter& _gc, const KoViewConverter &_converter) { QRectF canvasSize = QRectF(QPointF(0, 0), QSizeF(m_canvas->image()->size())); // show special display while a new assistant is in the process of being created if (m_newAssistant) { QColor assistantColor = m_newAssistant->effectiveAssistantColor(); assistantColor.setAlpha(80); m_newAssistant->drawAssistant(_gc, canvasSize, m_canvas->coordinatesConverter(), false, m_canvas, true, false); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_newAssistant->handles()) { QPainterPath path; path.addEllipse(QRectF(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize))); _gc.save(); _gc.setPen(Qt::NoPen); _gc.setBrush(assistantColor); _gc.drawPath(path); _gc.restore(); } } Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { QColor assistantColor = assistant->effectiveAssistantColor(); assistantColor.setAlpha(80); Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { QRectF ellipse(_converter.documentToView(*handle) - QPointF(m_handleSize * 0.5, m_handleSize * 0.5), QSizeF(m_handleSize, m_handleSize)); // render handles differently if it is the one being dragged. if (handle == m_handleDrag || handle == m_handleCombine) { QPen stroke(assistantColor, 4); _gc.save(); _gc.setPen(stroke); _gc.setBrush(Qt::NoBrush); _gc.drawEllipse(ellipse); _gc.restore(); } } } } void KisAssistantTool::removeAllAssistants() { m_origAssistantList = m_canvas->paintingAssistantsDecoration()->assistants(); m_canvas->viewManager()->canvasResourceProvider()->clearPerspectiveGrids(); m_canvas->paintingAssistantsDecoration()->removeAll(); KUndo2Command *removeAssistantCmd = new EditAssistantsCommand(m_canvas, m_origAssistantList, KisPaintingAssistant::cloneAssistantList(m_canvas->paintingAssistantsDecoration()->assistants())); m_canvas->viewManager()->undoAdapter()->addCommand(removeAssistantCmd); m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); m_canvas->paintingAssistantsDecoration()->deselectAssistant(); updateToolOptionsUI(); } void KisAssistantTool::loadAssistants() { KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::OpenFile, "OpenAssistant"); dialog.setCaption(i18n("Select an Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; if (!QFileInfo(filename).exists()) return; QFile file(filename); file.open(QIODevice::ReadOnly); QByteArray data = file.readAll(); QXmlStreamReader xml(data); QMap handleMap; QMap sideHandleMap; KisPaintingAssistantSP assistant; bool errors = false; while (!xml.atEnd()) { switch (xml.readNext()) { case QXmlStreamReader::StartElement: if (xml.name() == "handle") { if (assistant && !xml.attributes().value("ref").isEmpty()) { KisPaintingAssistantHandleSP handle = handleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle, HandleType::NORMAL); } else { errors = true; } } else { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(), y = strY.toDouble(); if (!handleMap.contains(id)) { handleMap.insert(id, new KisPaintingAssistantHandle(x, y)); } else { errors = true; } } else { errors = true; } } // for vanishing point assistant } else if (xml.name() == "sidehandle"){ // read in sidehandles if (!xml.attributes().value("id").isEmpty()) { QString strId = xml.attributes().value("id").toString(), strX = xml.attributes().value("x").toString(), strY = xml.attributes().value("y").toString(); if (!strId.isEmpty() && !strX.isEmpty() && !strY.isEmpty()) { int id = strId.toInt(); double x = strX.toDouble(); double y = strY.toDouble(); if (!sideHandleMap.contains(id)) { sideHandleMap.insert(id, new KisPaintingAssistantHandle(x,y)); }} } // addHandle to assistant if (!xml.attributes().value("ref").isEmpty() && assistant) { KisPaintingAssistantHandleSP handle = sideHandleMap.value(xml.attributes().value("ref").toString().toInt()); if (handle) { assistant->addHandle(handle, HandleType::SIDE); } } } else if (xml.name() == "assistant") { const KisPaintingAssistantFactory* factory = KisPaintingAssistantFactoryRegistry::instance()->get(xml.attributes().value("type").toString()); if (factory) { if (assistant) { errors = true; assistant.clear(); } assistant = toQShared(factory->createPaintingAssistant()); } else { errors = true; } if (assistant) { // load custom shared assistant properties if (xml.attributes().hasAttribute("useCustomColor")) { QStringRef useCustomColor = xml.attributes().value("useCustomColor"); bool usingColor = false; if (useCustomColor.toString() == "1") { usingColor = true; } assistant->setUseCustomColor(usingColor); } if ( xml.attributes().hasAttribute("useCustomColor")) { QStringRef customColor = xml.attributes().value("customColor"); assistant->setAssistantCustomColor( KisDomUtils::qStringToQColor(customColor.toString()) ); } } } if (assistant) { assistant->loadCustomXml(&xml); } break; case QXmlStreamReader::EndElement: if (xml.name() == "assistant") { if (assistant) { if (assistant->handles().size() == assistant->numHandles()) { if (assistant->id() == "vanishing point" && sideHandleMap.empty()){ // Create side handles if the saved vp assistant doesn't have any. QPointF pos = *assistant->handles()[0]; assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(-140,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(70,0)), HandleType::SIDE); assistant->addHandle(new KisPaintingAssistantHandle(pos+QPointF(140,0)), HandleType::SIDE); } m_canvas->paintingAssistantsDecoration()->addAssistant(assistant); KisAbstractPerspectiveGrid* grid = dynamic_cast(assistant.data()); if (grid) { m_canvas->viewManager()->canvasResourceProvider()->addPerspectiveGrid(grid); } } else { errors = true; } assistant.clear(); } } break; default: break; } } if (assistant) { errors = true; assistant.clear(); } if (xml.hasError()) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), xml.errorString()); } if (errors) { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Errors were encountered. Not all assistants were successfully loaded.")); } m_handles = m_canvas->paintingAssistantsDecoration()->handles(); m_canvas->updateCanvas(); } void KisAssistantTool::saveAssistants() { if (m_handles.isEmpty()) return; QByteArray data; QXmlStreamWriter xml(&data); xml.writeStartDocument(); xml.writeStartElement("paintingassistant"); xml.writeAttribute("color", KisDomUtils::qColorToQString( m_canvas->paintingAssistantsDecoration()->globalAssistantsColor())); // global color if no custom color used xml.writeStartElement("handles"); QMap handleMap; Q_FOREACH (const KisPaintingAssistantHandleSP handle, m_handles) { int id = handleMap.size(); handleMap.insert(handle, id); xml.writeStartElement("handle"); //xml.writeAttribute("type", handle->handleType()); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } xml.writeEndElement(); xml.writeStartElement("sidehandles"); QMap sideHandleMap; Q_FOREACH (KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { Q_FOREACH (KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { int id = sideHandleMap.size(); sideHandleMap.insert(handle, id); xml.writeStartElement("sidehandle"); xml.writeAttribute("id", QString::number(id)); xml.writeAttribute("x", QString::number(double(handle->x()), 'f', 3)); xml.writeAttribute("y", QString::number(double(handle->y()), 'f', 3)); xml.writeEndElement(); } } xml.writeStartElement("assistants"); Q_FOREACH (const KisPaintingAssistantSP assistant, m_canvas->paintingAssistantsDecoration()->assistants()) { xml.writeStartElement("assistant"); xml.writeAttribute("type", assistant->id()); xml.writeAttribute("useCustomColor", QString::number(assistant->useCustomColor())); xml.writeAttribute("customColor", KisDomUtils::qColorToQString(assistant->assistantCustomColor())); // custom assistant properties like angle density on vanishing point assistant->saveCustomXml(&xml); // handle information xml.writeStartElement("handles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->handles()) { xml.writeStartElement("handle"); xml.writeAttribute("ref", QString::number(handleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); if (!sideHandleMap.empty()) { xml.writeStartElement("sidehandles"); Q_FOREACH (const KisPaintingAssistantHandleSP handle, assistant->sideHandles()) { xml.writeStartElement("sidehandle"); xml.writeAttribute("ref", QString::number(sideHandleMap.value(handle))); xml.writeEndElement(); } xml.writeEndElement(); } xml.writeEndElement(); } xml.writeEndElement(); xml.writeEndElement(); xml.writeEndDocument(); KoFileDialog dialog(m_canvas->viewManager()->mainWindow(), KoFileDialog::SaveFile, "OpenAssistant"); dialog.setCaption(i18n("Save Assistant")); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(QStringList() << "application/x-krita-assistant", "application/x-krita-assistant"); QString filename = dialog.filename(); if (filename.isEmpty()) return; QFile file(filename); file.open(QIODevice::WriteOnly); file.write(data); } QWidget *KisAssistantTool::createOptionWidget() { if (!m_optionsWidget) { m_optionsWidget = new QWidget; m_options.setupUi(m_optionsWidget); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_options.loadAssistantButton->setIcon(KisIconUtils::loadIcon("document-open")); m_options.saveAssistantButton->setIcon(KisIconUtils::loadIcon("document-save")); m_options.deleteAllAssistantsButton->setIcon(KisIconUtils::loadIcon("edit-delete")); QList assistants; Q_FOREACH (const QString& key, KisPaintingAssistantFactoryRegistry::instance()->keys()) { QString name = KisPaintingAssistantFactoryRegistry::instance()->get(key)->name(); assistants << KoID(key, name); } std::sort(assistants.begin(), assistants.end(), KoID::compareNames); Q_FOREACH(const KoID &id, assistants) { m_options.availableAssistantsComboBox->addItem(id.name(), id.id()); } connect(m_options.saveAssistantButton, SIGNAL(clicked()), SLOT(saveAssistants())); connect(m_options.loadAssistantButton, SIGNAL(clicked()), SLOT(loadAssistants())); connect(m_options.deleteAllAssistantsButton, SIGNAL(clicked()), SLOT(removeAllAssistants())); connect(m_options.assistantsColor, SIGNAL(changed(QColor)), SLOT(slotGlobalAssistantsColorChanged(QColor))); connect(m_options.assistantsGlobalOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotGlobalAssistantOpacityChanged())); connect(m_options.vanishingPointAngleSpinbox, SIGNAL(valueChanged(double)), this, SLOT(slotChangeVanishingPointAngle(double))); // initialize UI elements with existing data if possible if (m_canvas && m_canvas->paintingAssistantsDecoration()) { const QColor color = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor(); QColor opaqueColor = color; opaqueColor.setAlpha(255); m_options.assistantsColor->setColor(opaqueColor); m_options.customAssistantColorButton->setColor(opaqueColor); m_options.assistantsGlobalOpacitySlider->setValue(color.alphaF() * 100.0); } else { m_options.assistantsColor->setColor(QColor(176, 176, 176, 255)); // grey default for all assistants m_options.assistantsGlobalOpacitySlider->setValue(100); // 100% } m_options.assistantsGlobalOpacitySlider->setPrefix(i18n("Opacity: ")); m_options.assistantsGlobalOpacitySlider->setSuffix(" %"); // custom color of selected assistant m_options.customColorOpacitySlider->setValue(100); // 100% m_options.customColorOpacitySlider->setPrefix(i18n("Opacity: ")); m_options.customColorOpacitySlider->setSuffix(" %"); connect(m_options.useCustomAssistantColor, SIGNAL(clicked(bool)), this, SLOT(slotUpdateCustomColor())); connect(m_options.customAssistantColorButton, SIGNAL(changed(QColor)), this, SLOT(slotUpdateCustomColor())); connect(m_options.customColorOpacitySlider, SIGNAL(valueChanged(int)), SLOT(slotCustomOpacityChanged())); m_options.vanishingPointAngleSpinbox->setPrefix(i18n("Density: ")); m_options.vanishingPointAngleSpinbox->setSuffix(QChar(Qt::Key_degree)); m_options.vanishingPointAngleSpinbox->setRange(1.0, 180.0); m_options.vanishingPointAngleSpinbox->setSingleStep(1.0); m_options.vanishingPointAngleSpinbox->setVisible(false); } updateToolOptionsUI(); return m_optionsWidget; } void KisAssistantTool::slotGlobalAssistantsColorChanged(const QColor& setColor) { // color and alpha are stored separately, so we need to merge the values before sending it on int oldAlpha = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor().alpha(); QColor newColor = setColor; newColor.setAlpha(oldAlpha); m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor); m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotGlobalAssistantOpacityChanged() { QColor newColor = m_canvas->paintingAssistantsDecoration()->globalAssistantsColor(); qreal newOpacity = m_options.assistantsGlobalOpacitySlider->value() * 0.01 * 255.0; newColor.setAlpha(int(newOpacity)); m_canvas->paintingAssistantsDecoration()->setGlobalAssistantsColor(newColor); m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotUpdateCustomColor() { // get the selected assistant and change the angle value KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { m_selectedAssistant->setUseCustomColor(m_options.useCustomAssistantColor->isChecked()); // changing color doesn't keep alpha, so update that before we send it on QColor newColor = m_options.customAssistantColorButton->color(); newColor.setAlpha(m_selectedAssistant->assistantCustomColor().alpha()); m_selectedAssistant->setAssistantCustomColor(newColor); m_selectedAssistant->uncache(); } updateToolOptionsUI(); m_canvas->canvasWidget()->update(); } void KisAssistantTool::slotCustomOpacityChanged() { KisPaintingAssistantSP m_selectedAssistant = m_canvas->paintingAssistantsDecoration()->selectedAssistant(); if (m_selectedAssistant) { QColor newColor = m_selectedAssistant->assistantCustomColor(); qreal newOpacity = m_options.customColorOpacitySlider->value() * 0.01 * 255.0; newColor.setAlpha(int(newOpacity)); m_selectedAssistant->setAssistantCustomColor(newColor); m_selectedAssistant->uncache(); } // this forces the canvas to refresh to see the changes immediately m_canvas->paintingAssistantsDecoration()->uncache(); m_canvas->canvasWidget()->update(); } diff --git a/plugins/impex/libkra/kis_kra_saver.cpp b/plugins/impex/libkra/kis_kra_saver.cpp index 594d1bf8e7..4b9f30b0b9 100644 --- a/plugins/impex/libkra/kis_kra_saver.cpp +++ b/plugins/impex/libkra/kis_kra_saver.cpp @@ -1,537 +1,542 @@ /* This file is part of the KDE project * Copyright 2008 (C) Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_kra_saver.h" #include "kis_kra_tags.h" #include "kis_kra_save_visitor.h" #include "kis_kra_savexml_visitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_keyframe_channel.h" #include #include "KisDocument.h" #include #include "kis_dom_utils.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "KisProofingConfiguration.h" #include #include #include using namespace KRA; struct KisKraSaver::Private { public: KisDocument* doc; QMap nodeFileNames; QMap keyframeFilenames; QString imageName; QString filename; QStringList errorMessages; }; KisKraSaver::KisKraSaver(KisDocument* document, const QString &filename) : m_d(new Private) { m_d->doc = document; m_d->filename = filename; m_d->imageName = m_d->doc->documentInfo()->aboutInfo("title"); if (m_d->imageName.isEmpty()) { m_d->imageName = i18n("Unnamed"); } } KisKraSaver::~KisKraSaver() { delete m_d; } QDomElement KisKraSaver::saveXML(QDomDocument& doc, KisImageSP image) { QDomElement imageElement = doc.createElement("IMAGE"); Q_ASSERT(image); imageElement.setAttribute(NAME, m_d->imageName); imageElement.setAttribute(MIME, NATIVE_MIMETYPE); imageElement.setAttribute(WIDTH, KisDomUtils::toString(image->width())); imageElement.setAttribute(HEIGHT, KisDomUtils::toString(image->height())); imageElement.setAttribute(COLORSPACE_NAME, image->colorSpace()->id()); imageElement.setAttribute(DESCRIPTION, m_d->doc->documentInfo()->aboutInfo("comment")); // XXX: Save profile as blob inside the image, instead of the product name. if (image->profile() && image->profile()-> valid()) { imageElement.setAttribute(PROFILE, image->profile()->name()); } imageElement.setAttribute(X_RESOLUTION, KisDomUtils::toString(image->xRes()*72.0)); imageElement.setAttribute(Y_RESOLUTION, KisDomUtils::toString(image->yRes()*72.0)); //now the proofing options: if (image->proofingConfiguration()) { if (image->proofingConfiguration()->storeSoftproofingInsideImage) { imageElement.setAttribute(PROOFINGPROFILENAME, KisDomUtils::toString(image->proofingConfiguration()->proofingProfile)); imageElement.setAttribute(PROOFINGMODEL, KisDomUtils::toString(image->proofingConfiguration()->proofingModel)); imageElement.setAttribute(PROOFINGDEPTH, KisDomUtils::toString(image->proofingConfiguration()->proofingDepth)); imageElement.setAttribute(PROOFINGINTENT, KisDomUtils::toString(image->proofingConfiguration()->intent)); imageElement.setAttribute(PROOFINGADAPTATIONSTATE, KisDomUtils::toString(image->proofingConfiguration()->adaptationState)); } } quint32 count = 1; // We don't save the root layer, but it does count KisSaveXmlVisitor visitor(doc, imageElement, count, m_d->doc->url().toLocalFile(), true); visitor.setSelectedNodes({m_d->doc->preActivatedNode()}); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); m_d->nodeFileNames = visitor.nodeFileNames(); m_d->keyframeFilenames = visitor.keyframeFileNames(); saveBackgroundColor(doc, imageElement, image); saveAssistantsGlobalColor(doc, imageElement); saveWarningColor(doc, imageElement, image); saveCompositions(doc, imageElement, image); saveAssistantsList(doc, imageElement); saveGrid(doc, imageElement); saveGuides(doc, imageElement); saveMirrorAxis(doc, imageElement); saveAudio(doc, imageElement); savePalettesToXML(doc, imageElement); QDomElement animationElement = doc.createElement("animation"); KisDomUtils::saveValue(&animationElement, "framerate", image->animationInterface()->framerate()); KisDomUtils::saveValue(&animationElement, "range", image->animationInterface()->fullClipRange()); KisDomUtils::saveValue(&animationElement, "currentTime", image->animationInterface()->currentUITime()); imageElement.appendChild(animationElement); return imageElement; } bool KisKraSaver::savePalettes(KoStore *store, KisImageSP image, const QString &uri) { Q_UNUSED(image); Q_UNUSED(uri); qDebug() << "saving palettes to document" << m_d->doc->paletteList().size(); bool res = false; if (m_d->doc->paletteList().size() == 0) { return true; } for (const KoColorSetSP palette : m_d->doc->paletteList()) { qDebug() << "saving palette..." << palette->storageLocation() << palette->filename(); if (!store->open(m_d->imageName + PALETTE_PATH + palette->filename())) { m_d->errorMessages << i18n("could not save palettes"); return false; } QByteArray ba = palette->toByteArray(); if (!ba.isEmpty()) { store->write(ba); } else { qWarning() << "Cannot save the palette to a byte array:" << palette->name(); } store->close(); res = true; } return res; } void KisKraSaver::savePalettesToXML(QDomDocument &doc, QDomElement &element) { QDomElement ePalette = doc.createElement(PALETTES); for (const KoColorSetSP palette : m_d->doc->paletteList()) { QDomElement eFile = doc.createElement("palette"); eFile.setAttribute("filename", palette->filename()); ePalette.appendChild(eFile); } element.appendChild(ePalette); } bool KisKraSaver::saveKeyframes(KoStore *store, const QString &uri, bool external) { QMap::iterator it; for (it = m_d->keyframeFilenames.begin(); it != m_d->keyframeFilenames.end(); it++) { const KisNode *node = it.key(); QString filename = it.value(); QString location = (external ? QString() : uri) + m_d->imageName + LAYER_PATH + filename; if (!saveNodeKeyframes(store, location, node)) { return false; } } return true; } bool KisKraSaver::saveNodeKeyframes(KoStore *store, QString location, const KisNode *node) { QDomDocument doc = KisDocument::createDomDocument("krita-keyframes", "keyframes", "1.0"); QDomElement root = doc.documentElement(); KisKeyframeChannel *channel; Q_FOREACH (channel, node->keyframeChannels()) { QDomElement element = channel->toXML(doc, m_d->nodeFileNames[node]); root.appendChild(element); } if (store->open(location)) { QByteArray xml = doc.toByteArray(); store->write(xml); store->close(); } else { m_d->errorMessages << i18n("could not save keyframes"); return false; } return true; } bool KisKraSaver::saveBinaryData(KoStore* store, KisImageSP image, const QString &uri, bool external, bool autosave) { QString location; // Save the layers data KisKraSaveVisitor visitor(store, m_d->imageName, m_d->nodeFileNames); if (external) visitor.setExternalUri(uri); image->rootLayer()->accept(visitor); m_d->errorMessages.append(visitor.errorMessages()); if (!m_d->errorMessages.isEmpty()) { return false; } // saving annotations // XXX this only saves EXIF and ICC info. This would probably need // a redesign of the dtd of the krita file to do this more generally correct // e.g. have tags or so. KisAnnotationSP annotation = image->annotation("exif"); if (annotation) { location = external ? QString() : uri; location += m_d->imageName + EXIF_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } if (image->profile()) { const KoColorProfile *profile = image->profile(); KisAnnotationSP annotation; if (profile) { QByteArray profileRawData = profile->rawData(); if (!profileRawData.isEmpty()) { if (profile->type() == "icc") { annotation = new KisAnnotation(ICC, profile->name(), profile->rawData()); } else { annotation = new KisAnnotation(PROFILE, profile->name(), profile->rawData()); } } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } //This'll embed the profile used for proofing into the kra file. if (image->proofingConfiguration()) { if (image->proofingConfiguration()->storeSoftproofingInsideImage) { const KoColorProfile *proofingProfile = KoColorSpaceRegistry::instance()->profileByName(image->proofingConfiguration()->proofingProfile); if (proofingProfile && proofingProfile->valid()) { QByteArray proofingProfileRaw = proofingProfile->rawData(); if (!proofingProfileRaw.isEmpty()) { annotation = new KisAnnotation(ICCPROOFINGPROFILE, proofingProfile->name(), proofingProfile->rawData()); } } if (annotation) { location = external ? QString() : uri; location += m_d->imageName + ICC_PROOFING_PATH; if (store->open(location)) { store->write(annotation->annotation()); store->close(); } } } } { warnKrita << "WARNING: Asl Layer Styles cannot be written (part of resource rewrite)."; // TODO: RESOURCES: needs implementation /* KisPSDLayerStyleCollectionResource collection("not-nexists.asl"); KIS_ASSERT_RECOVER_NOOP(!collection.valid()); collection.collectAllLayerStyles(image->root()); if (collection.valid()) { location = external ? QString() : uri; location += m_d->imageName + LAYER_STYLES_PATH; if (store->open(location)) { QBuffer aslBuffer; aslBuffer.open(QIODevice::WriteOnly); collection.saveToDevice(&aslBuffer); aslBuffer.close(); store->write(aslBuffer.buffer()); store->close(); } } */ } if (!autosave) { KisPaintDeviceSP dev = image->projection(); store->setCompressionEnabled(false); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); store->setCompressionEnabled(KisConfig(true).compressKra()); } saveAssistants(store, uri,external); return true; } QStringList KisKraSaver::errorMessages() const { return m_d->errorMessages; } void KisKraSaver::saveBackgroundColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { QDomElement e = doc.createElement(CANVASPROJECTIONCOLOR); KoColor color = image->defaultProjectionColor(); QByteArray colorData = QByteArray::fromRawData((const char*)color.data(), color.colorSpace()->pixelSize()); e.setAttribute(COLORBYTEDATA, QString(colorData.toBase64())); element.appendChild(e); } void KisKraSaver::saveAssistantsGlobalColor(QDomDocument& doc, QDomElement& element) { QDomElement e = doc.createElement(GLOBALASSISTANTSCOLOR); QString colorString = KisDomUtils::qColorToQString(m_d->doc->assistantsGlobalColor()); e.setAttribute(SIMPLECOLORDATA, QString(colorString)); element.appendChild(e); } void KisKraSaver::saveWarningColor(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (image->proofingConfiguration()) { if (image->proofingConfiguration()->storeSoftproofingInsideImage) { QDomElement e = doc.createElement(PROOFINGWARNINGCOLOR); KoColor color = image->proofingConfiguration()->warningColor; color.toXML(doc, e); element.appendChild(e); } } } void KisKraSaver::saveCompositions(QDomDocument& doc, QDomElement& element, KisImageSP image) { if (!image->compositions().isEmpty()) { QDomElement e = doc.createElement("compositions"); Q_FOREACH (KisLayerCompositionSP composition, image->compositions()) { composition->save(doc, e); } element.appendChild(e); } } bool KisKraSaver::saveAssistants(KoStore* store, QString uri, bool external) { QString location; QMap assistantcounters; QByteArray data; QList assistants = m_d->doc->assistants(); QMap handlemap; if (!assistants.isEmpty()) { Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (!assistantcounters.contains(assist->id())){ assistantcounters.insert(assist->id(),0); } location = external ? QString() : uri; location += m_d->imageName + ASSISTANTS_PATH; location += QString(assist->id()+"%1.assistant").arg(assistantcounters[assist->id()]); data = assist->saveXml(handlemap); store->open(location); store->write(data); store->close(); assistantcounters[assist->id()]++; } } return true; } bool KisKraSaver::saveAssistantsList(QDomDocument& doc, QDomElement& element) { - int count_ellipse = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; + int count_ellipse = 0, count_conjugate = 0, count_perspective = 0, count_ruler = 0, count_vanishingpoint = 0,count_infiniteruler = 0, count_parallelruler = 0, count_concentricellipse = 0, count_fisheyepoint = 0, count_spline = 0; QList assistants = m_d->doc->assistants(); if (!assistants.isEmpty()) { QDomElement assistantsElement = doc.createElement("assistants"); Q_FOREACH (KisPaintingAssistantSP assist, assistants){ if (assist->id() == "ellipse"){ assist->saveXmlList(doc, assistantsElement, count_ellipse); count_ellipse++; } else if (assist->id() == "spline"){ assist->saveXmlList(doc, assistantsElement, count_spline); count_spline++; } else if (assist->id() == "perspective"){ assist->saveXmlList(doc, assistantsElement, count_perspective); count_perspective++; } else if (assist->id() == "vanishing point"){ assist->saveXmlList(doc, assistantsElement, count_vanishingpoint); count_vanishingpoint++; } else if (assist->id() == "infinite ruler"){ assist->saveXmlList(doc, assistantsElement, count_infiniteruler); count_infiniteruler++; } else if (assist->id() == "parallel ruler"){ assist->saveXmlList(doc, assistantsElement, count_parallelruler); count_parallelruler++; } else if (assist->id() == "concentric ellipse"){ assist->saveXmlList(doc, assistantsElement, count_concentricellipse); count_concentricellipse++; } else if (assist->id() == "fisheye-point"){ assist->saveXmlList(doc, assistantsElement, count_fisheyepoint); count_fisheyepoint++; } + else if (assist->id() == "conjugate"){ + qDebug() << "saving conjugate"; + assist->saveXmlList(doc, assistantsElement, count_conjugate); + count_conjugate++; + } else if (assist->id() == "ruler"){ assist->saveXmlList(doc, assistantsElement, count_ruler); count_ruler++; } } element.appendChild(assistantsElement); } return true; } bool KisKraSaver::saveGrid(QDomDocument& doc, QDomElement& element) { KisGridConfig config = m_d->doc->gridConfig(); if (!config.isDefault()) { QDomElement gridElement = config.saveDynamicDataToXml(doc, "grid"); element.appendChild(gridElement); } return true; } bool KisKraSaver::saveGuides(QDomDocument& doc, QDomElement& element) { KisGuidesConfig guides = m_d->doc->guidesConfig(); if (!guides.isDefault()) { QDomElement guidesElement = guides.saveToXml(doc, "guides"); element.appendChild(guidesElement); } return true; } bool KisKraSaver::saveMirrorAxis(QDomDocument &doc, QDomElement &element) { KisMirrorAxisConfig mirrorAxisConfig = m_d->doc->mirrorAxisConfig(); if (!mirrorAxisConfig.isDefault()) { QDomElement mirrorAxisElement = mirrorAxisConfig.saveToXml(doc, MIRROR_AXIS); element.appendChild(mirrorAxisElement); } return true; } bool KisKraSaver::saveAudio(QDomDocument& doc, QDomElement& element) { const KisImageAnimationInterface *interface = m_d->doc->image()->animationInterface(); QString fileName = interface->audioChannelFileName(); if (fileName.isEmpty()) return true; if (!QFileInfo::exists(fileName)) { m_d->errorMessages << i18n("Audio channel file %1 doesn't exist!", fileName); return false; } const QDir documentDir = QFileInfo(m_d->filename).absoluteDir(); KIS_ASSERT_RECOVER_RETURN_VALUE(documentDir.exists(), false); fileName = documentDir.relativeFilePath(fileName); fileName = QDir::fromNativeSeparators(fileName); KIS_ASSERT_RECOVER_RETURN_VALUE(!fileName.isEmpty(), false); QDomElement audioElement = doc.createElement("audio"); KisDomUtils::saveValue(&audioElement, "masterChannelPath", fileName); KisDomUtils::saveValue(&audioElement, "audioMuted", interface->isAudioMuted()); KisDomUtils::saveValue(&audioElement, "audioVolume", interface->audioVolume()); element.appendChild(audioElement); return true; }