While working with Escher I found some bugs in the Swing painting. Strange enough they didn't show up on the Cairo peers, supposedly because CairoGraphics2D is more relaxed on hitClip() and returns true even when it probably shouldn't. Yes this bug was real anyway, and yes it's fixed now.

Another thing I did was get rid of that clunky paintChildrenWithOverlap thing. I put in some measurements and found that the overhead of doing all this fragmentation of paint rectangles isn't worth all the effort. What I do now is 'simply' check if child components are _completely_ obscured and don't paint those who are. Child components that are partially obscured are painted normally one after another and now the painting of overlapping components is also very fast.

This stuff works with both the Escher and Cairo peers now. :-)

2006-07-19  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/JComponent.java
        (paintChildren): Refactored. The paintChildrenOptimized method
        has been moved back in here. Added locking of the tree and
        only check for completely obscured child components
        when not optimized drawing enabled. Use Graphics.create() to
        protect from irreversible changes.
        (isCompletelyObscured): New helper method.
        (paintComponent): Also use Graphics.create() for Graphics2D,
        to protect from irreverible changes.
        (clipAndTranslateGraphics): Refactored to use more efficient
        iterative (vs recursive) approach.
        * javax/swing/RepaintManager.java
        (getOffscreenBuffer): Create image from root component.

/Roman
Index: javax/swing/JComponent.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/JComponent.java,v
retrieving revision 1.139
diff -u -1 -2 -r1.139 JComponent.java
--- javax/swing/JComponent.java	7 Jul 2006 12:41:53 -0000	1.139
+++ javax/swing/JComponent.java	19 Jul 2006 19:33:44 -0000
@@ -39,48 +39,45 @@
 package javax.swing;
 
 import java.applet.Applet;
 import java.awt.AWTEvent;
 import java.awt.Color;
 import java.awt.Component;
 import java.awt.Container;
 import java.awt.Dimension;
 import java.awt.EventQueue;
 import java.awt.FocusTraversalPolicy;
 import java.awt.Font;
 import java.awt.Graphics;
-import java.awt.Graphics2D;
 import java.awt.Image;
 import java.awt.Insets;
 import java.awt.Point;
 import java.awt.Rectangle;
-import java.awt.Shape;
 import java.awt.Window;
 import java.awt.dnd.DropTarget;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ContainerEvent;
 import java.awt.event.ContainerListener;
 import java.awt.event.FocusEvent;
 import java.awt.event.FocusListener;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseEvent;
 import java.awt.peer.LightweightPeer;
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import java.beans.PropertyVetoException;
 import java.beans.VetoableChangeListener;
 import java.beans.VetoableChangeSupport;
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.EventListener;
 import java.util.Hashtable;
 import java.util.Locale;
 import java.util.Set;
 
 import javax.accessibility.Accessible;
 import javax.accessibility.AccessibleContext;
 import javax.accessibility.AccessibleExtendedComponent;
 import javax.accessibility.AccessibleKeyBinding;
 import javax.accessibility.AccessibleRole;
 import javax.accessibility.AccessibleState;
 import javax.accessibility.AccessibleStateSet;
@@ -1931,270 +1928,123 @@
    * component's body and border.
    *
    * @param g The graphics context with which to paint the children
    *
    * @see #paint
    * @see #paintBorder
    * @see #paintComponent
    */
   protected void paintChildren(Graphics g)
   {
     if (getComponentCount() > 0)
       {
-        if (isOptimizedDrawingEnabled())
-          paintChildrenOptimized(g);
-        else
-          paintChildrenWithOverlap(g);
-      }
-  }
-
-  /**
-   * Paints the children of this JComponent in the case when the component
-   * is not marked as optimizedDrawingEnabled, that means the container cannot
-   * guarantee that it's children are tiled. For this case we must
-   * perform a more complex optimization to determine the minimal rectangle
-   * to be painted for each child component.
-   *
-   * @param g the graphics context to use
-   */
-  private void paintChildrenWithOverlap(Graphics g)
-  {
-    Shape originalClip = g.getClip();
-    Rectangle inner = SwingUtilities.calculateInnerArea(this, rectCache);
-    g.clipRect(inner.x, inner.y, inner.width, inner.height);
-
-    // Find the rectangles that need to be painted for each child component.
-    // We push on this list arrays that have the Rectangles to be painted as
-    // the first elements and the component to be painted as the last one.
-    // Later we go through that list in reverse order and paint the rectangles.
-    int numChildren = getComponentCount();
-    ArrayList paintRegions = new ArrayList(numChildren);
-    ArrayList paintRectangles = new ArrayList();
-    ArrayList newPaintRects = new ArrayList();
-    paintRectangles.add(g.getClipBounds());
-    ArrayList componentRectangles = new ArrayList();
-
-    // Go through children from top to bottom and find out their paint
-    // rectangles.
-    for (int index = 0; paintRectangles.size() > 0 && index < numChildren; index++)
-      {
-        Component comp = getComponent(index);
-        if (! comp.isVisible() || ! comp.isLightweight())
-          continue;
-
-        Rectangle compBounds = comp.getBounds();
-        boolean isOpaque = comp.isOpaque();
-
-        // Add all the current paint rectangles that intersect with the
-        // component to the component's paint rectangle array.
-        for (int i = paintRectangles.size() - 1; i >= 0; i--)
+        // Need to lock the tree to avoid problems with AWT and concurrency.
+        synchronized (getTreeLock())
           {
-            Rectangle r = (Rectangle) paintRectangles.get(i);
-            if (r.intersects(compBounds))
+            for (int i = getComponentCount() - 1; i >= 0; i--)
               {
-                Rectangle compRect = r.intersection(compBounds);
-                componentRectangles.add(compRect);
-                // If the component is opaque, split up each paint rect and
-                // add paintRect - compBounds to the newPaintRects array.
-                if (isOpaque)
+                Component child = getComponent(i);
+                if (child != null && child.isLightweight()
+                    && child.isVisible())
                   {
-                    int x, y, w, h;
-                    Rectangle rect = new Rectangle();
-
-                    // The north rectangle.
-                    x = Math.max(compBounds.x, r.x);
-                    y = r.y;
-                    w = Math.min(compBounds.width, r.width + r.x - x);
-                    h = compBounds.y - r.y;
-                    rect.setBounds(x, y, w, h);
-                    if (! rect.isEmpty())
+                    int cx = child.getX();
+                    int cy = child.getY();
+                    int cw = child.getWidth();
+                    int ch = child.getHeight();
+                    if (g.hitClip(cx, cy, cw, ch))
                       {
-                        newPaintRects.add(rect);
-                        rect = new Rectangle();
+                        if ((! isOptimizedDrawingEnabled()) && i > 0)
+                          {
+                            // Check if the child is completely obscured.
+                            Rectangle clip = g.getClipBounds(); // A copy.
+                            SwingUtilities.computeIntersection(cx, cy, cw, ch,
+                                                               clip);
+                            if (isCompletelyObscured(i, clip))
+                              continue; // Continues the for-loop.
+                          }
+                        Graphics cg = g.create(cx, cy, cw, ch);
+                        cg.setColor(child.getForeground());
+                        cg.setFont(child.getFont());
+                        try
+                          {
+                            child.paint(cg);
+                          }
+                        finally
+                          {
+                            cg.dispose();
+                          }
                       }
-
-                    // The south rectangle.
-                    x = Math.max(compBounds.x, r.x);
-                    y = compBounds.y + compBounds.height;
-                    w = Math.min(compBounds.width, r.width + r.x - x);
-                    h = r.height - (compBounds.y - r.y) - compBounds.height;
-                    rect.setBounds(x, y, w, h);
-                    if (! rect.isEmpty())
-                      {
-                        newPaintRects.add(rect);
-                        rect = new Rectangle();
-                      }
-
-                    // The west rectangle.
-                    x = r.x;
-                    y = r.y;
-                    w = compBounds.x - r.x;
-                    h = r.height;
-                    rect.setBounds(x, y, w, h);
-                    if (! rect.isEmpty())
-                      {
-                        newPaintRects.add(rect);
-                        rect = new Rectangle();
-                      }
-
-                    // The east rectangle.
-                    x = compBounds.x + compBounds.width;
-                    y = r.y;
-                    w = r.width - (compBounds.x - r.x) - compBounds.width;
-                    h = r.height;
-                    rect.setBounds(x, y, w, h);
-                    if (! rect.isEmpty())
-                      {
-                        newPaintRects.add(rect);
-                      }
-                  }
-                else
-                  {
-                    // Not opaque, need to reuse the current paint rectangles
-                    // for the next component.
-                    newPaintRects.add(r);
                   }
-                
-              }
-            else
-              {
-                newPaintRects.add(r);
-              }
-          }
-
-        // Replace the paintRectangles with the new split up
-        // paintRectangles.
-        paintRectangles.clear();
-        paintRectangles.addAll(newPaintRects);
-        newPaintRects.clear();
-
-        // Store paint rectangles if there are any for the current component.
-        int compRectsSize = componentRectangles.size();
-        if (compRectsSize > 0)
-          {
-            componentRectangles.add(comp);
-            paintRegions.add(componentRectangles);
-            componentRectangles = new ArrayList();
-          }
-      }
-
-    // paintingTile becomes true just before we start painting the component's
-    // children.
-    paintingTile = true;
-
-    // We must go through the painting regions backwards, because the
-    // topmost components have been added first, followed by the components
-    // below.
-    int prEndIndex = paintRegions.size() - 1;
-    for (int i = prEndIndex; i >= 0; i--)
-      {
-        // paintingTile must be set to false before we begin to start painting
-        // the last tile.
-        if (i == 0)
-          paintingTile = false;
-
-        ArrayList paintingRects = (ArrayList) paintRegions.get(i);
-        // The last element is always the component.
-        Component c = (Component) paintingRects.get(paintingRects.size() - 1);
-        int endIndex = paintingRects.size() - 2;
-        for (int j = 0; j <= endIndex; j++)
-          {
-            Rectangle cBounds = c.getBounds();
-            Rectangle bounds = (Rectangle) paintingRects.get(j);
-            Rectangle oldClip = g.getClipBounds();
-            if (oldClip == null)
-              oldClip = bounds;
-
-            boolean translated = false;
-            try
-              {
-                g.setClip(bounds);
-                g.translate(cBounds.x, cBounds.y);
-                translated = true;
-                c.paint(g);
-              }
-            finally
-              {
-                if (translated)
-                  g.translate(-cBounds.x, -cBounds.y);
-                g.setClip(oldClip);
               }
           }
       }
-    g.setClip(originalClip);
   }
 
   /**
-   * Paints the children of this container when it is marked as
-   * optimizedDrawingEnabled. In this case the container can guarantee that
-   * it's children are tiled, which allows for a much more efficient
-   * algorithm to determine the minimum rectangles to be painted for
-   * each child.
+   * Determines if a region of a child component is completely obscured by one
+   * of its siblings.
    *
-   * @param g the graphics context to use
+   * @param index the index of the child component
+   * @param rect the region to check
+   *
+   * @return <code>true</code> if the region is completely obscured by a
+   *         sibling, <code>false</code> otherwise
    */
-  private void paintChildrenOptimized(Graphics g)
+  private boolean isCompletelyObscured(int index, Rectangle rect)
   {
-    Rectangle inner = SwingUtilities.calculateInnerArea(this, rectCache);
-    g.clipRect(inner.x, inner.y, inner.width, inner.height);
-
-    // paintingTile becomes true just before we start painting the component's
-    // children.
-    paintingTile = true;
-    int numChildren = getComponentCount();
-    for (int i = numChildren - 1; i >= 0; i--) //children.length; i++)
+    boolean obscured = false;
+    for (int i = index - 1; i >= 0 && obscured == false; i--)
       {
-        Component child = getComponent(i);
-        // paintingTile must be set to false before we begin to start painting
-        // the last tile.
-        if (i == numChildren - 1)
-          paintingTile = false;
-
-        if (!child.isVisible() || ! child.isLightweight())
-          continue;
-
-        Rectangle bounds = child.getBounds(rectCache);
-        if (!g.hitClip(bounds.x, bounds.y, bounds.width, bounds.height))
-          continue;
-
-        Graphics g2 = g.create(bounds.x, bounds.y, bounds.width,
-                               bounds.height);
-        child.paint(g2);
-        g2.dispose();
+        Component sib = getComponent(i);
+        if (sib.isVisible())
+          {
+            Rectangle sibRect = sib.getBounds(rectCache);
+            if (sib.isOpaque() && rect.x >= sibRect.x
+                && (rect.x + rect.width) <= (sibRect.x + sibRect.width)
+                && rect.y >= sibRect.y
+                && (rect.y + rect.height) <= (sibRect.y + sibRect.height))
+              {
+                obscured = true;
+              }
+          }
       }
+    return obscured;
   }
 
   /**
    * Paint the component's body. This usually means calling [EMAIL PROTECTED]
    * ComponentUI#update} on the [EMAIL PROTECTED] #ui} property of the component, if
    * it is non-<code>null</code>. You may override this if you wish to
    * customize the component's body-painting behavior. The component's body
    * is painted first, before the border and children.
    *
    * @param g The graphics context with which to paint the body
    *
    * @see #paint
    * @see #paintBorder
    * @see #paintChildren
    */
   protected void paintComponent(Graphics g)
   {
     if (ui != null)
       {
-        Graphics g2 = g;
-        if (!(g instanceof Graphics2D))
-          g2 = g.create();
-        ui.update(g2, this);
-        if (!(g instanceof Graphics2D))
-          g2.dispose();
+        Graphics g2 = g.create();
+        try
+          {
+            ui.update(g2, this);
+          }
+        finally
+          {
+            g2.dispose();
+          }
       }
   }
 
   /**
    * A variant of [EMAIL PROTECTED] #paintImmediately(Rectangle)} which takes
    * integer parameters.
    *
    * @param x The left x coordinate of the dirty region
    * @param y The top y coordinate of the dirty region
    * @param w The width of the dirty region
    * @param h The height of the dirty region
    */
@@ -2320,29 +2170,34 @@
   /**
    * Clips and translates the Graphics instance for painting on the double
    * buffer. This has to be done, so that it reflects the component clip of the
    * target component.
    *
    * @param root the root component (top-level container usually)
    * @param target the component to be painted
    * @param g the Graphics instance
    */
   private void clipAndTranslateGraphics(Component root, Component target,
                                         Graphics g)
   {
-    Component parent = target.getParent();
-    if (parent != root)
-      clipAndTranslateGraphics(root, parent, g);
-
-    g.translate(target.getX(), target.getY());
+    Component parent = target;
+    int deltaX = 0;
+    int deltaY = 0;
+    while (parent != root)
+      {
+        deltaX += parent.getX();
+        deltaY += parent.getY();
+        parent = parent.getParent();
+      }
+    g.translate(deltaX, deltaY);
     g.clipRect(0, 0, target.getWidth(), target.getHeight());
   }
 
   /**
    * Performs normal painting without double buffering.
    *
    * @param r the area that should be repainted
    */
   void paintSimple(Rectangle r)
   {
     Graphics g = getGraphics();
     Graphics g2 = getComponentGraphics(g);
Index: javax/swing/RepaintManager.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/RepaintManager.java,v
retrieving revision 1.41
diff -u -1 -2 -r1.41 RepaintManager.java
--- javax/swing/RepaintManager.java	19 Jun 2006 09:54:03 -0000	1.41
+++ javax/swing/RepaintManager.java	19 Jul 2006 19:33:46 -0000
@@ -29,25 +29,24 @@
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package javax.swing;
 
-import java.applet.Applet;
 import java.awt.Component;
 import java.awt.Dimension;
 import java.awt.Graphics;
 import java.awt.Image;
 import java.awt.Rectangle;
 import java.awt.Window;
 import java.awt.image.VolatileImage;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
@@ -638,25 +637,25 @@
                                   int proposedHeight)
   {
     Component root = SwingUtilities.getWindowAncestor(component);
     Image buffer = (Image) offscreenBuffers.get(root);
     if (buffer == null 
         || buffer.getWidth(null) < proposedWidth 
         || buffer.getHeight(null) < proposedHeight)
       {
         int width = Math.max(proposedWidth, root.getWidth());
         width = Math.min(doubleBufferMaximumSize.width, width);
         int height = Math.max(proposedHeight, root.getHeight());
         height = Math.min(doubleBufferMaximumSize.height, height);
-        buffer = component.createImage(width, height);
+        buffer = root.createImage(width, height);
         offscreenBuffers.put(root, buffer);
       }
     return buffer;
   }
 
   /**
    * Blits the back buffer of the specified root component to the screen. If
    * the RepaintManager is currently working on a paint request, the commit
    * requests are queued up and committed at once when the paint request is
    * done (by [EMAIL PROTECTED] #commitRemainingBuffers}). This is package private because
    * it must get called by JComponent.
    *

Reply via email to