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:
HiDPIUtils.repaint( Component c );
HiDPIUtils.repaint( Component c, Rectangle r );
HiDPIUtils.repaint( Component c, int x, int y, int width, int height );
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:
HiDPIUtils.installHiDPIRepaintManager();
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).
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().
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 viaJComponent.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.