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