United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-6668436 : Painting a component to an off-screen image should not affect the double buffer

Details
Type:
Bug
Submit Date:
2008-02-27
Status:
Closed
Updated Date:
2011-03-29
Project Name:
JDK
Resolved Date:
2011-03-29
Component:
client-libs
OS:
generic,windows_xp
Sub-Component:
javax.swing
CPU:
x86,generic
Priority:
P3
Resolution:
Won't Fix
Affected Versions:
6,6u10,7
Fixed Versions:
7

Related Reports
Backport:
Duplicate:
Duplicate:
Duplicate:
Relates:

Sub Tasks

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

We should switch off the doulbe buffering
when painting to an off-screen image
                                     
2008-02-27
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;
             }
         }
                                     
2008-12-22
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.
                                     
2008-12-22
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.
                                     
2008-12-22
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
                                     
2008-12-30
WORK AROUND

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

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



Hardware and Software, Engineered to Work Together