Source for gnu.java.awt.peer.gtk.CairoSurface

   1: /* CairoSurface.java
   2:    Copyright (C) 2006, 2007 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package gnu.java.awt.peer.gtk;
  40: 
  41: import gnu.java.awt.Buffers;
  42: 
  43: import java.awt.Graphics2D;
  44: import java.awt.Point;
  45: import java.awt.Rectangle;
  46: import java.awt.Shape;
  47: import java.awt.color.ColorSpace;
  48: import java.awt.geom.AffineTransform;
  49: import java.awt.image.BufferedImage;
  50: import java.awt.image.ColorModel;
  51: import java.awt.image.DataBuffer;
  52: import java.awt.image.DataBufferInt;
  53: import java.awt.image.DirectColorModel;
  54: import java.awt.image.Raster;
  55: import java.awt.image.RasterFormatException;
  56: import java.awt.image.SampleModel;
  57: import java.awt.image.SinglePixelPackedSampleModel;
  58: import java.awt.image.WritableRaster;
  59: import java.nio.ByteOrder;
  60: import java.util.Arrays;
  61: import java.util.Hashtable;
  62: 
  63: /**
  64:  * CairoSurface - wraps a Cairo surface.
  65:  *
  66:  * @author Sven de Marothy
  67:  */
  68: public class CairoSurface extends WritableRaster
  69: {
  70:   int width = -1, height = -1;
  71: 
  72:   /**
  73:    * The native pointer to the Cairo surface. 
  74:    */
  75:   long surfacePointer;
  76: 
  77:   /**
  78:    * Whether the data buffer is shared between java and cairo.
  79:    */
  80:   boolean sharedBuffer;
  81: 
  82:   // FIXME: use only the cairoCM_pre colormodel
  83:   // since that's what Cairo really uses (is there a way to do this cheaply?
  84:   // we use a non-multiplied model most of the time to avoid costly coercion
  85:   // operations...)
  86:   static ColorModel cairoColorModel = new DirectColorModel(32, 0x00FF0000,
  87:                                                            0x0000FF00,
  88:                                                            0x000000FF,
  89:                                                            0xFF000000);
  90: 
  91:   static ColorModel cairoCM_pre = new DirectColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
  92:                                                        32, 0x00FF0000,
  93:                                                        0x0000FF00,
  94:                                                        0x000000FF,
  95:                                                        0xFF000000,
  96:                                                        true,
  97:                                                        Buffers.smallestAppropriateTransferType(32));
  98:   
  99:   // This CM corresponds to the CAIRO_FORMAT_RGB24 type in Cairo 
 100:   static ColorModel cairoCM_opaque = new DirectColorModel(24, 0x00FF0000,
 101:                                                           0x0000FF00,
 102:                                                           0x000000FF);
 103:   /**
 104:    * Allocates and clears the buffer and creates the cairo surface.
 105:    * @param width, height - the image size
 106:    * @param stride - the buffer row stride. (in ints)
 107:    */
 108:   private native void create(int width, int height, int stride, int[] buf);
 109: 
 110:   /**
 111:    * Destroys the cairo surface and frees the buffer.
 112:    */
 113:   private native void destroy(long surfacePointer, int[] buf);
 114: 
 115:   /**
 116:    * Draws this image to a given CairoGraphics context, 
 117:    * with an affine transform given by i2u.
 118:    */
 119:   public native void nativeDrawSurface(long surfacePointer, long contextPointer,
 120:                                        double[] i2u, double alpha,
 121:                                        int interpolation);
 122: 
 123:   /**
 124:    * Synchronizes the image's data buffers, copying any changes made in the
 125:    * Java array into the native array.
 126:    * 
 127:    * This method should only be called if (sharedBuffers == false).
 128:    */
 129:   native void syncNativeToJava(long surfacePointer, int[] buffer);
 130:   
 131:   /**
 132:    * Synchronizes the image's data buffers, copying any changes made in the
 133:    * native array into the Java array.
 134:    * 
 135:    * This method should only be called if (sharedBuffers == false).
 136:    */
 137:   native void syncJavaToNative(long surfacePointer, int[] buffer);
 138:   
 139:   /**
 140:    * Return the buffer, with the sample values of each pixel reversed
 141:    * (ie, in ABGR instead of ARGB). 
 142:    * 
 143:    * @return A pointer to a flipped buffer.  The memory is allocated in native
 144:    *        code, and must be explicitly freed when it is no longer needed.
 145:    */
 146:   native long getFlippedBuffer(long surfacePointer);
 147: 
 148:   /**
 149:    * Create a cairo_surface_t with specified width and height.
 150:    * The format will be ARGB32 with premultiplied alpha and native bit 
 151:    * and word ordering.
 152:    */
 153:   public CairoSurface(int width, int height)
 154:   {
 155:     this(0, 0, width, height);
 156:   }
 157:   
 158:   public CairoSurface(int x, int y, int width, int height)
 159:   {
 160:     super(createCairoSampleModel(width, height), null, new Point(x, y));
 161: 
 162:     if(width <= 0 || height <= 0)
 163:       throw new IllegalArgumentException("Image must be at least 1x1 pixels.");
 164:     
 165:     this.width = width;
 166:     this.height = height;
 167:     dataBuffer = new DataBufferInt(width * height);
 168:     create(width, height, width, getData());
 169: 
 170:     if(surfacePointer == 0)
 171:       throw new Error("Could not allocate bitmap.");
 172:   }
 173:   
 174:   /**
 175:    * Create a Cairo Surface that is a subimage of another Cairo Surface
 176:    */
 177:   public CairoSurface(SampleModel sm, CairoSurface parent, Rectangle bounds,
 178:                       Point origin)
 179:   {
 180:     super(sm, parent.dataBuffer, bounds, origin, parent);
 181:     
 182:     this.width = super.width;
 183:     this.height = super.height;
 184:     this.surfacePointer = parent.surfacePointer;
 185:     this.sharedBuffer = parent.sharedBuffer;
 186:     this.dataBuffer = parent.dataBuffer;
 187:   }
 188: 
 189:   /**
 190:    * Create a cairo_surface_t from a GtkImage instance.
 191:    * (data is copied, not shared)
 192:    */
 193:   CairoSurface(GtkImage image)
 194:   {
 195:     this(image.width, image.height);
 196: 
 197:     // Copy the pixel data from the GtkImage.
 198:     int[] data = image.getPixels();
 199: 
 200:     // Swap ordering from GdkPixbuf to Cairo
 201:     if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN)
 202:       {
 203:         for (int i = 0; i < data.length; i++ )
 204:           {
 205:             // On a big endian system we get a RRGGBBAA data array.
 206:             int alpha = data[i] & 0xFF;
 207:             if( alpha == 0 ) // I do not know why we need this, but it works.
 208:               data[i] = 0;
 209:             else
 210:               {
 211:                 // Cairo needs a ARGB32 native array.
 212:                 data[i] = (data[i] >>> 8) | (alpha << 24);
 213:               }
 214:           }
 215:       }
 216:     else
 217:       {
 218:         for (int i = 0; i < data.length; i++ )
 219:           {
 220:             // On a little endian system we get a AABBGGRR data array.
 221:             int alpha = data[i] & 0xFF000000;
 222:             if( alpha == 0 ) // I do not know why we need this, but it works.
 223:               data[i] = 0;
 224:             else
 225:               {
 226:                 int b = (data[i] & 0xFF0000) >> 16;
 227:                 int g = (data[i] & 0xFF00);
 228:                 int r = (data[i] & 0xFF) << 16;
 229:                 // Cairo needs a ARGB32 native array.
 230:                 data[i] = alpha | r | g | b;
 231:               }
 232:           }
 233:       }
 234: 
 235:     System.arraycopy(data, 0, getData(), 0, data.length);
 236:   }
 237: 
 238:   /**
 239:    * Dispose of the native data.
 240:    */
 241:   public void dispose()
 242:   {
 243:     if(surfacePointer != 0 && parent == null)
 244:       destroy(surfacePointer, getData());
 245:   }
 246: 
 247:   /**
 248:    * Call dispose() to clean up any native resources allocated.
 249:    */
 250:   protected void finalize()
 251:   {
 252:     dispose();
 253:   }
 254: 
 255:   /**
 256:    * Return a GtkImage from this Cairo surface.
 257:    */
 258:   public GtkImage getGtkImage()
 259:   {
 260:     return new GtkImage(width, height, getFlippedBuffer(surfacePointer));
 261:   }
 262:   
 263:   /**
 264:    * Convenience method to quickly grab the data array backing this Raster.
 265:    * 
 266:    * @return The array behind the databuffer.
 267:    */
 268:   public int[] getData()
 269:   {
 270:     return ((DataBufferInt)dataBuffer).getData();
 271:   }
 272: 
 273:   /**
 274:    * Returns a BufferedImage backed by a Cairo surface.
 275:    */    
 276:   public static BufferedImage getBufferedImage(int width, int height)
 277:   {
 278:     return getBufferedImage(new CairoSurface(width, height));
 279:   }
 280: 
 281:   /**
 282:    * Returns a BufferedImage backed by a Cairo surface, 
 283:    * created from a GtkImage.
 284:    */    
 285:   public static BufferedImage getBufferedImage(GtkImage image)
 286:   {
 287:     return getBufferedImage(new CairoSurface(image));
 288:   }
 289: 
 290:   /**
 291:    * Returns a BufferedImage backed by a Cairo surface.
 292:    */    
 293:   public static BufferedImage getBufferedImage(CairoSurface surface)
 294:   {
 295:     return new BufferedImage(cairoColorModel, surface,
 296:                              cairoColorModel.isAlphaPremultiplied(),
 297:                              new Hashtable());
 298:   }
 299: 
 300:   /**
 301:    * Return a Graphics2D drawing to the CairoSurface.
 302:    */
 303:   public Graphics2D getGraphics()
 304:   {
 305:     return new CairoSurfaceGraphics(this);
 306:   } 
 307: 
 308:   ///// Methods used by CairoSurfaceGraphics /////
 309:   /**
 310:    * Creates a cairo_t drawing context, returns the pointer as a long.
 311:    * Used by CairoSurfaceGraphics.
 312:    */
 313:   native long nativeNewCairoContext(long surfacePointer);
 314: 
 315:   public long newCairoContext()
 316:   {
 317:     return nativeNewCairoContext(surfacePointer);
 318:   }
 319: 
 320:   /**
 321:    * Copy a portion of this surface to another area on the surface.  The given
 322:    * parameters must be within bounds - count on a segfault otherwise.
 323:    * 
 324:    * @param x The x coordinate of the area to be copied from.
 325:    * @param y The y coordinate of the area to be copied from.
 326:    * @param width The width of the area to be copied.
 327:    * @param height The height of the area to be copied.
 328:    * @param dx The destination x coordinate.
 329:    * @param dy The destination y coordinate.
 330:    * @param stride The scanline stride.
 331:    */
 332:   public void copyAreaNative(int x, int y, int width,
 333:                              int height, int dx, int dy, int stride)
 334:   {
 335:     copyAreaNative2(surfacePointer, x, y, width, height, dx, dy, stride);
 336:   }
 337:   native void copyAreaNative2(long surfacePointer,
 338:                               int x, int y, int width, int height,
 339:                               int dx, int dy, int stride);
 340:   
 341:   /**
 342:    * Creates a SampleModel that matches Cairo's native format
 343:    */
 344:   protected static SampleModel createCairoSampleModel(int w, int h)
 345:   {
 346:     return new SinglePixelPackedSampleModel(DataBuffer.TYPE_INT, w, h,
 347:                                             new int[]{0x00FF0000, 0x0000FF00,
 348:                                                       0x000000FF, 0xFF000000});    
 349:   }
 350:   
 351:   /**
 352:    * Returns whether this ColorModel is compatible with Cairo's native types.
 353:    * 
 354:    * @param cm The color model to check.
 355:    * @return Whether it is compatible.
 356:    */
 357:   public static boolean isCompatibleColorModel(ColorModel cm)
 358:   {
 359:     return (cm.equals(cairoCM_pre) || cm.equals(cairoCM_opaque) ||
 360:             cm.equals(cairoColorModel));
 361:   }
 362:   
 363:   /**
 364:    * Returns whether this SampleModel is compatible with Cairo's native types.
 365:    * 
 366:    * @param sm The sample model to check.
 367:    * @return Whether it is compatible.
 368:    */
 369:   public static boolean isCompatibleSampleModel(SampleModel sm)
 370:   {
 371:     return (sm instanceof SinglePixelPackedSampleModel
 372:         && sm.getDataType() == DataBuffer.TYPE_INT
 373:         && Arrays.equals(((SinglePixelPackedSampleModel)sm).getBitMasks(),
 374:                          new int[]{0x00FF0000, 0x0000FF00,
 375:                                    0x000000FF, 0xFF000000}));
 376:   }
 377: 
 378:   ///// Methods interhited from Raster and WritableRaster /////
 379:   public Raster createChild(int parentX, int parentY, int width, int height,
 380:                             int childMinX, int childMinY, int[] bandList)
 381:   {
 382:     return createWritableChild(parentX, parentY, width, height,
 383:                                childMinX, childMinY, bandList);
 384:   }
 385:   
 386:   public WritableRaster createCompatibleWritableRaster()
 387:   {
 388:     return new CairoSurface(width, height);
 389:   }
 390:   
 391:   public WritableRaster createCompatibleWritableRaster (int x, int y,
 392:                                                         int w, int h)
 393:   {
 394:     return new CairoSurface(x, y, w, h);
 395:   }
 396:   
 397:   public Raster createTranslatedChild(int childMinX, int childMinY)
 398:   {
 399:     return createWritableTranslatedChild(childMinX, childMinY);
 400:   }
 401:   
 402:   public WritableRaster createWritableChild(int parentX, int parentY,
 403:                                             int w, int h, int childMinX,
 404:                                             int childMinY, int[] bandList)
 405:   {
 406:     if (parentX < minX || parentX + w > minX + width
 407:         || parentY < minY || parentY + h > minY + height)
 408:       throw new RasterFormatException("Child raster extends beyond parent");
 409:     
 410:     SampleModel sm = (bandList == null) ?
 411:       sampleModel :
 412:       sampleModel.createSubsetSampleModel(bandList);
 413: 
 414:     return new CairoSurface(sm, this,
 415:                             new Rectangle(childMinX, childMinY, w, h),
 416:                             new Point(sampleModelTranslateX + childMinX - parentX,
 417:                                       sampleModelTranslateY + childMinY - parentY));
 418:   }
 419:   
 420:   public WritableRaster createWritableTranslatedChild(int x, int y)
 421:   {
 422:     int tcx = sampleModelTranslateX - minX + x;
 423:     int tcy = sampleModelTranslateY - minY + y;
 424:     
 425:     return new CairoSurface(sampleModel, this,
 426:                       new Rectangle(x, y, width, height),
 427:                       new Point(tcx, tcy));
 428:   }
 429: }