HiDPI: fix incomplete component repainting at 125% or 175% scaling on Windows #864
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There is a problem in Swing, when using scale factors that end on .25 or .75
(e.g. 1.25, 1.75, 2.25, etc) and repainting single components.
Sometimes, under special conditions, the right and/or bottom 1px edge of the component is not repainted.
Following screenshot shows examples where the right and/or bottom 1px edge of a component was not repainted:
This PR introduces new repaint methods that fix/workaround the issue:
fixes issues #860 and #582
Repaint Manager
There is also a repaint manager, that also fixes/workarounds the issue,
but does not need any change to existing code (no need to use
HiDPIUtils.repaint()
).This can be useful for custom or 3rd party components.
Invoke following on application startup to use it:
Be careful if already using a custom repaint manager,
or if using a library that may use a custom repaint manager (e.g. SwingX for translucent JXPanel).
In-depth analysis of the Swing issue
When repainting a component using
Component.repaint()
,the component is first painted to an in-memory image,
and then that image is copied to the screen.
See javax.swing.RepaintManager.PaintManager.paintDoubleBufferedFPScales().
There are two clipping rectangles involved when copying the image to the screen:
sun.java2d.SunGraphics2D.devClip and sun.java2d.SunGraphics2D.usrClip.
devClip
is the device clipping in physical pixels.It gets the bounds of the painting component, which is either the passed component,
or if it is non-opaque, then the first opaque ancestor of the passed component.
It is calculated in sun.java2d.SunGraphics2D.constrain() while
getting a graphics context via
JComponent.getGraphics()
.usrClip
is the user clipping, which is set viaGraphics
clipping methods.This is done in javax.swing.RepaintManager.PaintManager.paintDoubleBufferedFPScales().
The intersection of
devClip
andusrClip
(computed in sun.java2d.SunGraphics2D.validateCompClip())
is used to copy the image to the screen.
Unfortunately different scaling/rounding strategies are used to calculate
the two clipping rectangles, which is the reason of the issue.
devClip
(see sun.java2d.SunGraphics2D.constrain()):usrClip
(see javax.swing.RepaintManager.PaintManager.paintDoubleBufferedFPScales()):X/Y coordinates are always rounded down for
devClip,
but rounded up forusrClip.
Width/height calculation is also different.
Conclusion
devClip
is sometimes 1px too small.Workaround
Do repaint on an (larger) ancestor.
In this case,
devClip
is larger because it gets the bounds of the ancestor.