Hi,

The attached patch starts implementing support for custom Composite
classes, starting with VolatileImageGraphics.

I've also attached the simple test I've been using, MyComposite.java
which wraps the AlphaComposite class (thus preventing Cairo from
handling alpha composite internally), and CustomComposite.java (which
draws two arcs and an optional image, all with custom composite.. check
the source code for a couple of options).
"ecj CustomComposite.java MyComposite.java ; cacao CustomComposite" to
run.

As this is, or at least feels like, a large change, I'd appreciate some
feedback before committing it.  If this approach looks good, I'll
continue with it for the other CairoGraphics2D subclasses.

Cheers,
Francis

import java.awt.Color;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.AlphaComposite;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;


public class CustomComposite
{
  public static void main(String[] args)
  {
    // Set this string to the path of an image to test image-drawing
    final String testImage = null;
//    final String testImage = "/home/fkung/palme.gif";

    // TRUE to test custom composite; FALSE to test against reference
    final boolean custom = true;


    JFrame f = new JFrame();
    JPanel p = new JPanel()
    {
      public void paint(Graphics g)
      {
//      long stime = System.currentTimeMillis();

        CubicCurve2D curve1 = new CubicCurve2D.Double(0, 0, 150, 280, 280, 150, 0, 0);
        CubicCurve2D curve2 = new CubicCurve2D.Double(150, 150, 0, -130, -130, 0, 150, 150);

        ((Graphics2D)g).setColor(Color.BLACK);
        ((Graphics2D)g).draw(curve1);
        ((Graphics2D)g).setColor(Color.RED);
        ((Graphics2D)g).fill(curve1);

        if (custom)
          ((Graphics2D)g).setComposite(new MyComposite(0.5f));
        else
          ((Graphics2D)g).setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));

        ((Graphics2D)g).setColor(Color.BLACK);
        ((Graphics2D)g).draw(curve2);
        ((Graphics2D)g).setColor(Color.BLUE);
        ((Graphics2D)g).fill(curve2);

//      long etime = System.currentTimeMillis();
//      System.out.println("drawing took " + (etime - stime) + " ms");

        if (testImage != null)
          {
            Image realimg = Toolkit.getDefaultToolkit().getImage(testImage);
            BufferedImage img;
            if( !(realimg instanceof BufferedImage) )
              {
                // Ensure image is completely loaded (required for Sun)
                realimg = new ImageIcon(realimg).getImage();

                // Stuff into a BufferedImage
                img = new BufferedImage(realimg.getWidth(null),
                                        realimg.getHeight(null),
                                        BufferedImage.TYPE_INT_ARGB);
                Graphics gr = img.createGraphics();
                gr.drawImage(realimg, 0, 0, null);
                gr.dispose();
              }
            else
              img = (BufferedImage)realimg;

            ((Graphics2D)g).drawImage(img, 50, 50, null);
          }
      }
    };
    
    p.setBackground(Color.white);
    f.setContentPane(p);
    f.setSize(200, 200);
    f.setVisible(true);
  }
}
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.AlphaComposite;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;

public class MyComposite implements Composite
{
  private AlphaComposite comp;

  public MyComposite(float alpha)
  {
    comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha);
  }

  public CompositeContext createContext(ColorModel srcCM, ColorModel dstCM, RenderingHints hints)
  {
    return comp.createContext(srcCM, dstCM, hints);
  }
}

Index: gnu/java/awt/peer/gtk/CairoSurface.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoSurface.java,v
retrieving revision 1.17
diff -u -r1.17 CairoSurface.java
--- gnu/java/awt/peer/gtk/CairoSurface.java	14 Sep 2006 18:30:58 -0000	1.17
+++ gnu/java/awt/peer/gtk/CairoSurface.java	27 Sep 2006 19:13:18 -0000
@@ -38,15 +38,14 @@
 
 package gnu.java.awt.peer.gtk;
 
-import java.awt.Point;
 import java.awt.Graphics2D;
-import java.awt.image.DataBuffer;
-import java.awt.image.Raster;
-import java.awt.image.WritableRaster;
+import java.awt.Point;
 import java.awt.image.BufferedImage;
 import java.awt.image.ColorModel;
+import java.awt.image.DataBuffer;
 import java.awt.image.DirectColorModel;
 import java.awt.image.SinglePixelPackedSampleModel;
+import java.awt.image.WritableRaster;
 import java.nio.ByteOrder;
 import java.util.Hashtable;
 
@@ -69,11 +68,12 @@
    */
   long bufferPointer;
 
+  // nativeGetPixels will return [0]=red, [1]=green, [2]=blue, [3]=alpha
   static ColorModel nativeModel = new DirectColorModel(32,
-						       0x00FF0000,
-						       0x0000FF00,
-						       0x000000FF,
-						       0xFF000000);
+                                                       0x000000FF,
+                                                       0x0000FF00,
+                                                       0x00FF0000,
+                                                       0xFF000000);
 
   /**
    * Allocates and clears the buffer and creates the cairo surface.
Index: gnu/java/awt/peer/gtk/CairoGraphics2D.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/CairoGraphics2D.java,v
retrieving revision 1.40
diff -u -r1.40 CairoGraphics2D.java
--- gnu/java/awt/peer/gtk/CairoGraphics2D.java	14 Sep 2006 18:30:58 -0000	1.40
+++ gnu/java/awt/peer/gtk/CairoGraphics2D.java	27 Sep 2006 19:13:18 -0000
@@ -167,7 +167,7 @@
   /**
    * Rendering hint map.
    */
-  private RenderingHints hints;
+  protected RenderingHints hints;
 
   /**
    * Some operations (drawing rather than filling) require that their
@@ -967,7 +967,8 @@
           sm.checkPermission(new AWTPermission("readDisplayPixels"));
 
         // FIXME: implement general Composite support
-        throw new java.lang.UnsupportedOperationException();
+        //throw new java.lang.UnsupportedOperationException();
+        // this is in progress!  yay!
       }
   }
 
Index: gnu/java/awt/peer/gtk/VolatileImageGraphics.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/awt/peer/gtk/VolatileImageGraphics.java,v
retrieving revision 1.7
diff -u -r1.7 VolatileImageGraphics.java
--- gnu/java/awt/peer/gtk/VolatileImageGraphics.java	17 Jul 2006 22:41:03 -0000	1.7
+++ gnu/java/awt/peer/gtk/VolatileImageGraphics.java	27 Sep 2006 19:13:18 -0000
@@ -38,11 +38,22 @@
 
 package gnu.java.awt.peer.gtk;
 
+import java.awt.AlphaComposite;
+import java.awt.Color;
+import java.awt.CompositeContext;
 import java.awt.Graphics;
+import java.awt.Graphics2D;
 import java.awt.GraphicsConfiguration;
 import java.awt.Image;
+import java.awt.Shape;
+import java.awt.Toolkit;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Point2D;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
 import java.awt.image.ImageObserver;
+import java.awt.image.ImageProducer;
+import java.awt.image.WritableRaster;
 
 public class VolatileImageGraphics extends ComponentGraphics
 {
@@ -77,10 +88,88 @@
     return new VolatileImageGraphics( this );
   }
 
+  public void draw(Shape s)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.draw(s);
+    
+    // Custom composite
+    else
+      {
+        // Draw operation to temporary buffer
+        BufferedImage tmp = new BufferedImage(owner.width, 
+                                              owner.height,
+                                              BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2d = (Graphics2D)tmp.getGraphics();
+        g2d.setStroke(this.getStroke());
+        g2d.draw(s);
+        
+        drawComposite(tmp, s.getBounds2D(), null);
+      }
+  }
+  
+  public void fill(Shape s)
+  {
+    if (comp == null || comp instanceof AlphaComposite)
+      super.fill(s);
 
+    // Custom composite
+    else
+      {
+        // Draw operation to temporary buffer
+        BufferedImage tmp = new BufferedImage(owner.width, 
+                                              owner.height,
+                                              BufferedImage.TYPE_INT_ARGB);
+        Graphics2D g2d = (Graphics2D)tmp.getGraphics();
+        g2d.setPaint(this.getPaint());
+        g2d.fill(s);
+        
+        drawComposite(tmp, s.getBounds2D(), 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);
+            }
+          
+          // Create appropriately sized buffer and draw image to buffer
+          BufferedImage bImg = (BufferedImage) img;
+          Point2D pt = new Point2D.Double(bImg.getWidth(), bImg.getHeight());
+          if (xform != null)
+            pt = xform.transform(pt, pt);
+          
+          BufferedImage tmp = new BufferedImage((int)pt.getX(), (int)pt.getY(),
+                                                BufferedImage.TYPE_INT_ARGB);
+          
+          Graphics2D g2d = (Graphics2D)tmp.getGraphics();
+          g2d.setBackground(bgcolor);
+          g2d.drawImage(img, xform, obs);
+
+          // Perform compositing
+          return drawComposite(tmp,
+                               new Rectangle2D.Double(0, 0, tmp.getWidth(),
+                                                      tmp.getHeight()),
+                               obs);
+        }
+    }
+  
   public boolean drawImage(Image img, int x, int y, ImageObserver observer)
   {
-    if( img instanceof GtkVolatileImage )
+    if (img instanceof GtkVolatileImage
+        && (comp == null || comp instanceof AlphaComposite))
       {
 	owner.drawVolatile( ((GtkVolatileImage)img).nativePointer, 
 			    x, y,
@@ -94,7 +183,8 @@
   public boolean drawImage(Image img, int x, int y, int width, int height,
                            ImageObserver observer)
   {
-    if( img instanceof GtkVolatileImage )
+    if ((img instanceof GtkVolatileImage)
+        && (comp == null || comp instanceof AlphaComposite))
       {
 	owner.drawVolatile( ((GtkVolatileImage)img).nativePointer, 
 			    x, y, width, height );
@@ -107,5 +197,56 @@
   {
     return new Rectangle2D.Double(0, 0, owner.width, owner.height);
   }
+  
+  private boolean drawComposite(BufferedImage tmp, Rectangle2D bounds,
+                                ImageObserver observer)
+  {
+    // Clip to the area that contains the shape, and is visible on screen
+    Rectangle2D screen = this.getRealBounds();
+    Rectangle2D.intersect(bounds, screen, bounds);
+    
+    if (!bounds.equals(tmp.getRaster().getBounds()))
+      {
+        tmp = tmp.getSubimage((int)bounds.getX(), (int)bounds.getY(),
+                              (int)Math.ceil(bounds.getWidth()),
+                              (int)Math.ceil(bounds.getHeight() + 1));
+      }
+
+    // Get current pixels visible on screen, and also clip
+    BufferedImage current = owner.getSnapshot();
+    Point2D origin = transform.transform(new Point2D.Double(bounds.getX(),
+                                                            bounds.getY()), 
+                                         null);
+    Point2D dim = transform.transform(new Point2D.Double(bounds.getMaxX(),
+                                                         bounds.getMaxY()),
+                                      null);
+    
+    current = current.getSubimage((int)origin.getX(), (int)origin.getY(),
+                                  (int)dim.getX(), (int)dim.getY());
+
+    // Set up composite context
+    CompositeContext ctx = comp.createContext(tmp.getColorModel(),
+                                              current.getColorModel(),
+                                              hints);
+    
+    // FIXME: use current.getRaster() directly as the destination.
+    // this currently doesn't work since that raster is backed by a
+    // CairoSurfaceBuffer, not the expected DataBufferInt
+    WritableRaster dest = current.getRaster().createCompatibleWritableRaster();
+    ctx.compose(tmp.getRaster(), current.getRaster(), dest);
+    ctx.dispose();
+    BufferedImage destIm = new BufferedImage(current.getColorModel(),
+                                             dest,
+                                             current.isAlphaPremultiplied(),
+                                             null);
+    
+    // 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!
+    return super.drawImage(destIm,
+                           AffineTransform.getTranslateInstance(bounds.getX(),
+                                                                bounds.getY()),
+                           null, null);
+  }
 }
 
Index: native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkVolatileImage.c
===================================================================
RCS file: /cvsroot/classpath/classpath/native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkVolatileImage.c,v
retrieving revision 1.7
diff -u -r1.7 gnu_java_awt_peer_gtk_GtkVolatileImage.c
--- native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkVolatileImage.c	5 Sep 2006 21:27:28 -0000	1.7
+++ native/jni/gtk-peer/gnu_java_awt_peer_gtk_GtkVolatileImage.c	27 Sep 2006 19:13:18 -0000
@@ -110,10 +110,12 @@
   /* jint *pixeldata, *jpixdata; */
   jint *jpixdata;
   GdkPixmap *pixmap;
+  GdkPixbuf *pixbuf;
   jintArray jpixels;
   int width, height, depth, size;
   jclass cls;
   jfieldID field;
+  guchar *pixels;
 
   cls = (*env)->GetObjectClass (env, obj);
   field = (*env)->GetFieldID (env, cls, "width", "I");
@@ -131,11 +133,16 @@
 
   /* get depth in bytes */
   depth = gdk_drawable_get_depth( pixmap ) >> 3;
-  size = width * height * 4;
+  size = width * height;
   jpixels = (*env)->NewIntArray ( env, size );
   jpixdata = (*env)->GetIntArrayElements (env, jpixels, NULL);
-  /*  memcpy (jpixdata, pixeldata, size * sizeof( jint )); */
-
+  
+  pixbuf = gdk_pixbuf_new( GDK_COLORSPACE_RGB, TRUE, 8, width, height );
+  gdk_pixbuf_get_from_drawable( pixbuf, pixmap, NULL, 0, 0, 0, 0, width, height );
+  pixels = gdk_pixbuf_get_pixels(pixbuf);
+  
+  memcpy (jpixdata, pixels, size * sizeof(jint));
+    
   (*env)->ReleaseIntArrayElements (env, jpixels, jpixdata, 0);
 
   gdk_threads_leave();

Reply via email to