diff --git a/libs/ui/canvas/kis_coordinates_converter.cpp b/libs/ui/canvas/kis_coordinates_converter.cpp --- a/libs/ui/canvas/kis_coordinates_converter.cpp +++ b/libs/ui/canvas/kis_coordinates_converter.cpp @@ -27,6 +27,8 @@ #include #include +#include + struct KisCoordinatesConverter::Private { Private(): @@ -152,12 +154,25 @@ void KisCoordinatesConverter::setCanvasWidgetSize(QSize size) { - m_d->canvasWidgetSize = size; + // The given widget size is in widget logical pixels. Here we adjust it to + // reflect the actual viewport size in case devicePixelRatio is fractional. + // FIXME: It might be more appropriate to do this rounding in the canvas classes... + int viewportWidth = static_cast(size.width() * m_d->devicePixelRatio); + int viewportHeight = static_cast(size.height() * m_d->devicePixelRatio); + // The adjusted size will be in logical pixel but aligned to device pixels. + qreal pixelPerfectWidth = viewportWidth / m_d->devicePixelRatio; + qreal pixelPerfectHeight = viewportHeight / m_d->devicePixelRatio; + m_d->canvasWidgetSize = QSizeF(pixelPerfectWidth, pixelPerfectHeight); + qDebug() << "KisCoordinatesConverter::setCanvasWidgetSize" + << "size:" << size + << "adjusted size:" << m_d->canvasWidgetSize; recalculateTransformations(); } void KisCoordinatesConverter::setDevicePixelRatio(qreal value) { + qDebug() << "KisCoordinatesConverter::setDevicePixelRatio," + << "value:" << m_d->devicePixelRatio << "to" << value; m_d->devicePixelRatio = value; } @@ -169,10 +184,28 @@ void KisCoordinatesConverter::setDocumentOffset(const QPoint& offset) { - QPointF diff = m_d->documentOffset - offset; - - m_d->documentOffset = offset; + // The given offset is in widget logical pixels. In order to prevent fuzzy + // canvas rendering at 100% pixel-perfect zoom level when devicePixelRatio + // is not integral, we adjusts the offset to map to whole device pixels. + // We use qFloor here since the offset can be negative. + int deviceOffsetX = qFloor(offset.x() * m_d->devicePixelRatio); + int deviceOffsetY = qFloor(offset.y() * m_d->devicePixelRatio); + // These adjusted offsets will be in logical pixel but is aligned in device + // pixel space for pixel-perfect rendering. + qreal pixelPerfectOffsetX = deviceOffsetX / m_d->devicePixelRatio; + qreal pixelPerfectOffsetY = deviceOffsetY / m_d->devicePixelRatio; + // FIXME: This is a temporary hack for fixing the canvas under fractional + // DPI scaling before a new coordinate system is introduced. + QPointF offsetAdjusted(pixelPerfectOffsetX, pixelPerfectOffsetY); + QPointF diff = m_d->documentOffset - offsetAdjusted; + + m_d->documentOffset = offsetAdjusted; m_d->flakeToWidget *= QTransform::fromTranslate(diff.x(), diff.y()); + qDebug() << "KisCoordinatesConverter::setDocumentOffset," + << "offset:" << offset + << "offsetAdjusted:" << offsetAdjusted + << "documentOffset:" << m_d->documentOffset + << "flakeToWidget:" << m_d->flakeToWidget; recalculateTransformations(); } diff --git a/libs/ui/opengl/kis_opengl_canvas2.h b/libs/ui/opengl/kis_opengl_canvas2.h --- a/libs/ui/opengl/kis_opengl_canvas2.h +++ b/libs/ui/opengl/kis_opengl_canvas2.h @@ -119,6 +119,8 @@ void drawImage(); void drawCheckers(); void drawGrid(); + QSize viewportDevicePixelSize() const; + QSizeF widgetSizeAlignedToDevicePixel() const; private: diff --git a/libs/ui/opengl/kis_opengl_canvas2.cpp b/libs/ui/opengl/kis_opengl_canvas2.cpp --- a/libs/ui/opengl/kis_opengl_canvas2.cpp +++ b/libs/ui/opengl/kis_opengl_canvas2.cpp @@ -421,10 +421,14 @@ return; } + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + // setup the mvp transformation QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->flakeToWidgetTransform()); @@ -521,9 +525,10 @@ QRectF textureRect; QRectF modelRect; + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); QRectF viewportRect = !d->wrapAroundMode ? converter->imageRectInViewportPixels() : - converter->widgetToViewport(this->rect()); + converter->widgetToViewport(QRectF(0, 0, widgetSize.width(), widgetSize.height())); if (!canvas()->renderingLimit().isEmpty()) { const QRect vrect = converter->imageToViewport(canvas()->renderingLimit()).toAlignedRect(); @@ -543,7 +548,9 @@ QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(modelTransform); @@ -591,9 +598,13 @@ return; } + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(coordinatesConverter()->imageToWidgetTransform()); @@ -613,7 +624,7 @@ d->lineBuffer.bind(); } - QRectF widgetRect(0,0, width(), height()); + QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = coordinatesConverter()->documentToImage(coordinatesConverter()->widgetToDocument(widgetRect)); QRect wr = widgetRectInImagePixels.toAlignedRect(); @@ -666,9 +677,13 @@ d->displayShader->bind(); + QSizeF widgetSize = widgetSizeAlignedToDevicePixel(); + QMatrix4x4 projectionMatrix; projectionMatrix.setToIdentity(); - projectionMatrix.ortho(0, width(), height(), 0, NEAR_VAL, FAR_VAL); + // FIXME: It may be better to have the projection in device pixel, but + // this requires introducing a new coordinate system. + projectionMatrix.ortho(0, widgetSize.width(), widgetSize.height(), 0, NEAR_VAL, FAR_VAL); // Set view/projection matrices QMatrix4x4 modelMatrix(converter->imageToWidgetTransform()); @@ -680,7 +695,7 @@ textureMatrix.setToIdentity(); d->displayShader->setUniformValue(d->displayShader->location(Uniform::TextureMatrix), textureMatrix); - QRectF widgetRect(0,0, width(), height()); + QRectF widgetRect(0,0, widgetSize.width(), widgetSize.height()); QRectF widgetRectInImagePixels = converter->documentToImage(converter->widgetToDocument(widgetRect)); const QRect renderingLimit = canvas()->renderingLimit(); @@ -838,6 +853,23 @@ glDisable(GL_BLEND); } +QSize KisOpenGLCanvas2::viewportDevicePixelSize() const +{ + // This is how QOpenGLCanvas sets the FBO and the viewport size. If + // devicePixelRatioF() is non-integral, the result is truncated. + int viewportWidth = static_cast(width() * devicePixelRatioF()); + int viewportHeight = static_cast(height() * devicePixelRatioF()); + return QSize(viewportWidth, viewportHeight); +} + +QSizeF KisOpenGLCanvas2::widgetSizeAlignedToDevicePixel() const +{ + QSize viewportSize = viewportDevicePixelSize(); + qreal scaledWidth = viewportSize.width() / devicePixelRatioF(); + qreal scaledHeight = viewportSize.height() / devicePixelRatioF(); + return QSizeF(scaledWidth, scaledHeight); +} + void KisOpenGLCanvas2::slotConfigChanged() { KisConfig cfg(true);