mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			539 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			539 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* BufferedImageGraphics.java
 | |
|    Copyright (C) 2006 Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| 
 | |
| package gnu.java.awt.peer.gtk;
 | |
| 
 | |
| import java.awt.AlphaComposite;
 | |
| import java.awt.Color;
 | |
| import java.awt.Composite;
 | |
| import java.awt.Graphics;
 | |
| import java.awt.Graphics2D;
 | |
| import java.awt.GraphicsConfiguration;
 | |
| 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.Rectangle2D;
 | |
| import java.awt.image.BufferedImage;
 | |
| import java.awt.image.ColorModel;
 | |
| import java.awt.image.DataBufferInt;
 | |
| import java.awt.image.ImageObserver;
 | |
| import java.awt.image.ImageProducer;
 | |
| import java.awt.image.Raster;
 | |
| import java.awt.image.RenderedImage;
 | |
| import java.awt.image.SinglePixelPackedSampleModel;
 | |
| import java.util.WeakHashMap;
 | |
| 
 | |
| /**
 | |
|  * Implementation of Graphics2D on a Cairo surface.
 | |
|  *
 | |
|  * Simutanously maintains a CairoSurface and updates the
 | |
|  * BufferedImage from that after each drawing operation.
 | |
|  */
 | |
| public class BufferedImageGraphics extends CairoGraphics2D
 | |
| {
 | |
|   /**
 | |
|    * the buffered Image.
 | |
|    */
 | |
|   private BufferedImage image, buffer;
 | |
| 
 | |
|   /**
 | |
|    * Image size.
 | |
|    */
 | |
|   private int imageWidth, imageHeight;
 | |
| 
 | |
|   /**
 | |
|    * The cairo surface that we actually draw on.
 | |
|    */
 | |
|   CairoSurface surface;
 | |
| 
 | |
|   /**
 | |
|    * Cache BufferedImageGraphics surfaces.
 | |
|    */
 | |
|   static WeakHashMap<BufferedImage, CairoSurface> bufferedImages
 | |
|     = new WeakHashMap<BufferedImage, CairoSurface>();
 | |
| 
 | |
|   /**
 | |
|    * Its corresponding cairo_t.
 | |
|    */
 | |
|   private long cairo_t;
 | |
| 
 | |
|   private boolean hasFastCM;
 | |
|   private boolean hasAlpha;
 | |
| 
 | |
| 
 | |
|   public BufferedImageGraphics(BufferedImage bi)
 | |
|   {
 | |
|     this.image = bi;
 | |
|     imageWidth = bi.getWidth();
 | |
|     imageHeight = bi.getHeight();
 | |
| 
 | |
|     if (!(image.getSampleModel() instanceof SinglePixelPackedSampleModel))
 | |
|       hasFastCM = false;
 | |
|     else if(bi.getColorModel().equals(CairoSurface.cairoCM_opaque))
 | |
|       {
 | |
|         hasFastCM = true;
 | |
|         hasAlpha = false;
 | |
|       }
 | |
|     else if(bi.getColorModel().equals(CairoSurface.cairoColorModel)
 | |
|         || bi.getColorModel().equals(CairoSurface.cairoCM_pre))
 | |
|       {
 | |
|         hasFastCM = true;
 | |
|         hasAlpha = true;
 | |
|       }
 | |
|     else
 | |
|       hasFastCM = false;
 | |
| 
 | |
|     // Cache surfaces.
 | |
|     if( bufferedImages.get( bi ) != null )
 | |
|       surface = bufferedImages.get( bi );
 | |
|     else
 | |
|       {
 | |
|         surface = new CairoSurface( imageWidth, imageHeight );
 | |
|         bufferedImages.put(bi, surface);
 | |
|       }
 | |
| 
 | |
|     cairo_t = surface.newCairoContext();
 | |
| 
 | |
|     // Get pixels out of buffered image and set in cairo surface
 | |
|     Raster raster = bi.getRaster();
 | |
|     int[] pixels;
 | |
| 
 | |
|     if (hasFastCM)
 | |
|       {
 | |
|         SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel();
 | |
|         int minX = image.getRaster().getSampleModelTranslateX();
 | |
|         int minY = image.getRaster().getSampleModelTranslateY();
 | |
| 
 | |
|         // Pull pixels directly out of data buffer
 | |
|         pixels = ((DataBufferInt)raster.getDataBuffer()).getData();
 | |
| 
 | |
|         // Discard pixels that fall outside of the image's bounds
 | |
|         // (ie, this image is actually a subimage of a different image)
 | |
|         if (!(sm.getScanlineStride() == imageWidth && minX == 0 && minY == 0))
 | |
|           {
 | |
|             int[] pixels2 = new int[imageWidth * imageHeight];
 | |
|             int scanline = sm.getScanlineStride();
 | |
| 
 | |
|             for (int i = 0; i < imageHeight; i++)
 | |
|               System.arraycopy(pixels, (i - minY) * scanline - minX, pixels2,
 | |
|                                i * imageWidth, imageWidth);
 | |
| 
 | |
|             pixels = pixels2;
 | |
|           }
 | |
| 
 | |
|         // Fill the alpha channel as opaque if image does not have alpha
 | |
|         if( !hasAlpha )
 | |
|           for(int i = 0; i < pixels.length; i++)
 | |
|             pixels[i] &= 0xFFFFFFFF;
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         pixels = CairoGraphics2D.findSimpleIntegerArray(image.getColorModel(),
 | |
|                                                         image.getData());
 | |
|         if (pixels != null)
 | |
|           System.arraycopy(pixels, 0, surface.getData(),
 | |
|                            0, pixels.length);
 | |
|       }
 | |
| 
 | |
|     setup( cairo_t );
 | |
|     setClip(0, 0, imageWidth, imageHeight);
 | |
|   }
 | |
| 
 | |
|   BufferedImageGraphics(BufferedImageGraphics copyFrom)
 | |
|   {
 | |
|     image = copyFrom.image;
 | |
|     surface = copyFrom.surface;
 | |
|     cairo_t = surface.newCairoContext();
 | |
|     imageWidth = copyFrom.imageWidth;
 | |
|     imageHeight = copyFrom.imageHeight;
 | |
| 
 | |
|     hasFastCM = copyFrom.hasFastCM;
 | |
|     hasAlpha = copyFrom.hasAlpha;
 | |
| 
 | |
|     copy( copyFrom, cairo_t );
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Update a rectangle of the bufferedImage. This can be improved upon a lot.
 | |
|    */
 | |
|   private void updateBufferedImage(int x, int y, int width, int height)
 | |
|   {
 | |
|     Rectangle bounds = new Rectangle(x, y, width, height);
 | |
|     bounds = getTransformedBounds(bounds, transform).getBounds();
 | |
|     x = bounds.x;
 | |
|     y = bounds.y;
 | |
|     width = bounds.width;
 | |
|     height = bounds.height;
 | |
| 
 | |
|     int[] pixels = surface.getData();
 | |
| 
 | |
|     if( x > imageWidth || y > imageHeight )
 | |
|       return;
 | |
| 
 | |
|     // Deal with negative width/height.
 | |
|     if (height < 0)
 | |
|       {
 | |
|         y += height;
 | |
|         height = -height;
 | |
|       }
 | |
|     if (width < 0)
 | |
|       {
 | |
|         x += width;
 | |
|         width = -width;
 | |
|       }
 | |
| 
 | |
|     // Clip edges.
 | |
|     if( x < 0 )
 | |
|       x = 0;
 | |
|     if( y < 0 )
 | |
|       y = 0;
 | |
| 
 | |
|     if( x + width > imageWidth )
 | |
|       width = imageWidth - x;
 | |
|     if( y + height > imageHeight )
 | |
|       height = imageHeight - y;
 | |
| 
 | |
|     if(!hasFastCM)
 | |
|       {
 | |
|         image.setRGB(x, y, width, height, pixels,
 | |
|                      x + y * imageWidth, imageWidth);
 | |
|         // The setRGB method assumes (or should assume) that pixels are NOT
 | |
|         // alpha-premultiplied, but Cairo stores data with premultiplication
 | |
|         // (thus the pixels returned in getPixels are premultiplied).
 | |
|         // This is ignored for consistency, however, since in
 | |
|         // CairoGrahpics2D.drawImage we also use non-premultiplied data
 | |
| 
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         int[] db = ((DataBufferInt)image.getRaster().getDataBuffer()).
 | |
|                   getData();
 | |
| 
 | |
|         // This should not fail, as we check the image sample model when we
 | |
|         // set the hasFastCM flag
 | |
|         SinglePixelPackedSampleModel sm = (SinglePixelPackedSampleModel)image.getSampleModel() ;
 | |
| 
 | |
|         int minX = image.getRaster().getSampleModelTranslateX();
 | |
|         int minY = image.getRaster().getSampleModelTranslateY();
 | |
| 
 | |
|         if (sm.getScanlineStride() == imageWidth && minX == 0)
 | |
|           {
 | |
|             System.arraycopy(pixels, y * imageWidth,
 | |
|                              db, (y - minY) * imageWidth,
 | |
|                              height * imageWidth);
 | |
|           }
 | |
|         else
 | |
|           {
 | |
|             int scanline = sm.getScanlineStride();
 | |
|             for (int i = y; i < (height + y); i++)
 | |
|               System.arraycopy(pixels, i * imageWidth + x, db,
 | |
|                                (i - minY) * scanline + x - minX, width);
 | |
| 
 | |
|           }
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Abstract methods.
 | |
|    */
 | |
|   public Graphics create()
 | |
|   {
 | |
|     return new BufferedImageGraphics(this);
 | |
|   }
 | |
| 
 | |
|   public GraphicsConfiguration getDeviceConfiguration()
 | |
|   {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   protected Rectangle2D getRealBounds()
 | |
|   {
 | |
|     return new Rectangle2D.Double(0.0, 0.0, imageWidth, imageHeight);
 | |
|   }
 | |
| 
 | |
|   public void copyAreaImpl(int x, int y, int width, int height, int dx, int dy)
 | |
|   {
 | |
|     surface.copyAreaNative(x, y, width, height, dx, dy, surface.width);
 | |
|     updateBufferedImage(x + dx, y + dy, width, height);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Overloaded methods that do actual drawing need to enter the gdk threads
 | |
|    * and also do certain things before and after.
 | |
|    */
 | |
|   public void draw(Shape s)
 | |
|   {
 | |
|     // Find total bounds of shape
 | |
|     Rectangle r = findStrokedBounds(s);
 | |
|     if (shiftDrawCalls)
 | |
|       {
 | |
|         r.width++;
 | |
|         r.height++;
 | |
|       }
 | |
| 
 | |
|     // Do the drawing
 | |
|     if (comp == null || comp instanceof AlphaComposite)
 | |
|       {
 | |
|         super.draw(s);
 | |
|         updateBufferedImage(r.x, r.y, r.width, r.height);
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         createBuffer();
 | |
| 
 | |
|         Graphics2D g2d = (Graphics2D)buffer.getGraphics();
 | |
|         g2d.setStroke(this.getStroke());
 | |
|         g2d.setColor(this.getColor());
 | |
|         g2d.setTransform(transform);
 | |
|         g2d.draw(s);
 | |
| 
 | |
|         drawComposite(r.getBounds2D(), null);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void fill(Shape s)
 | |
|   {
 | |
|     if (comp == null || comp instanceof AlphaComposite)
 | |
|       {
 | |
|         super.fill(s);
 | |
|         Rectangle r = s.getBounds();
 | |
|         updateBufferedImage(r.x, r.y, r.width, r.height);
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         createBuffer();
 | |
| 
 | |
|         Graphics2D g2d = (Graphics2D)buffer.getGraphics();
 | |
|         g2d.setPaint(this.getPaint());
 | |
|         g2d.setColor(this.getColor());
 | |
|         g2d.setTransform(transform);
 | |
|         g2d.fill(s);
 | |
| 
 | |
|         drawComposite(s.getBounds2D(), null);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void drawRenderedImage(RenderedImage image, AffineTransform xform)
 | |
|   {
 | |
|     if (comp == null || comp instanceof AlphaComposite)
 | |
|       {
 | |
|         super.drawRenderedImage(image, xform);
 | |
|         updateBufferedImage(0, 0, imageWidth, imageHeight);
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         createBuffer();
 | |
| 
 | |
|         Graphics2D g2d = (Graphics2D)buffer.getGraphics();
 | |
|         g2d.setRenderingHints(this.getRenderingHints());
 | |
|         g2d.setTransform(transform);
 | |
|         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)
 | |
|       {
 | |
|         boolean rv = super.drawImage(img, xform, bgcolor, obs);
 | |
|         updateBufferedImage(0, 0, imageWidth, imageHeight);
 | |
|         return rv;
 | |
|       }
 | |
|     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
 | |
|         Rectangle2D bounds = new Rectangle(bImg.getMinX(), bImg.getMinY(),
 | |
|                                            bImg.getWidth(), bImg.getHeight());
 | |
|         if (xform != null)
 | |
|           bounds = getTransformedBounds(bounds, xform);
 | |
| 
 | |
|         // Create buffer and draw image
 | |
|         createBuffer();
 | |
| 
 | |
|         Graphics2D g2d = (Graphics2D)buffer.getGraphics();
 | |
|         g2d.setRenderingHints(this.getRenderingHints());
 | |
|         g2d.drawImage(img, xform, obs);
 | |
| 
 | |
|         // Perform compositing
 | |
|         return drawComposite(bounds, obs);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   public void drawGlyphVector(GlyphVector gv, float x, float y)
 | |
|   {
 | |
|     // Find absolute bounds, in user-space, of this glyph vector
 | |
|     Rectangle2D bounds = gv.getLogicalBounds();
 | |
|     bounds = new Rectangle2D.Double(x + bounds.getX(), y + bounds.getY(),
 | |
|                                     bounds.getWidth(), bounds.getHeight());
 | |
| 
 | |
|     // Perform draw operation
 | |
|     if (comp == null || comp instanceof AlphaComposite)
 | |
|       {
 | |
|         super.drawGlyphVector(gv, x, y);
 | |
| 
 | |
|         // this returns an integer-based Rectangle (rather than a
 | |
|         // Rectangle2D), which takes care of any necessary rounding for us.
 | |
|         bounds = bounds.getBounds();
 | |
| 
 | |
|         updateBufferedImage((int)bounds.getX(), (int)bounds.getY(),
 | |
|                             (int)bounds.getWidth(), (int)bounds.getHeight());
 | |
|       }
 | |
|     else
 | |
|       {
 | |
|         createBuffer();
 | |
| 
 | |
|         Graphics2D g2d = (Graphics2D)buffer.getGraphics();
 | |
|         g2d.setPaint(this.getPaint());
 | |
|         g2d.setStroke(this.getStroke());
 | |
|         g2d.setTransform(transform);
 | |
|         g2d.drawGlyphVector(gv, x, y);
 | |
| 
 | |
|         drawComposite(bounds, null);
 | |
|       }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Perform composite drawing from the buffer onto the main image.
 | |
|    *
 | |
|    * The image to be composited should already be drawn into the buffer, in the
 | |
|    * proper place, after all necessary transforms have been applied.
 | |
|    *
 | |
|    * @param bounds The bounds to draw, in user-space.
 | |
|    * @param observer The image observer, if any (may be null).
 | |
|    * @return True on success, false on failure.
 | |
|    */
 | |
|   private boolean drawComposite(Rectangle2D bounds, ImageObserver observer)
 | |
|   {
 | |
|     // Find bounds in device space
 | |
|     bounds = getTransformedBounds(bounds, transform);
 | |
| 
 | |
|     // Clip bounds by the stored clip, and by the internal buffer
 | |
|     Rectangle2D devClip = this.getClipInDevSpace();
 | |
|     Rectangle2D.intersect(bounds, devClip, bounds);
 | |
|     devClip = new Rectangle(buffer.getMinX(), buffer.getMinY(),
 | |
|                             buffer.getWidth(), buffer.getHeight());
 | |
|     Rectangle2D.intersect(bounds, devClip, bounds);
 | |
| 
 | |
|     // Round bounds as needed, but be careful in our rounding
 | |
|     // (otherwise it may leave unpainted stripes)
 | |
|     double x = bounds.getX();
 | |
|     double y = bounds.getY();
 | |
|     double maxX = x + bounds.getWidth();
 | |
|     double maxY = y + bounds.getHeight();
 | |
|     x = Math.round(x);
 | |
|     y = Math.round(y);
 | |
|     bounds.setRect(x, y, Math.round(maxX - x), Math.round(maxY - y));
 | |
| 
 | |
|     // Find subimage of internal buffer for updating
 | |
|     BufferedImage buffer2 = buffer;
 | |
|     if (!bounds.equals(buffer2.getRaster().getBounds()))
 | |
|       buffer2 = buffer2.getSubimage((int)bounds.getX(), (int)bounds.getY(),
 | |
|                                     (int)bounds.getWidth(),
 | |
|                                     (int)bounds.getHeight());
 | |
| 
 | |
|     // Find subimage of main image for updating
 | |
|     BufferedImage current = image;
 | |
|     current = current.getSubimage((int)bounds.getX(), (int)bounds.getY(),
 | |
|                                   (int)bounds.getWidth(),
 | |
|                                   (int)bounds.getHeight());
 | |
| 
 | |
|     // Perform actual composite operation
 | |
|     compCtx.compose(buffer2.getRaster(), current.getRaster(),
 | |
|                     current.getRaster());
 | |
| 
 | |
|     // Set cairo's composite to direct SRC, since we've already done our own
 | |
|     // compositing
 | |
|     Composite oldcomp = comp;
 | |
|     setComposite(AlphaComposite.Src);
 | |
| 
 | |
|     // 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(current,
 | |
|                                  AffineTransform.getTranslateInstance(bounds.getX(),
 | |
|                                                                       bounds.getY()),
 | |
|                                  null, null);
 | |
|     setComposite(oldcomp);
 | |
|     updateColor();
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   private void createBuffer()
 | |
|   {
 | |
|     if (buffer == null)
 | |
|       {
 | |
|         buffer = new BufferedImage(image.getWidth(), image.getHeight(),
 | |
|                                    BufferedImage.TYPE_INT_ARGB);
 | |
|       }
 | |
|     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 image.getColorModel();
 | |
|   }
 | |
| 
 | |
|   protected ColorModel getBufferCM()
 | |
|   {
 | |
|     return ColorModel.getRGBdefault();
 | |
|   }
 | |
| }
 |