JDK-6668436 : Painting a component to an off-screen image should not affect the double buffer
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 6,6u10,7
  • Priority: P3
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic,windows_xp
  • CPU: generic,x86
  • Submitted: 2008-02-27
  • Updated: 2011-03-29
  • Resolved: 2011-03-29
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 6
6-poolResolved
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Relates :  
Description
When implementing visual effects with Swing
it's often necessary to paint a component to an off-screen buffer

unfortunately it causes problems if this component or any of its children 
are double-buffered

Run the following test and see the unexpected visual artefacts

JDK 6u10 or previous version

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;

public class Test {

    private static void createGui() {
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // panel is double buffered
        final JPanel panel = new JPanel();
        panel.setSize(100, 100);

        JButton b = new JButton("Test") {

            protected void paintComponent(Graphics g) {
                super.paintComponent(g);

                // Note - the panel is not added to the frame
                BufferedImage im = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
                panel.paint(im.getGraphics());
            }
        };
        frame.add(b);

        frame.setSize(200, 200);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                Test.createGui();
            }
        });
    }
}

Comments
EVALUATION The requested feature is not supported by design, closed
29-03-2011

WORK AROUND Turn off double buffering for all the components painted out of order (i.e. not from their paint()/paintComponent() methods).
28-09-2010

EVALUATION This bug is about something that Swing painting system is not supported from the beginning, in other words the Swing painting is not re-entrant and breaking the common painting mechanism by calling paint() of stranger component inside another component's paint() leads to unpredictable result I submitted this bug some time ago but now I can see that there is no serious reasons to spend lots of time for this, because what we actually need is to fix 6683775 for which we found an elegant and robust fix this bug is downgraded to P5, 6683775 is reopened
30-12-2008

EVALUATION Here is a rough sequence of calls causing NPE: 1. RepaintManager processes all the dirty components on EDT and calls (indirectly) paint() for JButton b. 2. Button paints itself with the call super.paint(g). 3. super.paint(g) calls RepaintManager's methods beginPaint, paint, endPaint - see JComponent.paint() (~line 1024). 4. RepaintManager.paint() forwards all the painting to its paintManager, which is an instance of BufferStrategyPaintManager (BSPM). 5. BSPM.paint() successfully paints the button into its bufferStrategy. 6. Button paints the panel to offscreen image - see the test code. 7. Steps 3-4 are repeated for the panel. 8. BSPM.paint() calls to prepare(), which nullify 'bufferStrategy' field and calls to fetchRoot(). The latter method returns false, because the panel is outside of any Swing hierarchy, and default paint() is used instead of using bufferStrategy. Here the problem is: while traversing all the parents for component in fetchRoot(), BSPM fields 'root', 'rootJ' and 'xOffset/yOffset' are used and modified(!) regardless of the (future) return value of fetchRoot(). The same problem observed in prepare(): 'bufferStrategy' is nullified regardless of return value from fetchRoot(). 9. RepaintManager.endPaint() forwards to BSPM.endPaint(), which calls to flushAccumulatedRegion(). 10. flushAccumulatedRegion throws NPE as 'bufferStrategy' field is null.
22-12-2008

SUGGESTED FIX diff -r d5bf2dd61ed5 src/share/classes/javax/swing/BufferStrategyPaintManager.java --- a/src/share/classes/javax/swing/BufferStrategyPaintManager.java Fri Dec 19 16:04:04 2008 +0300 +++ b/src/share/classes/javax/swing/BufferStrategyPaintManager.java Mon Dec 22 13:38:51 2008 +0300 @@ -513,8 +513,8 @@ class BufferStrategyPaintManager extends bsg.dispose(); bsg = null; } - bufferStrategy = null; if (fetchRoot(c)) { + bufferStrategy = null; boolean contentsLost = false; BufferInfo bufferInfo = getBufferInfo(root); if (bufferInfo == null) { @@ -576,19 +576,21 @@ class BufferStrategyPaintManager extends private boolean fetchRoot(JComponent c) { boolean encounteredHW = false; - rootJ = c; - root = c; - xOffset = yOffset = 0; - while (root != null && (!(root instanceof Window) && - !(root instanceof Applet))) { - xOffset += root.getX(); - yOffset += root.getY(); - root = root.getParent(); - if (root != null) { - if (root instanceof JComponent) { - rootJ = (JComponent)root; + // Fix for 6668436: use local vars for root, rootJ and x/yOffset and copy + // their values to corresponding fields only on successful search. + Container tempRoot = c; + JComponent tempRootJ = c; + int tempxOffset = 0, tempyOffset = 0; + while (tempRoot != null && (!(tempRoot instanceof Window) && + !(tempRoot instanceof Applet))) { + tempxOffset += tempRoot.getX(); + tempyOffset += tempRoot.getY(); + tempRoot = tempRoot.getParent(); + if (tempRoot != null) { + if (tempRoot instanceof JComponent) { + tempRootJ = (JComponent)tempRoot; } - else if (!root.isLightweight()) { + else if (!tempRoot.isLightweight()) { if (!encounteredHW) { encounteredHW = true; } @@ -608,17 +610,21 @@ class BufferStrategyPaintManager extends } } } - if ((root instanceof RootPaneContainer) && - (rootJ instanceof JRootPane)) { + if ((tempRoot instanceof RootPaneContainer) && + (tempRootJ instanceof JRootPane)) { // We're in a Swing heavyeight (JFrame/JWindow...), use double // buffering if double buffering enabled on the JRootPane and // the JRootPane wants true double buffering. - if (rootJ.isDoubleBuffered() && - ((JRootPane)rootJ).getUseTrueDoubleBuffering()) { + if (tempRootJ.isDoubleBuffered() && + ((JRootPane)tempRootJ).getUseTrueDoubleBuffering()) { // Whether or not a component is double buffered is a // bit tricky with Swing. This gives a good approximation // of the various ways to turn on double buffering for // components. + xOffset = tempxOffset; + yOffset = tempyOffset; + root = tempRoot; + rootJ = tempRootJ; return true; } }
22-12-2008

EVALUATION I see the following exception in console when running the test with 7.0-b42: Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException at javax.swing.BufferStrategyPaintManager.flushAccumulatedRegion(BufferStrategyPaintManager.java:423) at javax.swing.BufferStrategyPaintManager.endPaint(BufferStrategyPaintManager.java:387) at javax.swing.RepaintManager.endPaint(RepaintManager.java:1253) at javax.swing.JComponent.paint(JComponent.java:1031) at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39) at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:78) at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:115) at java.awt.Container.paint(Container.java:1785) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:786) at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:731) at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:711) at javax.swing.RepaintManager.access$700(RepaintManager.java:59) at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1594) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:235) at java.awt.EventQueue.dispatchEvent(EventQueue.java:603) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:286) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:201) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:191) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:186) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:178) at java.awt.EventDispatchThread.run(EventDispatchThread.java:139) NPE is caused by null 'bufferStrategy' field in BufferStrategyPaintManager - see flushAccumulatedRegion method for details.
22-12-2008

EVALUATION We should switch off the doulbe buffering when painting to an off-screen image
27-02-2008