Hi,

The attached patch implements custom composites in CairoSurfaceGraphics,
with the same approach that I've been using so far.

I'll commit my tests to either mauve as visual tests, or to the java2d
demo, eventually...

Cheers,
Francis


2006-10-18  Francis Kung  <[EMAIL PROTECTED]>

        * gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java
        (drawRenderedImage):  New method.
        (drawImage): New method.
        (CairoSurfaceGraphics): Set clip.
        (createBuffer): New method.
        (getBufferCM): New method.
        (drawComposite): New method.
        (fill): New method.
        (getNativeCM): New method.
        (drawGlyphVector): New method.
        (draw): New method.
        * gnu/java/awt/peer/gtk/VolatileImageGraphics.java
        (getNativeCM): Reflect renamed field.
        * gnu/java/awt/peer/gtk/CairoSurface.java
        (cairoCM_pre): Renamed from cairoColorModel.
        (cairoColorModel): Set premultiplication to false.

Index: gnu/java/awt/peer/gtk/CairoSurface.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoSurface.java,v
retrieving revision 1.20
diff -u -r1.20 CairoSurface.java
--- gnu/java/awt/peer/gtk/CairoSurface.java	11 Oct 2006 20:14:59 -0000	1.20
+++ gnu/java/awt/peer/gtk/CairoSurface.java	18 Oct 2006 18:44:55 -0000
@@ -72,14 +72,22 @@
    */
   long bufferPointer;
 
-  static ColorModel cairoColorModel = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
-                                                           32, 0x00FF0000,
+  // FIXME: use only the cairoCM_pre colormodel
+  // since that's what Cairo really uses (is there a way to do this cheaply?
+  // we use a non-multiplied model most of the time to avoid costly coercion
+  // operations...)
+  static ColorModel cairoColorModel = new DirectColorModel(32, 0x00FF0000,
                                                            0x0000FF00,
                                                            0x000000FF,
-                                                           0xFF000000,
-                                                           true,
-                                                           Buffers.smallestAppropriateTransferType(32));
+                                                           0xFF000000);
 
+  static ColorModel cairoCM_pre = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
+                                                       32, 0x00FF0000,
+                                                       0x0000FF00,
+                                                       0x000000FF,
+                                                       0xFF000000,
+                                                       true,
+                                                       Buffers.smallestAppropriateTransferType(32));
   /**
    * Allocates and clears the buffer and creates the cairo surface.
    * @param width, height - the image size
Index: gnu/java/awt/peer/gtk/VolatileImageGraphics.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/VolatileImageGraphics.java,v
retrieving revision 1.9
diff -u -r1.9 VolatileImageGraphics.java
--- gnu/java/awt/peer/gtk/VolatileImageGraphics.java	11 Oct 2006 20:14:59 -0000	1.9
+++ gnu/java/awt/peer/gtk/VolatileImageGraphics.java	18 Oct 2006 18:44:56 -0000
@@ -306,7 +306,7 @@
     // the fixme in drawImage) so we use the naive Cairo model instead to trick
     // the compositing context.
     // Because getNativeCM() == getBufferCM() for this peer, it doesn't break.
-    return CairoSurface.cairoColorModel;
+    return CairoSurface.cairoCM_pre;
   }
 }
 
Index: gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java,v
retrieving revision 1.7
diff -u -r1.7 CairoSurfaceGraphics.java
--- gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java	17 Jul 2006 22:41:03 -0000	1.7
+++ gnu/java/awt/peer/gtk/CairoSurfaceGraphics.java	18 Oct 2006 18:44:55 -0000
@@ -38,10 +38,26 @@
 
 package gnu.java.awt.peer.gtk;
 
+import java.awt.AlphaComposite;
+import java.awt.Color;
 import java.awt.Graphics;
-import java.awt.GraphicsEnvironment;
+import java.awt.Graphics2D;
 import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsEnvironment;
+import java.awt.Image;
+import java.awt.Rectangle;
+import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.font.GlyphVector;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.ImageObserver;
+import java.awt.image.ImageProducer;
+import java.awt.image.RenderedImage;
+import java.util.Hashtable;
 
 /**
  * Implementation of Graphics2D on a Cairo surface.
@@ -49,6 +65,7 @@
 public class CairoSurfaceGraphics extends CairoGraphics2D
 {
   protected CairoSurface surface;
+  private BufferedImage buffer;
   private long cairo_t;
   
   /**
@@ -59,6 +76,7 @@
     this.surface = surface;
     cairo_t = surface.newCairoContext();
     setup( cairo_t );
+    setClip(0, 0, surface.width, surface.height);
   }
 
   /**
@@ -91,4 +109,200 @@
   {
     surface.copyAreaNative(x, y, width, height, dx, dy, surface.width);
   }
+  
+  /**
+   * Overloaded methods that do actual drawing need to account for custom
+   * composites
+   */
+  public void draw(Shape s)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.draw(s);
+    
+    else
+      {
+        createBuffer();
+        
+        Graphics2D g2d = (Graphics2D)buffer.getGraphics();
+        g2d.setStroke(this.getStroke());
+        g2d.setColor(this.getColor());
+        g2d.draw(s);
+        
+        drawComposite(s.getBounds2D(), null);
+      }
+  }
+
+  public void fill(Shape s)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.fill(s);
+    
+    else
+      {
+        createBuffer();
+        
+        Graphics2D g2d = (Graphics2D)buffer.getGraphics();
+        g2d.setPaint(this.getPaint());
+        g2d.setColor(this.getColor());
+        g2d.fill(s);
+        
+        drawComposite(s.getBounds2D(), null);
+      }
+  }
+
+  public void drawRenderedImage(RenderedImage image, AffineTransform xform)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.drawRenderedImage(image, xform);
+    
+    else
+      {
+        createBuffer();
+
+        Graphics2D g2d = (Graphics2D)buffer.getGraphics();
+        g2d.setRenderingHints(this.getRenderingHints());
+        g2d.drawRenderedImage(image, xform);
+        
+        drawComposite(buffer.getRaster().getBounds(), null);
+      }
+
+  }
+
+  protected boolean drawImage(Image img, AffineTransform xform,
+                              Color bgcolor, ImageObserver obs)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      return super.drawImage(img, xform, bgcolor, obs);
+    
+    else
+      {
+        // Get buffered image of source
+        if( !(img instanceof BufferedImage) )
+          {
+            ImageProducer source = img.getSource();
+            if (source == null)
+              return false;
+            img = Toolkit.getDefaultToolkit().createImage(source);
+          }
+        BufferedImage bImg = (BufferedImage) img;
+        
+        // Find translated bounds
+        Point2D origin = new Point2D.Double(bImg.getMinX(), bImg.getMinY());
+        Point2D pt = new Point2D.Double(bImg.getWidth() + bImg.getMinX(),
+                                        bImg.getHeight() + bImg.getMinY());
+        if (xform != null)
+          {
+            origin = xform.transform(origin, origin);
+            pt = xform.transform(pt, pt);
+          }
+        
+        // Create buffer and draw image
+        createBuffer();
+        
+        Graphics2D g2d = (Graphics2D)buffer.getGraphics();
+        g2d.setRenderingHints(this.getRenderingHints());
+        g2d.drawImage(img, xform, obs);
+
+        // Perform compositing
+        return drawComposite(new Rectangle2D.Double(origin.getX(),
+                                                    origin.getY(),
+                                                    pt.getX(), pt.getY()),
+                             obs);
+      }
+  }
+
+  public void drawGlyphVector(GlyphVector gv, float x, float y)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.drawGlyphVector(gv, x, y);
+    
+    else
+      {
+        createBuffer();
+
+        Graphics2D g2d = (Graphics2D)buffer.getGraphics();
+        g2d.setPaint(this.getPaint());
+        g2d.setStroke(this.getStroke());
+        g2d.drawGlyphVector(gv, x, y);
+        
+        Rectangle2D bounds = gv.getLogicalBounds();
+        bounds = new Rectangle2D.Double(x + bounds.getX(), y + bounds.getY(),
+                                        bounds.getWidth(), bounds.getHeight());
+        drawComposite(bounds, null);
+      }
+  }
+  
+  private boolean drawComposite(Rectangle2D bounds, ImageObserver observer)
+  {
+    // Clip source to visible areas that need updating
+    Rectangle2D clip = this.getClipBounds();
+    Rectangle2D.intersect(bounds, clip, bounds);
+    clip = new Rectangle(buffer.getMinX(), buffer.getMinY(),
+                         buffer.getWidth(), buffer.getHeight());
+    Rectangle2D.intersect(bounds, clip, bounds);
+    
+    BufferedImage buffer2 = buffer;
+    if (!bounds.equals(buffer2.getRaster().getBounds()))
+      buffer2 = buffer2.getSubimage((int)bounds.getX(), (int)bounds.getY(),
+                                    (int)bounds.getWidth(),
+                                    (int)bounds.getHeight());
+    
+    // Get destination clip to bounds
+    double[] points = new double[] {bounds.getX(), bounds.getY(),
+                                    bounds.getMaxX(), bounds.getMaxY()};
+    transform.transform(points, 0, points, 0, 2);
+    
+    Rectangle2D deviceBounds = new Rectangle2D.Double(points[0], points[1],
+                                                       points[2] - points[0],
+                                                       points[3] - points[1]);
+    
+    Rectangle2D.intersect(deviceBounds, this.getClipInDevSpace(), deviceBounds);
+    
+    BufferedImage current = CairoSurface.getBufferedImage(surface);
+    current = current.getSubimage((int)deviceBounds.getX(),
+                                  (int)deviceBounds.getY(),
+                                  (int)deviceBounds.getWidth(),
+                                  (int)deviceBounds.getHeight());
+
+    // Perform actual composite operation
+    compCtx.compose(buffer2.getRaster(), current.getRaster(),
+                    buffer2.getRaster());
+    
+    // This MUST call directly into the "action" method in CairoGraphics2D,
+    // not one of the wrappers, to ensure that the composite isn't processed
+    // more than once!
+    boolean rv = super.drawImage(buffer2,
+                                 AffineTransform.getTranslateInstance(bounds.getX(),
+                                                                      bounds.getY()),
+                                 new Color(0,0,0,0), null);
+    return rv;
+  }
+  
+  private void createBuffer()
+  {
+    if (buffer == null)
+      {
+        buffer = new BufferedImage(getBufferCM(),
+                                   surface.createCompatibleWritableRaster(),
+                                   getBufferCM().isAlphaPremultiplied(),
+                                   new Hashtable());
+      }
+    else
+      {
+        Graphics2D g2d = ((Graphics2D)buffer.getGraphics());
+        
+        g2d.setBackground(new Color(0,0,0,0));
+        g2d.clearRect(0, 0, buffer.getWidth(), buffer.getHeight());
+      }
+  }
+  
+  protected ColorModel getNativeCM()
+  {
+    return CairoSurface.cairoCM_pre;
+  }
+  
+  protected ColorModel getBufferCM()
+  {
+    return CairoSurface.cairoColorModel;
+  }
 }

Reply via email to