From 1d3028abc646f8da510abefdd66a02ad7c447e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=BDeljan=20Rikalo?= Date: Tue, 11 Feb 2025 13:53:37 +0000 Subject: [PATCH] Qt5,Qt6: use floating point functions for primitives drawing on scaled displays. issue #41422 (cherry-picked from commit 683afd5cbb89ac064ec1e00a7865abd2ce60785e) Co-authored-by: zeljan1 --- lcl/interfaces/qt5/qtobjects.pas | 30 ++++++++++++++++++++++++++++++ lcl/interfaces/qt5/qtwinapi.inc | 19 +++++++++++++------ lcl/interfaces/qt6/qtobjects.pas | 20 ++++++++++++++++++++ lcl/interfaces/qt6/qtwinapi.inc | 19 +++++++++++++------ 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/lcl/interfaces/qt5/qtobjects.pas b/lcl/interfaces/qt5/qtobjects.pas index a327f897c8..6e0ad782e0 100644 --- a/lcl/interfaces/qt5/qtobjects.pas +++ b/lcl/interfaces/qt5/qtobjects.pas @@ -464,6 +464,7 @@ type function getDeviceSize: TPoint; function getRegionType(ARegion: QRegionH): integer; function getClipRegion: TQtRegion; + function preferFloatingPointDrawingFunctions: boolean; {when screen is scaled we use drawLineF, drawRectF etc.} procedure setClipping(const AValue: Boolean); procedure setClipRect(const ARect: TRect); procedure setClipRegion(ARegion: QRegionH; AOperation: QtClipOperation = QtReplaceClip); @@ -3377,6 +3378,35 @@ begin Result := vRegion; end; +function TQtDeviceContext.preferFloatingPointDrawingFunctions: boolean; +const + AEpsilon: double = 0.01; +var + APixelRatio: QReal; + AWindow: QWindowH; + AWidget: QWidgetH; + AScreen: QScreenH; +begin + Result := False; + if Assigned(Parent) then + begin + AWindow := QWidget_windowHandle(Parent); + if AWindow = nil then + begin + AWidget := QWidget_topLevelWidget(Parent); + AWindow := QWidget_windowHandle(AWidget); + if AWindow = nil then + exit; + end; + AScreen := QWindow_screen(AWindow); + if AScreen = nil then + exit; + APixelRatio := QScreen_devicePixelRatio(AScreen); + if Abs(APixelRatio - 1.00) > AEpsilon then + exit(True); + end; +end; + procedure TQtDeviceContext.setClipping(const AValue: Boolean); begin QPainter_setClipping(Widget, AValue); diff --git a/lcl/interfaces/qt5/qtwinapi.inc b/lcl/interfaces/qt5/qtwinapi.inc index 3a6df3376a..aaeea1bd66 100644 --- a/lcl/interfaces/qt5/qtwinapi.inc +++ b/lcl/interfaces/qt5/qtwinapi.inc @@ -4860,7 +4860,11 @@ begin Pt := TQtCustomControl(AHandle).viewport.ScrolledOffset; Types.OffsetRect(Rect^, -Pt.X, -Pt.Y); end; - + if (Rect^.Width <= 0) or (Rect^.Height <= 0) then + begin + //DebugLn('WARNING: TQtWidgetSet.InvalidateRect() Rect is null ',dbgs(Rect^),' LCL=',dbgsName(TQtWidget(AHandle).LCLObject)); + exit(True); + end; // no need to handle bErase. Qt automatically erase rect on paint event according to docs TQtWidget(aHandle).Update(Rect); end else @@ -4930,8 +4934,9 @@ begin OldBkMode := SetBkMode(DC, TRANSPARENT); if TQtDeviceContext(DC).pen.getCosmetic then LastPos := TQtDeviceContext(DC).GetLineLastPixelPos(PenPos, LastPos); - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then - TQtDeviceContext(DC).drawLineF(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y) + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then + TQtDeviceContext(DC).drawLineF(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y) else TQtDeviceContext(DC).drawLine(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y); @@ -5285,7 +5290,8 @@ begin Result := IsValidDC(DC); if Result then begin - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then begin GetMem(QtPointsF, NumPts * SizeOf(TQtPointF)); for i := 0 to NumPts - 1 do @@ -5371,8 +5377,9 @@ begin R := NormalizeRect(Rect(X1, Y1, X2, Y2)); if IsRectEmpty(R) then Exit(True); - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then - TQtDeviceContext(DC).drawRectF(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1) + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then + TQtDeviceContext(DC).drawRectF(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1) else TQtDeviceContext(DC).drawRect(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1); Result := True; diff --git a/lcl/interfaces/qt6/qtobjects.pas b/lcl/interfaces/qt6/qtobjects.pas index 841e970c73..b5f1d860ab 100644 --- a/lcl/interfaces/qt6/qtobjects.pas +++ b/lcl/interfaces/qt6/qtobjects.pas @@ -471,6 +471,7 @@ type function getDeviceSize: TPoint; function getRegionType(ARegion: QRegionH): integer; function getClipRegion: TQtRegion; + function preferFloatingPointDrawingFunctions: boolean; {when screen is scaled we use drawLineF, drawRectF etc.} procedure setClipping(const AValue: Boolean); procedure setClipRect(const ARect: TRect); procedure setClipRegion(ARegion: QRegionH; AOperation: QtClipOperation = QtReplaceClip); @@ -3535,6 +3536,25 @@ begin Result := vRegion; end; +function TQtDeviceContext.preferFloatingPointDrawingFunctions: boolean; +const + AEpsilon: double = 0.01; +var + APixelRatio: QReal; + AScreen: QScreenH; +begin + Result := False; + if Assigned(Parent) then + begin + AScreen := QWidget_screen(Parent); + if AScreen = nil then + exit; + APixelRatio := QScreen_devicePixelRatio(AScreen); + if Abs(APixelRatio - 1.00) > AEpsilon then + exit(True); + end; +end; + procedure TQtDeviceContext.setClipping(const AValue: Boolean); begin QPainter_setClipping(Widget, AValue); diff --git a/lcl/interfaces/qt6/qtwinapi.inc b/lcl/interfaces/qt6/qtwinapi.inc index 1aa1524703..af9231ad5b 100644 --- a/lcl/interfaces/qt6/qtwinapi.inc +++ b/lcl/interfaces/qt6/qtwinapi.inc @@ -4870,7 +4870,11 @@ begin Pt := TQtCustomControl(AHandle).viewport.ScrolledOffset; Types.OffsetRect(Rect^, -Pt.X, -Pt.Y); end; - + if (Rect^.Width <= 0) or (Rect^.Height <= 0) then + begin + //DebugLn('WARNING: TQtWidgetSet.InvalidateRect() Rect is null ',dbgs(Rect^),' LCL=',dbgsName(TQtWidget(AHandle).LCLObject)); + exit(True); + end; // no need to handle bErase. Qt automatically erase rect on paint event according to docs TQtWidget(aHandle).Update(Rect); end else @@ -4940,8 +4944,9 @@ begin OldBkMode := SetBkMode(DC, TRANSPARENT); if TQtDeviceContext(DC).pen.getCosmetic then LastPos := TQtDeviceContext(DC).GetLineLastPixelPos(PenPos, LastPos); - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then - TQtDeviceContext(DC).drawLineF(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y) + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then + TQtDeviceContext(DC).drawLineF(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y) else TQtDeviceContext(DC).drawLine(PenPos.X, PenPos.Y, LastPos.X, LastPos.Y); if TQtDeviceContext(DC).pen.getStyle = QtCustomDashLine then @@ -5322,7 +5327,8 @@ begin Result := IsValidDC(DC); if Result then begin - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then begin GetMem(QtPointsF, NumPts * SizeOf(TQtPointF)); for i := 0 to NumPts - 1 do @@ -5408,8 +5414,9 @@ begin R := NormalizeRect(Rect(X1, Y1, X2, Y2)); if IsRectEmpty(R) then Exit(True); - if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) then - TQtDeviceContext(DC).drawRectF(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1) + if QPainter_testRenderHint(TQtDeviceContext(DC).Widget, QPainterAntialiasing) or + TQtDeviceContext(DC).preferFloatingPointDrawingFunctions then + TQtDeviceContext(DC).drawRectF(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1) else TQtDeviceContext(DC).drawRect(R.Left, R.Top, R.Right - R.Left - 1, R.Bottom - R.Top - 1); Result := True;