Skip to content

Commit

Permalink
Fix rare painting glitch with image rotated 90 degrees
Browse files Browse the repository at this point in the history
  • Loading branch information
jdpurcell committed Jan 1, 2025
1 parent 3c68fb9 commit 04e7c6b
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 25 deletions.
56 changes: 33 additions & 23 deletions src/qvgraphicsview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ void QVGraphicsView::goToFile(const GoToFileMode &mode, int index)

QSizeF QVGraphicsView::getEffectiveOriginalSize() const
{
return getTransformWithNoScaling().mapRect(QRectF(QPoint(), getCurrentFileDetails().loadedPixmapSize)).size() * getDpiAdjustment();
return getUnspecializedTransform().mapRect(QRectF(QPoint(), getCurrentFileDetails().loadedPixmapSize)).size() * getDpiAdjustment();
}

LogicalPixelFitter QVGraphicsView::getPixelFitter() const
Expand All @@ -595,18 +595,12 @@ QRect QVGraphicsView::getContentRect() const
// which introduces a rounding error to begin with, and even worse, the error will be magnified if we're in the
// the process of zooming in and haven't re-applied the expensive scaling yet. If that's the case, callers need
// to know what the content rect will be once the dust settles rather than what's being temporarily displayed.
const QRectF loadedPixmapBoundingRect = QRectF(QPoint(), getCurrentFileDetails().loadedPixmapSize);
const qreal effectiveTransformScale = zoomLevel * appliedDpiAdjustment;
const QTransform effectiveTransform = getTransformWithNoScaling().scale(effectiveTransformScale, effectiveTransformScale);
const QRectF contentRect = effectiveTransform.mapRect(loadedPixmapBoundingRect);
const QSize snappedSize = getPixelFitter().snapSize(contentRect.size());
const bool isHorizontalReversed = effectiveTransform.m11() < 0 || effectiveTransform.m21() < 0;
const bool isVerticalReversed = effectiveTransform.m22() < 0 || effectiveTransform.m12() < 0;
const QPointF topLeftCorrection = QPointF(
isHorizontalReversed ? contentRect.width() - snappedSize.width() : 0,
isVerticalReversed ? contentRect.height() - snappedSize.height() : 0
);
return QRect((contentRect.topLeft() + topLeftCorrection).toPoint(), snappedSize);
const QSizeF pixmapSize = getCurrentFileDetails().loadedPixmapSize;
const QRectF pixmapBoundingRect = QRectF(QPoint(), pixmapSize);
const qreal pixmapScale = zoomLevel * appliedDpiAdjustment;
const QTransform pixmapTransform = normalizeTransformOrigin(getUnspecializedTransform().scale(pixmapScale, pixmapScale), pixmapSize);
const QRectF contentRect = pixmapTransform.mapRect(pixmapBoundingRect);
return QRect(contentRect.topLeft().toPoint(), getPixelFitter().snapSize(contentRect.size()));
}

QRect QVGraphicsView::getUsableViewportRect() const
Expand All @@ -616,24 +610,40 @@ QRect QVGraphicsView::getUsableViewportRect() const
return rect;
}

void QVGraphicsView::setTransformScale(qreal value)
void QVGraphicsView::setTransformScale(const qreal value)
{
setTransform(getTransformWithNoScaling().scale(value, value));
setTransformWithNormalization(getUnspecializedTransform().scale(value, value));
}

QTransform QVGraphicsView::getTransformWithNoScaling() const
void QVGraphicsView::setTransformWithNormalization(const QTransform &matrix)
{
setTransform(normalizeTransformOrigin(matrix, loadedPixmapItem->boundingRect().size()));
}

QTransform QVGraphicsView::getUnspecializedTransform() const
{
// Returns a transform that represents the currently applied mirroring, flipping, and rotation
// (only in increments of 90 degrees) operations, but with no scaling or translation.
const QTransform t = transform();
// Only intended to handle combinations of scaling, mirroring, flipping, and rotation
// in increments of 90 degrees. A seemingly simpler approach would be to scale the
// transform by the inverse of its scale factor, but the resulting scale factor may
// not exactly equal 1 due to floating point rounding errors.
if (t.type() == QTransform::TxRotate)
return { 0, t.m12() < 0 ? -1.0 : 1.0, t.m21() < 0 ? -1.0 : 1.0, 0, 0, 0 };
else
return { t.m11() < 0 ? -1.0 : 1.0, 0, 0, t.m22() < 0 ? -1.0 : 1.0, 0, 0 };
}

QTransform QVGraphicsView::normalizeTransformOrigin(const QTransform &matrix, const QSizeF &pixmapSize) const
{
// This applies translation to compensate for mirroring, flipping, and rotation to ensure that
// a pixmap will have its resulting top left at 0, 0. In theory this shouldn't matter, but it
// works around a glitch where Qt sometimes won't paint the last pixel on the right of the
// viewport if an image is rotated 90 degrees and just touching the right edge.
const int rtlFlip = isRightToLeft() ? -1 : 1;
const int horizontalFactor = matrix.m11() < 0 ? -1 * rtlFlip : matrix.m12() < 0 ? -1 : 0;
const int verticalFactor = matrix.m22() < 0 ? -1 : matrix.m21() < 0 ? -1 * rtlFlip : 0;
QTransform t { matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), 0, 0 };
return t.translate(pixmapSize.width() * horizontalFactor, pixmapSize.height() * verticalFactor);
}

qreal QVGraphicsView::getDpiAdjustment() const
{
// Although inverting this potentially introduces a rounding error, it is inevitable. For
Expand Down Expand Up @@ -736,17 +746,17 @@ void QVGraphicsView::rotateImage(const int relativeAngle)
{
const QTransform t = transform();
const bool isMirroredOrFlipped = t.isRotating() ? (t.m12() < 0 == t.m21() < 0) : (t.m11() < 0 != t.m22() < 0);
rotate(relativeAngle * (isMirroredOrFlipped ? -1 : 1));
setTransformWithNormalization(transform().rotate(relativeAngle * (isMirroredOrFlipped ? -1 : 1)));
}

void QVGraphicsView::mirrorImage()
{
const int rotateCorrection = transform().isRotating() ? -1 : 1;
scale(-1 * rotateCorrection, 1 * rotateCorrection);
setTransformWithNormalization(transform().scale(-1 * rotateCorrection, 1 * rotateCorrection));
}

void QVGraphicsView::flipImage()
{
const int rotateCorrection = transform().isRotating() ? -1 : 1;
scale(1 * rotateCorrection, -1 * rotateCorrection);
setTransformWithNormalization(transform().scale(1 * rotateCorrection, -1 * rotateCorrection));
}
8 changes: 6 additions & 2 deletions src/qvgraphicsview.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,13 @@ class QVGraphicsView : public QGraphicsView

QRect getUsableViewportRect() const;

void setTransformScale(qreal absoluteScale);
void setTransformScale(const qreal absoluteScale);

QTransform getTransformWithNoScaling() const;
void setTransformWithNormalization(const QTransform &matrix);

QTransform getUnspecializedTransform() const;

QTransform normalizeTransformOrigin(const QTransform &matrix, const QSizeF &pixmapSize) const;

qreal getDpiAdjustment() const;

Expand Down

0 comments on commit 04e7c6b

Please sign in to comment.