diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp index 0c3220a64f..19cc69db31 100644 --- a/libs/ui/canvas/kis_coordinates_converter.cpp +++ b/libs/ui/canvas/kis_coordinates_converter.cpp @@ -1,494 +1,490 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * 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_coordinates_converter.h" #include #include #include #include #include struct KisCoordinatesConverter::Private { Private(): isXAxisMirrored(false), isYAxisMirrored(false), rotationAngle(0.0), devicePixelRatio(1.0) { } KisImageWSP image; bool isXAxisMirrored; bool isYAxisMirrored; qreal rotationAngle; QSizeF canvasWidgetSize; qreal devicePixelRatio; QPointF documentOffset; QTransform flakeToWidget; QTransform imageToDocument; QTransform documentToFlake; QTransform widgetToViewport; }; /** * When vastScrolling value is less than 0.5 it is possible * that the whole scrolling area (viewport) will be smaller than * the size of the widget. In such cases the image should be * centered in the widget. Previously we used a special parameter * documentOrigin for this purpose, now the value for this * centering is calculated dynamically, helping the offset to * center the image inside the widget * * Note that the correction is null when the size of the document * plus vast scrolling reserve is larger than the widget. This * is always true for vastScrolling parameter > 0.5. */ QPointF KisCoordinatesConverter::centeringCorrection() const { KisConfig cfg(true); QSize documentSize = imageRectInWidgetPixels().toAlignedRect().size(); QPointF dPoint(documentSize.width(), documentSize.height()); QPointF wPoint(m_d->canvasWidgetSize.width(), m_d->canvasWidgetSize.height()); QPointF minOffset = -cfg.vastScrolling() * wPoint; QPointF maxOffset = dPoint - wPoint + cfg.vastScrolling() * wPoint; QPointF range = maxOffset - minOffset; range.rx() = qMin(range.x(), (qreal)0.0); range.ry() = qMin(range.y(), (qreal)0.0); range /= 2; return -range; } /** * The document offset and the position of the top left corner of the * image must always coincide, that is why we need to correct them to * and fro. * * When we change zoom level, the calculation of the new offset is * done by KoCanvasControllerWidget, that is why we just passively fix * the flakeToWidget transform to conform the offset and wait until * the canvas controller will recenter us. * * But when we do our own transformations of the canvas, like rotation * and mirroring, we cannot rely on the centering of the canvas * controller and we do it ourselves. Then we just set new offset and * return its value to be set in the canvas controller explicitly. */ void KisCoordinatesConverter::correctOffsetToTransformation() { m_d->documentOffset = snapToDevicePixel(-(imageRectInWidgetPixels().topLeft() - centeringCorrection())); } void KisCoordinatesConverter::correctTransformationToOffset() { QPointF topLeft = imageRectInWidgetPixels().topLeft(); QPointF diff = (-topLeft) - m_d->documentOffset; diff += centeringCorrection(); m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); } void KisCoordinatesConverter::recalculateTransformations() { if(!m_d->image) return; m_d->imageToDocument = QTransform::fromScale(1 / m_d->image->xRes(), 1 / m_d->image->yRes()); qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); m_d->documentToFlake = QTransform::fromScale(zoomX, zoomY); correctTransformationToOffset(); QRectF irect = imageRectInWidgetPixels(); QRectF wrect = QRectF(QPoint(0,0), m_d->canvasWidgetSize); QRectF rrect = irect & wrect; QTransform reversedTransform = flakeToWidgetTransform().inverted(); QRectF canvasBounds = reversedTransform.mapRect(rrect); QPointF offset = canvasBounds.topLeft(); m_d->widgetToViewport = reversedTransform * QTransform::fromTranslate(-offset.x(), -offset.y()); } KisCoordinatesConverter::KisCoordinatesConverter() : m_d(new Private) { } KisCoordinatesConverter::~KisCoordinatesConverter() { delete m_d; } QSizeF KisCoordinatesConverter::getCanvasWidgetSize() const { return m_d->canvasWidgetSize; } void KisCoordinatesConverter::setCanvasWidgetSize(QSizeF size) { m_d->canvasWidgetSize = size; recalculateTransformations(); } void KisCoordinatesConverter::setDevicePixelRatio(qreal value) { m_d->devicePixelRatio = value; } void KisCoordinatesConverter::setImage(KisImageWSP image) { m_d->image = image; recalculateTransformations(); } void KisCoordinatesConverter::setDocumentOffset(const QPointF& offset) { QPointF diff = m_d->documentOffset - offset; m_d->documentOffset = offset; m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); recalculateTransformations(); } qreal KisCoordinatesConverter::devicePixelRatio() const { return m_d->devicePixelRatio; } QPoint KisCoordinatesConverter::documentOffset() const { return QPoint(int(m_d->documentOffset.x()), int(m_d->documentOffset.y())); } qreal KisCoordinatesConverter::rotationAngle() const { return m_d->rotationAngle; } void KisCoordinatesConverter::setZoom(qreal zoom) { KoZoomHandler::setZoom(zoom); recalculateTransformations(); } qreal KisCoordinatesConverter::effectiveZoom() const { qreal scaleX, scaleY; this->imageScale(&scaleX, &scaleY); if (scaleX != scaleY) { qWarning() << "WARNING: Zoom is not isotropic!" << ppVar(scaleX) << ppVar(scaleY) << ppVar(qFuzzyCompare(scaleX, scaleY)); } // zoom by average of x and y return 0.5 * (scaleX + scaleY); } QPoint KisCoordinatesConverter::rotate(QPointF center, qreal angle) { QTransform rot; rot.rotate(angle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = std::fmod(m_d->rotationAngle + angle, 360.0); correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } QPoint KisCoordinatesConverter::mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis) { bool keepOrientation = false; // XXX: Keep here for now, maybe some day we can restore the parameter again. bool doXMirroring = m_d->isXAxisMirrored ^ mirrorXAxis; bool doYMirroring = m_d->isYAxisMirrored ^ mirrorYAxis; qreal scaleX = doXMirroring ? -1.0 : 1.0; qreal scaleY = doYMirroring ? -1.0 : 1.0; QTransform mirror = QTransform::fromScale(scaleX, scaleY); QTransform rot; rot.rotate(m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(),-center.y()); if (keepOrientation) { m_d->flakeToWidget *= rot.inverted(); } m_d->flakeToWidget *= mirror; if (keepOrientation) { m_d->flakeToWidget *= rot; } m_d->flakeToWidget *= QTransform::fromTranslate(center.x(),center.y()); if (!keepOrientation && (doXMirroring ^ doYMirroring)) { m_d->rotationAngle = -m_d->rotationAngle; } m_d->isXAxisMirrored = mirrorXAxis; m_d->isYAxisMirrored = mirrorYAxis; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } bool KisCoordinatesConverter::xAxisMirrored() const { return m_d->isXAxisMirrored; } bool KisCoordinatesConverter::yAxisMirrored() const { return m_d->isYAxisMirrored; } QPoint KisCoordinatesConverter::resetRotation(QPointF center) { QTransform rot; rot.rotate(-m_d->rotationAngle); m_d->flakeToWidget *= QTransform::fromTranslate(-center.x(), -center.y()); m_d->flakeToWidget *= rot; m_d->flakeToWidget *= QTransform::fromTranslate(center.x(), center.y()); m_d->rotationAngle = 0.0; correctOffsetToTransformation(); recalculateTransformations(); return m_d->documentOffset.toPoint(); } -QTransform KisCoordinatesConverter::imageToWidgetTransform() const{ +QTransform KisCoordinatesConverter::imageToWidgetTransform() const { return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget; } QTransform KisCoordinatesConverter::imageToDocumentTransform() const { return m_d->imageToDocument; } QTransform KisCoordinatesConverter::documentToFlakeTransform() const { return m_d->documentToFlake; } QTransform KisCoordinatesConverter::flakeToWidgetTransform() const { return m_d->flakeToWidget; } -QTransform KisCoordinatesConverter::documentToWidgetTransform() const{ +QTransform KisCoordinatesConverter::documentToWidgetTransform() const { return m_d->documentToFlake * m_d->flakeToWidget; } -QTransform KisCoordinatesConverter::documentToViewportTransform() const{ - return m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport; -} - QTransform KisCoordinatesConverter::viewportToWidgetTransform() const { return m_d->widgetToViewport.inverted(); } QTransform KisCoordinatesConverter::imageToViewportTransform() const { return m_d->imageToDocument * m_d->documentToFlake * m_d->flakeToWidget * m_d->widgetToViewport; } void KisCoordinatesConverter::getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *polygon, const bool scrollCheckers) const { /** * Qt has different rounding for QPainter::drawRect/drawImage. * The image is rounded mathematically, while rect in aligned * to the next integer. That causes transparent line appear on * the canvas. * * See: https://bugreports.qt.nokia.com/browse/QTBUG-22827 */ QRectF imageRect = imageRectInViewportPixels(); imageRect.adjust(0,0,-0.5,-0.5); if (scrollCheckers) { *transform = viewportToWidgetTransform(); *polygon = imageRect; *brushOrigin = imageToViewport(QPointF(0,0)); } else { *transform = QTransform(); *polygon = viewportToWidgetTransform().map(imageRect); *brushOrigin = QPoint(0,0); } } void KisCoordinatesConverter::getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const { if(scrollCheckers) { *textureTransform = QTransform(); *textureRect = QRectF(0, 0, viewportRect.width(),viewportRect.height()); } else { *textureTransform = viewportToWidgetTransform(); *textureRect = viewportRect; } *modelTransform = viewportToWidgetTransform(); *modelRect = viewportRect; } QPointF KisCoordinatesConverter::imageCenterInWidgetPixel() const { if(!m_d->image) return QPointF(); QPolygonF poly = imageToWidget(QPolygon(m_d->image->bounds())); return (poly[0] + poly[1] + poly[2] + poly[3]) / 4.0; } // these functions return a bounding rect if the canvas is rotated QRectF KisCoordinatesConverter::imageRectInWidgetPixels() const { if(!m_d->image) return QRectF(); return imageToWidget(m_d->image->bounds()); } QRectF KisCoordinatesConverter::imageRectInViewportPixels() const { if(!m_d->image) return QRectF(); return imageToViewport(m_d->image->bounds()); } QRect KisCoordinatesConverter::imageRectInImagePixels() const { if(!m_d->image) return QRect(); return m_d->image->bounds(); } QRectF KisCoordinatesConverter::imageRectInDocumentPixels() const { if(!m_d->image) return QRectF(); return imageToDocument(m_d->image->bounds()); } QSizeF KisCoordinatesConverter::imageSizeInFlakePixels() const { if(!m_d->image) return QSizeF(); qreal scaleX, scaleY; imageScale(&scaleX, &scaleY); QSize imageSize = m_d->image->size(); return QSizeF(imageSize.width() * scaleX, imageSize.height() * scaleY); } QRectF KisCoordinatesConverter::widgetRectInFlakePixels() const { return widgetToFlake(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QRectF KisCoordinatesConverter::widgetRectInImagePixels() const { return widgetToImage(QRectF(QPoint(0,0), m_d->canvasWidgetSize)); } QPointF KisCoordinatesConverter::flakeCenterPoint() const { QRectF widgetRect = widgetRectInFlakePixels(); return QPointF(widgetRect.left() + widgetRect.width() / 2, widgetRect.top() + widgetRect.height() / 2); } QPointF KisCoordinatesConverter::widgetCenterPoint() const { return QPointF(m_d->canvasWidgetSize.width() / 2.0, m_d->canvasWidgetSize.height() / 2.0); } void KisCoordinatesConverter::imageScale(qreal *scaleX, qreal *scaleY) const { if(!m_d->image) { *scaleX = 1.0; *scaleY = 1.0; return; } // get the x and y zoom level of the canvas qreal zoomX, zoomY; KoZoomHandler::zoom(&zoomX, &zoomY); // Get the KisImage resolution qreal resX = m_d->image->xRes(); qreal resY = m_d->image->yRes(); // Compute the scale factors *scaleX = zoomX / resX; *scaleY = zoomY / resY; } void KisCoordinatesConverter::imagePhysicalScale(qreal *scaleX, qreal *scaleY) const { imageScale(scaleX, scaleY); *scaleX *= m_d->devicePixelRatio; *scaleY *= m_d->devicePixelRatio; } /** * @brief Adjust a given pair of coordinates to the nearest device pixel * according to the value of `devicePixelRatio`. * @param point a point in logical pixel space * @return The point in logical pixel space but adjusted to the nearest device * pixel */ QPointF KisCoordinatesConverter::snapToDevicePixel(const QPointF &point) const { QPoint devicePixel = (point * m_d->devicePixelRatio).toPoint(); // These adjusted coords will be in logical pixel but is aligned in device // pixel space for pixel-perfect rendering. return QPointF(devicePixel) / m_d->devicePixelRatio; } diff --git a/libs/ui/canvas/kis_coordinates_converter.h b/libs/ui/canvas/kis_coordinates_converter.h index 7bd9f0b8d0..27be4283ac 100644 --- a/libs/ui/canvas/kis_coordinates_converter.h +++ b/libs/ui/canvas/kis_coordinates_converter.h @@ -1,172 +1,171 @@ /* * Copyright (c) 2010 Dmitry Kazakov * Copyright (c) 2011 Silvio Heinrich * * 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. */ #ifndef KIS_COORDINATES_CONVERTER_H #define KIS_COORDINATES_CONVERTER_H #include #include #include "kritaui_export.h" #include "kis_types.h" #define EPSILON 1e-6 #define SCALE_LESS_THAN(scX, scY, value) \ (scX < (value) - EPSILON && scY < (value) - EPSILON) #define SCALE_MORE_OR_EQUAL_TO(scX, scY, value) \ (scX > (value) - EPSILON && scY > (value) - EPSILON) namespace _Private { template struct Traits { typedef T Result; static T map(const QTransform& transform, const T& obj) { return transform.map(obj); } }; template<> struct Traits { typedef QRectF Result; static QRectF map(const QTransform& transform, const QRectF& rc) { return transform.mapRect(rc); } }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; template<> struct Traits: public Traits { }; } class KRITAUI_EXPORT KisCoordinatesConverter: public KoZoomHandler { public: KisCoordinatesConverter(); ~KisCoordinatesConverter() override; QSizeF getCanvasWidgetSize() const; void setCanvasWidgetSize(QSizeF size); void setDevicePixelRatio(qreal value); void setImage(KisImageWSP image); void setDocumentOffset(const QPointF &offset); qreal devicePixelRatio() const; QPoint documentOffset() const; qreal rotationAngle() const; QPoint rotate(QPointF center, qreal angle); QPoint mirror(QPointF center, bool mirrorXAxis, bool mirrorYAxis); bool xAxisMirrored() const; bool yAxisMirrored() const; QPoint resetRotation(QPointF center); void setZoom(qreal zoom) override; /** * A composition of to scale methods: zoom level + image resolution */ qreal effectiveZoom() const; template typename _Private::Traits::Result imageToViewport(const T& obj) const { return _Private::Traits::map(imageToViewportTransform(), obj); } template typename _Private::Traits::Result viewportToImage(const T& obj) const { return _Private::Traits::map(imageToViewportTransform().inverted(), obj); } template typename _Private::Traits::Result flakeToWidget(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToFlake(const T& obj) const { return _Private::Traits::map(flakeToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result widgetToViewport(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result viewportToWidget(const T& obj) const { return _Private::Traits::map(viewportToWidgetTransform(), obj); } template typename _Private::Traits::Result documentToWidget(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToDocument(const T& obj) const { return _Private::Traits::map(documentToWidgetTransform().inverted(), obj); } template typename _Private::Traits::Result imageToDocument(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform(), obj); } template typename _Private::Traits::Result documentToImage(const T& obj) const { return _Private::Traits::map(imageToDocumentTransform().inverted(), obj); } template typename _Private::Traits::Result documentToFlake(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform(), obj); } template typename _Private::Traits::Result flakeToDocument(const T& obj) const { return _Private::Traits::map(documentToFlakeTransform().inverted(), obj); } template typename _Private::Traits::Result imageToWidget(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform(), obj); } template typename _Private::Traits::Result widgetToImage(const T& obj) const { return _Private::Traits::map(imageToWidgetTransform().inverted(), obj); } QTransform imageToWidgetTransform() const; QTransform imageToDocumentTransform() const; QTransform documentToFlakeTransform() const; QTransform imageToViewportTransform() const; QTransform viewportToWidgetTransform() const; QTransform flakeToWidgetTransform() const; QTransform documentToWidgetTransform() const; - QTransform documentToViewportTransform() const; void getQPainterCheckersInfo(QTransform *transform, QPointF *brushOrigin, QPolygonF *poligon, const bool scrollCheckers) const; void getOpenGLCheckersInfo(const QRectF &viewportRect, QTransform *textureTransform, QTransform *modelTransform, QRectF *textureRect, QRectF *modelRect, const bool scrollCheckers) const; QPointF imageCenterInWidgetPixel() const; QRectF imageRectInWidgetPixels() const; QRectF imageRectInViewportPixels() const; QSizeF imageSizeInFlakePixels() const; QRectF widgetRectInFlakePixels() const; QRectF widgetRectInImagePixels() const; QRect imageRectInImagePixels() const; QRectF imageRectInDocumentPixels() const; QPointF flakeCenterPoint() const; QPointF widgetCenterPoint() const; void imageScale(qreal *scaleX, qreal *scaleY) const; void imagePhysicalScale(qreal *scaleX, qreal *scaleY) const; QPointF snapToDevicePixel(const QPointF &point) const; private: friend class KisZoomAndPanTest; QPointF centeringCorrection() const; void correctOffsetToTransformation(); void correctTransformationToOffset(); void recalculateTransformations(); private: struct Private; Private * const m_d; }; #endif /* KIS_COORDINATES_CONVERTER_H */