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    */
create(int width, int height, int stride, int[] buf)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    */
destroy(long surfacePointer, int[] buf)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    */
nativeDrawSurface(long surfacePointer, long contextPointer, double[] i2u, double alpha, int interpolation)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    */
syncNativeToJava(long surfacePointer, int[] buffer)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    */
syncJavaToNative(long surfacePointer, int[] buffer)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    */
getFlippedBuffer(long surfacePointer)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    */
CairoSurface(int width, int height)152   public CairoSurface(int width, int height)
153   {
154     this(0, 0, width, height);
155   }
156 
CairoSurface(int x, int y, int width, int height)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    */
CairoSurface(SampleModel sm, CairoSurface parent, Rectangle bounds, Point origin)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    */
CairoSurface(GtkImage image)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    */
dispose()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    */
finalize()249   protected void finalize()
250   {
251     dispose();
252   }
253 
254   /**
255    * Return a GtkImage from this Cairo surface.
256    */
getGtkImage()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    */
getData()267   public int[] getData()
268   {
269     return ((DataBufferInt)dataBuffer).getData();
270   }
271 
272   /**
273    * Returns a BufferedImage backed by a Cairo surface.
274    */
getBufferedImage(int width, int height)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    */
getBufferedImage(GtkImage image)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    */
getBufferedImage(CairoSurface surface)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    */
getGraphics()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    */
nativeNewCairoContext(long surfacePointer)312   native long nativeNewCairoContext(long surfacePointer);
313 
newCairoContext()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    */
copyAreaNative(int x, int y, int width, int height, int dx, int dy, int stride)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   }
copyAreaNative2(long surfacePointer, int x, int y, int width, int height, int dx, int dy, int stride)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    */
createCairoSampleModel(int w, int h)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    */
isCompatibleColorModel(ColorModel cm)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    */
isCompatibleSampleModel(SampleModel sm)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 /////
createChild(int parentX, int parentY, int width, int height, int childMinX, int childMinY, int[] bandList)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 
createCompatibleWritableRaster()385   public WritableRaster createCompatibleWritableRaster()
386   {
387     return new CairoSurface(width, height);
388   }
389 
createCompatibleWritableRaster(int x, int y, int w, int h)390   public WritableRaster createCompatibleWritableRaster (int x, int y,
391                                                         int w, int h)
392   {
393     return new CairoSurface(x, y, w, h);
394   }
395 
createTranslatedChild(int childMinX, int childMinY)396   public Raster createTranslatedChild(int childMinX, int childMinY)
397   {
398     return createWritableTranslatedChild(childMinX, childMinY);
399   }
400 
createWritableChild(int parentX, int parentY, int w, int h, int childMinX, int childMinY, int[] bandList)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 
createWritableTranslatedChild(int x, int y)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 }
429