
2007-03-16 Francis Kung <fkung@redhat.com> * gnu/java/awt/peer/gtk/CairoGraphics2D.java (cairoArc): Make protected rather than private so it can be over-ridden. (cairoClip): Likewise. (cairoClosePath): Likewise. (cairoCurveTo): Likewise. (cairoDrawGlyphVector): Likewise. (cairoFill): Likewise. (cairoLineTo): Likewise. (cairoMoveTo): Likewise. (cairoNewPath): Likewise. (cairoRectangle): Likewise. (cairoResetClip): Likewise. (cairoRestore): Likewise. (cairoSave): Likewise. (cairoScale): Likewise. (cairoSetAntialias): Likewise. (cairoSetDash): Likewise. (cairoSetFillRule): Likewise. (cairoSetFont): Likewise. (cairoSetLine): Likewise. (cairoSetMatrix): Likewise. (cairoSetOperator): Likewise. (cairoSetRGBAColor): Likewise. (cairoStroke): Likewise. (drawPixels): Likewise. (init): Likewise. (setGradient): Likewise. (setPaintPixels): Likewise. (cairoDrawLine): Removed. (cairoDrawRect): Removed. (cairoFillRect): Removed. (cairoPreserveClip): Removed. (cairoRelCurveTo): Removed. (cairoRelLineTo): Removed. (cairoRelMoveTo): Removed. * gnu/java/awt/peer/gtk/ComponentGraphics.java (cairoArc): New method wrapping superclass method in locks. (cairoClip): Likewise. (cairoClosePath): Likewise. (cairoCurveTo): Likewise. (cairoDrawGlyphVector): Likewise. (cairoFill): Likewise. (cairoLineTo): Likewise. (cairoMoveTo): Likewise. (cairoNewPath): Likewise. (cairoRectangle): Likewise. (cairoResetClip): Likewise. (cairoRestore): Likewise. (cairoSave): Likewise. (cairoScale): Likewise. (cairoSetAntialias): Likewise. (cairoSetDash): Likewise. (cairoSetFillRule): Likewise. (cairoSetFont): Likewise. (cairoSetLine): Likewise. (cairoSetMatrix): Likewise. (cairoSetOperator): Likewise. (cairoSetRGBAColor): Likewise. (cairoStroke): Likewise. (disposeNative): Likewise. (drawPixels): Likewise. (init): Likewise. (setGradient): Likewise. (setPaintPixels): Likewise. (draw): Do not lock, as locking is now done in the wrapped native methods. (drawComposite): Likewise. (drawGlyphVector): Likewise. (drawImage): Likewise. (drawRenderedImage): Likewise. (fill): Likewise. (setClip): Removed. (lock): Added documentation. (unlock): Added documentation. * include/gnu_java_awt_peer_gtk_CairoGraphics2D.h: Regenerated. * include/gnu_java_awt_peer_gtk_ComponentGraphics.h: Regenerated. * lib/gnu/java/awt/peer/gtk/ComponentGraphics.class: Regenerated. * lib/gnu/java/awt/peer/gtk/CairoGraphics2D.class: Regenerated. * native/jni/gtk-peer/gnu_java_awt_peer_gtk_CairoGraphics2D.c (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoDrawLine): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoDrawRect): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoFillRect): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoPreserveClip): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoRelCurveTo): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoRelLineTo): Removed. (Java_gnu_java_awt_peer_gtk_CairoGraphics2D_cairoRelMoveTo): Removed. 2007-04-27 Thomas Fitzsimmons <fitzsim@redhat.com> * gnu/java/awt/peer/gtk/CairoGraphics2D.h: Regenerate. * gnu/java/awt/peer/gtk/ComponentGraphics.h: Regenerate. From-SVN: r124226
2002 lines
57 KiB
Java
2002 lines
57 KiB
Java
/* CairoGraphics2D.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 gnu.java.awt.ClasspathToolkit;
|
|
|
|
import java.awt.AWTPermission;
|
|
import java.awt.AlphaComposite;
|
|
import java.awt.BasicStroke;
|
|
import java.awt.Color;
|
|
import java.awt.Composite;
|
|
import java.awt.CompositeContext;
|
|
import java.awt.Font;
|
|
import java.awt.FontMetrics;
|
|
import java.awt.GradientPaint;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.GraphicsConfiguration;
|
|
import java.awt.Image;
|
|
import java.awt.Paint;
|
|
import java.awt.PaintContext;
|
|
import java.awt.Point;
|
|
import java.awt.Polygon;
|
|
import java.awt.Rectangle;
|
|
import java.awt.RenderingHints;
|
|
import java.awt.Shape;
|
|
import java.awt.Stroke;
|
|
import java.awt.TexturePaint;
|
|
import java.awt.Toolkit;
|
|
import java.awt.font.FontRenderContext;
|
|
import java.awt.font.GlyphVector;
|
|
import java.awt.font.TextLayout;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.awt.geom.Arc2D;
|
|
import java.awt.geom.Area;
|
|
import java.awt.geom.Ellipse2D;
|
|
import java.awt.geom.GeneralPath;
|
|
import java.awt.geom.Line2D;
|
|
import java.awt.geom.NoninvertibleTransformException;
|
|
import java.awt.geom.PathIterator;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.awt.geom.RoundRectangle2D;
|
|
import java.awt.image.AffineTransformOp;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.BufferedImageOp;
|
|
import java.awt.image.ColorModel;
|
|
import java.awt.image.DataBuffer;
|
|
import java.awt.image.DataBufferInt;
|
|
import java.awt.image.DirectColorModel;
|
|
import java.awt.image.ImageObserver;
|
|
import java.awt.image.ImageProducer;
|
|
import java.awt.image.ImagingOpException;
|
|
import java.awt.image.MultiPixelPackedSampleModel;
|
|
import java.awt.image.Raster;
|
|
import java.awt.image.RenderedImage;
|
|
import java.awt.image.SampleModel;
|
|
import java.awt.image.WritableRaster;
|
|
import java.awt.image.renderable.RenderContext;
|
|
import java.awt.image.renderable.RenderableImage;
|
|
import java.text.AttributedCharacterIterator;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* This is an abstract implementation of Graphics2D on Cairo.
|
|
*
|
|
* It should be subclassed for different Cairo contexts.
|
|
*
|
|
* Note for subclassers: Apart from the constructor (see comments below),
|
|
* The following abstract methods must be implemented:
|
|
*
|
|
* Graphics create()
|
|
* GraphicsConfiguration getDeviceConfiguration()
|
|
* copyArea(int x, int y, int width, int height, int dx, int dy)
|
|
*
|
|
* Also, dispose() must be overloaded to free any native datastructures
|
|
* used by subclass and in addition call super.dispose() to free the
|
|
* native cairographics2d structure and cairo_t.
|
|
*
|
|
* @author Sven de Marothy
|
|
*/
|
|
public abstract class CairoGraphics2D extends Graphics2D
|
|
{
|
|
static
|
|
{
|
|
System.loadLibrary("gtkpeer");
|
|
}
|
|
|
|
/**
|
|
* Important: This is a pointer to the native cairographics2d structure
|
|
*
|
|
* DO NOT CHANGE WITHOUT CHANGING NATIVE CODE.
|
|
*/
|
|
long nativePointer;
|
|
|
|
// Drawing state variables
|
|
/**
|
|
* The current paint
|
|
*/
|
|
Paint paint;
|
|
boolean customPaint;
|
|
|
|
/**
|
|
* The current stroke
|
|
*/
|
|
Stroke stroke;
|
|
|
|
/*
|
|
* Current foreground and background color.
|
|
*/
|
|
Color fg, bg;
|
|
|
|
/**
|
|
* Current clip shape.
|
|
*/
|
|
Shape clip;
|
|
|
|
/**
|
|
* Current transform.
|
|
*/
|
|
AffineTransform transform;
|
|
|
|
/**
|
|
* Current font.
|
|
*/
|
|
Font font;
|
|
|
|
/**
|
|
* The current compositing context, if any.
|
|
*/
|
|
Composite comp;
|
|
CompositeContext compCtx;
|
|
|
|
/**
|
|
* Rendering hint map.
|
|
*/
|
|
private RenderingHints hints;
|
|
|
|
/**
|
|
* Some operations (drawing rather than filling) require that their
|
|
* coords be shifted to land on 0.5-pixel boundaries, in order to land on
|
|
* "middle of pixel" coordinates and light up complete pixels.
|
|
*/
|
|
protected boolean shiftDrawCalls = false;
|
|
|
|
/**
|
|
* Keep track if the first clip to be set, which is restored on setClip(null);
|
|
*/
|
|
private boolean firstClip = true;
|
|
private Shape originalClip;
|
|
|
|
/**
|
|
* Stroke used for 3DRects
|
|
*/
|
|
private static BasicStroke draw3DRectStroke = new BasicStroke();
|
|
|
|
static ColorModel rgb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF);
|
|
static ColorModel argb32 = new DirectColorModel(32, 0xFF0000, 0xFF00, 0xFF,
|
|
0xFF000000);
|
|
|
|
/**
|
|
* Native constants for interpolation methods.
|
|
* Note, this corresponds to an enum in native/jni/gtk-peer/cairographics2d.h
|
|
*/
|
|
public static final int INTERPOLATION_NEAREST = 0,
|
|
INTERPOLATION_BILINEAR = 1,
|
|
INTERPOLATION_BICUBIC = 5,
|
|
ALPHA_INTERPOLATION_SPEED = 2,
|
|
ALPHA_INTERPOLATION_QUALITY = 3,
|
|
ALPHA_INTERPOLATION_DEFAULT = 4;
|
|
// TODO: Does ALPHA_INTERPOLATION really correspond to CAIRO_FILTER_FAST/BEST/GOOD?
|
|
|
|
/**
|
|
* Constructor does nothing.
|
|
*/
|
|
public CairoGraphics2D()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Sets up the default values and allocates the native cairographics2d structure
|
|
* @param cairo_t_pointer, a native pointer to a cairo_t of the context.
|
|
*/
|
|
public void setup(long cairo_t_pointer)
|
|
{
|
|
nativePointer = init(cairo_t_pointer);
|
|
setRenderingHints(new RenderingHints(getDefaultHints()));
|
|
setFont(new Font("SansSerif", Font.PLAIN, 12));
|
|
setColor(Color.black);
|
|
setBackground(Color.white);
|
|
setPaint(Color.black);
|
|
setStroke(new BasicStroke());
|
|
setTransform(new AffineTransform());
|
|
}
|
|
|
|
/**
|
|
* Same as above, but copies the state of another CairoGraphics2D.
|
|
*/
|
|
public void copy(CairoGraphics2D g, long cairo_t_pointer)
|
|
{
|
|
nativePointer = init(cairo_t_pointer);
|
|
paint = g.paint;
|
|
stroke = g.stroke;
|
|
setRenderingHints(g.hints);
|
|
|
|
Color foreground;
|
|
|
|
if (g.fg.getAlpha() != -1)
|
|
foreground = new Color(g.fg.getRed(), g.fg.getGreen(), g.fg.getBlue(),
|
|
g.fg.getAlpha());
|
|
else
|
|
foreground = new Color(g.fg.getRGB());
|
|
|
|
if (g.bg != null)
|
|
{
|
|
if (g.bg.getAlpha() != -1)
|
|
bg = new Color(g.bg.getRed(), g.bg.getGreen(), g.bg.getBlue(),
|
|
g.bg.getAlpha());
|
|
else
|
|
bg = new Color(g.bg.getRGB());
|
|
}
|
|
|
|
firstClip = g.firstClip;
|
|
originalClip = g.originalClip;
|
|
clip = g.getClip();
|
|
|
|
if (g.transform == null)
|
|
transform = null;
|
|
else
|
|
transform = new AffineTransform(g.transform);
|
|
|
|
setFont(g.font);
|
|
setColor(foreground);
|
|
setBackground(bg);
|
|
setPaint(paint);
|
|
setStroke(stroke);
|
|
setTransformImpl(transform);
|
|
setClip(clip);
|
|
setComposite(comp);
|
|
}
|
|
|
|
/**
|
|
* Generic destructor - call the native dispose() method.
|
|
*/
|
|
public void finalize()
|
|
{
|
|
dispose();
|
|
}
|
|
|
|
/**
|
|
* Disposes the native cairographics2d structure, including the
|
|
* cairo_t and any gradient stuff, if allocated.
|
|
* Subclasses should of course overload and call this if
|
|
* they have additional native structures.
|
|
*/
|
|
public void dispose()
|
|
{
|
|
disposeNative(nativePointer);
|
|
nativePointer = 0;
|
|
if (compCtx != null)
|
|
compCtx.dispose();
|
|
}
|
|
|
|
/**
|
|
* Allocate the cairographics2d structure and set the cairo_t pointer in it.
|
|
* @param pointer - a cairo_t pointer, casted to a long.
|
|
*/
|
|
protected native long init(long pointer);
|
|
|
|
/**
|
|
* These are declared abstract as there may be context-specific issues.
|
|
*/
|
|
public abstract Graphics create();
|
|
|
|
public abstract GraphicsConfiguration getDeviceConfiguration();
|
|
|
|
protected abstract void copyAreaImpl(int x, int y,
|
|
int width, int height, int dx, int dy);
|
|
|
|
|
|
/**
|
|
* Find the bounds of this graphics context, in device space.
|
|
*
|
|
* @return the bounds in device-space
|
|
*/
|
|
protected abstract Rectangle2D getRealBounds();
|
|
|
|
////// Native Methods ////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* Dispose of allocate native resouces.
|
|
*/
|
|
public native void disposeNative(long pointer);
|
|
|
|
/**
|
|
* Draw pixels as an RGBA int matrix
|
|
* @param w, h - width and height
|
|
* @param stride - stride of the array width
|
|
* @param i2u - affine transform array
|
|
*/
|
|
protected native void drawPixels(long pointer, int[] pixels, int w, int h,
|
|
int stride, double[] i2u, double alpha,
|
|
int interpolation);
|
|
|
|
protected native void setGradient(long pointer, double x1, double y1,
|
|
double x2, double y2,
|
|
int r1, int g1, int b1, int a1, int r2,
|
|
int g2, int b2, int a2, boolean cyclic);
|
|
|
|
protected native void setPaintPixels(long pointer, int[] pixels, int w,
|
|
int h, int stride, boolean repeat,
|
|
int x, int y);
|
|
|
|
/**
|
|
* Set the current transform matrix
|
|
*/
|
|
protected native void cairoSetMatrix(long pointer, double[] m);
|
|
|
|
/**
|
|
* Scaling method
|
|
*/
|
|
protected native void cairoScale(long pointer, double x, double y);
|
|
|
|
/**
|
|
* Set the compositing operator
|
|
*/
|
|
protected native void cairoSetOperator(long pointer, int cairoOperator);
|
|
|
|
/**
|
|
* Sets the current color in RGBA as a 0.0-1.0 double
|
|
*/
|
|
protected native void cairoSetRGBAColor(long pointer, double red, double green,
|
|
double blue, double alpha);
|
|
|
|
/**
|
|
* Sets the current winding rule in Cairo
|
|
*/
|
|
protected native void cairoSetFillRule(long pointer, int cairoFillRule);
|
|
|
|
/**
|
|
* Set the line style, cap, join and miter limit.
|
|
* Cap and join parameters are in the BasicStroke enumerations.
|
|
*/
|
|
protected native void cairoSetLine(long pointer, double width, int cap,
|
|
int join, double miterLimit);
|
|
|
|
/**
|
|
* Set the dash style
|
|
*/
|
|
protected native void cairoSetDash(long pointer, double[] dashes, int ndash,
|
|
double offset);
|
|
|
|
/*
|
|
* Draws a Glyph Vector
|
|
*/
|
|
native void cairoDrawGlyphVector(long pointer, GdkFontPeer font,
|
|
float x, float y, int n,
|
|
int[] codes, float[] positions);
|
|
|
|
/**
|
|
* Set the font in cairo.
|
|
*/
|
|
protected native void cairoSetFont(long pointer, GdkFontPeer font);
|
|
|
|
/**
|
|
* Appends a rectangle to the current path
|
|
*/
|
|
protected native void cairoRectangle(long pointer, double x, double y,
|
|
double width, double height);
|
|
|
|
/**
|
|
* Appends an arc to the current path
|
|
*/
|
|
protected native void cairoArc(long pointer, double x, double y,
|
|
double radius, double angle1, double angle2);
|
|
|
|
/**
|
|
* Save / restore a cairo path
|
|
*/
|
|
protected native void cairoSave(long pointer);
|
|
protected native void cairoRestore(long pointer);
|
|
|
|
/**
|
|
* New current path
|
|
*/
|
|
protected native void cairoNewPath(long pointer);
|
|
|
|
/**
|
|
* Close current path
|
|
*/
|
|
protected native void cairoClosePath(long pointer);
|
|
|
|
/** moveTo */
|
|
protected native void cairoMoveTo(long pointer, double x, double y);
|
|
|
|
/** lineTo */
|
|
protected native void cairoLineTo(long pointer, double x, double y);
|
|
|
|
/** Cubic curve-to */
|
|
protected native void cairoCurveTo(long pointer, double x1, double y1,
|
|
double x2, double y2,
|
|
double x3, double y3);
|
|
|
|
/**
|
|
* Stroke current path
|
|
*/
|
|
protected native void cairoStroke(long pointer);
|
|
|
|
/**
|
|
* Fill current path
|
|
*/
|
|
protected native void cairoFill(long pointer, double alpha);
|
|
|
|
/**
|
|
* Clip current path
|
|
*/
|
|
protected native void cairoClip(long pointer);
|
|
|
|
/**
|
|
* Save clip
|
|
*/
|
|
protected native void cairoResetClip(long pointer);
|
|
|
|
///////////////////////// TRANSFORMS ///////////////////////////////////
|
|
/**
|
|
* Set the current transform
|
|
*/
|
|
public void setTransform(AffineTransform tx)
|
|
{
|
|
// Transform clip into target space using the old transform.
|
|
updateClip(transform);
|
|
|
|
// Update the native transform.
|
|
setTransformImpl(tx);
|
|
|
|
// Transform the clip back into user space using the inverse new transform.
|
|
try
|
|
{
|
|
updateClip(transform.createInverse());
|
|
}
|
|
catch (NoninvertibleTransformException ex)
|
|
{
|
|
// TODO: How can we deal properly with this?
|
|
ex.printStackTrace();
|
|
}
|
|
|
|
if (clip != null)
|
|
setClip(clip);
|
|
}
|
|
|
|
private void setTransformImpl(AffineTransform tx)
|
|
{
|
|
transform = tx;
|
|
if (transform != null)
|
|
{
|
|
double[] m = new double[6];
|
|
transform.getMatrix(m);
|
|
cairoSetMatrix(nativePointer, m);
|
|
}
|
|
}
|
|
|
|
public void transform(AffineTransform tx)
|
|
{
|
|
if (transform == null)
|
|
transform = new AffineTransform(tx);
|
|
else
|
|
transform.concatenate(tx);
|
|
|
|
if (clip != null)
|
|
{
|
|
try
|
|
{
|
|
AffineTransform clipTransform = tx.createInverse();
|
|
updateClip(clipTransform);
|
|
}
|
|
catch (NoninvertibleTransformException ex)
|
|
{
|
|
// TODO: How can we deal properly with this?
|
|
ex.printStackTrace();
|
|
}
|
|
}
|
|
|
|
setTransformImpl(transform);
|
|
}
|
|
|
|
public void rotate(double theta)
|
|
{
|
|
transform(AffineTransform.getRotateInstance(theta));
|
|
}
|
|
|
|
public void rotate(double theta, double x, double y)
|
|
{
|
|
transform(AffineTransform.getRotateInstance(theta, x, y));
|
|
}
|
|
|
|
public void scale(double sx, double sy)
|
|
{
|
|
transform(AffineTransform.getScaleInstance(sx, sy));
|
|
}
|
|
|
|
/**
|
|
* Translate the system of the co-ordinates. As translation is a frequent
|
|
* operation, it is done in an optimised way, unlike scaling and rotating.
|
|
*/
|
|
public void translate(double tx, double ty)
|
|
{
|
|
if (transform != null)
|
|
transform.translate(tx, ty);
|
|
else
|
|
transform = AffineTransform.getTranslateInstance(tx, ty);
|
|
|
|
if (clip != null)
|
|
{
|
|
// FIXME: this should actuall try to transform the shape
|
|
// rather than degrade to bounds.
|
|
if (clip instanceof Rectangle2D)
|
|
{
|
|
Rectangle2D r = (Rectangle2D) clip;
|
|
r.setRect(r.getX() - tx, r.getY() - ty, r.getWidth(),
|
|
r.getHeight());
|
|
}
|
|
else
|
|
{
|
|
AffineTransform clipTransform =
|
|
AffineTransform.getTranslateInstance(-tx, -ty);
|
|
updateClip(clipTransform);
|
|
}
|
|
}
|
|
|
|
setTransformImpl(transform);
|
|
}
|
|
|
|
public void translate(int x, int y)
|
|
{
|
|
translate((double) x, (double) y);
|
|
}
|
|
|
|
public void shear(double shearX, double shearY)
|
|
{
|
|
transform(AffineTransform.getShearInstance(shearX, shearY));
|
|
}
|
|
|
|
///////////////////////// DRAWING STATE ///////////////////////////////////
|
|
|
|
public void clip(Shape s)
|
|
{
|
|
// Do not touch clip when s == null.
|
|
if (s == null)
|
|
{
|
|
// The spec says this should clear the clip. The reference
|
|
// implementation throws a NullPointerException instead. I think,
|
|
// in this case we should conform to the specs, as it shouldn't
|
|
// affect compatibility.
|
|
setClip(null);
|
|
return;
|
|
}
|
|
|
|
// If the current clip is still null, initialize it.
|
|
if (clip == null)
|
|
{
|
|
clip = getRealBounds();
|
|
}
|
|
|
|
// This is so common, let's optimize this.
|
|
if (clip instanceof Rectangle2D && s instanceof Rectangle2D)
|
|
{
|
|
Rectangle2D clipRect = (Rectangle2D) clip;
|
|
Rectangle2D r = (Rectangle2D) s;
|
|
Rectangle2D.intersect(clipRect, r, clipRect);
|
|
setClip(clipRect);
|
|
}
|
|
else
|
|
{
|
|
Area current;
|
|
if (clip instanceof Area)
|
|
current = (Area) clip;
|
|
else
|
|
current = new Area(clip);
|
|
|
|
Area intersect;
|
|
if (s instanceof Area)
|
|
intersect = (Area) s;
|
|
else
|
|
intersect = new Area(s);
|
|
|
|
current.intersect(intersect);
|
|
clip = current;
|
|
// Call setClip so that the native side gets notified.
|
|
setClip(clip);
|
|
}
|
|
}
|
|
|
|
public Paint getPaint()
|
|
{
|
|
return paint;
|
|
}
|
|
|
|
public AffineTransform getTransform()
|
|
{
|
|
return (AffineTransform) transform.clone();
|
|
}
|
|
|
|
public void setPaint(Paint p)
|
|
{
|
|
if (p == null)
|
|
return;
|
|
|
|
paint = p;
|
|
if (paint instanceof Color)
|
|
{
|
|
setColor((Color) paint);
|
|
customPaint = false;
|
|
}
|
|
else if (paint instanceof TexturePaint)
|
|
{
|
|
TexturePaint tp = (TexturePaint) paint;
|
|
BufferedImage img = tp.getImage();
|
|
|
|
// map the image to the anchor rectangle
|
|
int width = (int) tp.getAnchorRect().getWidth();
|
|
int height = (int) tp.getAnchorRect().getHeight();
|
|
|
|
double scaleX = width / (double) img.getWidth();
|
|
double scaleY = height / (double) img.getHeight();
|
|
|
|
AffineTransform at = new AffineTransform(scaleX, 0, 0, scaleY, 0, 0);
|
|
AffineTransformOp op = new AffineTransformOp(at, getRenderingHints());
|
|
BufferedImage texture = op.filter(img, null);
|
|
int[] pixels = texture.getRGB(0, 0, width, height, null, 0, width);
|
|
setPaintPixels(nativePointer, pixels, width, height, width, true, 0, 0);
|
|
customPaint = false;
|
|
}
|
|
else if (paint instanceof GradientPaint)
|
|
{
|
|
GradientPaint gp = (GradientPaint) paint;
|
|
Point2D p1 = gp.getPoint1();
|
|
Point2D p2 = gp.getPoint2();
|
|
Color c1 = gp.getColor1();
|
|
Color c2 = gp.getColor2();
|
|
setGradient(nativePointer, p1.getX(), p1.getY(), p2.getX(), p2.getY(),
|
|
c1.getRed(), c1.getGreen(), c1.getBlue(), c1.getAlpha(),
|
|
c2.getRed(), c2.getGreen(), c2.getBlue(), c2.getAlpha(),
|
|
gp.isCyclic());
|
|
customPaint = false;
|
|
}
|
|
else
|
|
{
|
|
customPaint = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets a custom paint
|
|
*
|
|
* @param bounds the bounding box, in user space
|
|
*/
|
|
protected void setCustomPaint(Rectangle bounds)
|
|
{
|
|
if (paint instanceof Color || paint instanceof TexturePaint
|
|
|| paint instanceof GradientPaint)
|
|
return;
|
|
|
|
int userX = bounds.x;
|
|
int userY = bounds.y;
|
|
int userWidth = bounds.width;
|
|
int userHeight = bounds.height;
|
|
|
|
// Find bounds in device space
|
|
Point2D origin = transform.transform(new Point2D.Double(userX, userY),
|
|
null);
|
|
Point2D extreme = transform.transform(new Point2D.Double(userWidth + userX,
|
|
userHeight + userY),
|
|
null);
|
|
int deviceX = (int)origin.getX();
|
|
int deviceY = (int)origin.getY();
|
|
int deviceWidth = (int)Math.ceil(extreme.getX() - origin.getX());
|
|
int deviceHeight = (int)Math.ceil(extreme.getY() - origin.getY());
|
|
|
|
// Get raster of the paint background
|
|
PaintContext pc = paint.createContext(CairoSurface.cairoColorModel,
|
|
new Rectangle(deviceX, deviceY,
|
|
deviceWidth,
|
|
deviceHeight),
|
|
bounds,
|
|
transform, hints);
|
|
|
|
Raster raster = pc.getRaster(deviceX, deviceY, deviceWidth,
|
|
deviceHeight);
|
|
|
|
// Clear the transform matrix in Cairo, since the raster returned by the
|
|
// PaintContext is already in device-space
|
|
AffineTransform oldTx = new AffineTransform(transform);
|
|
setTransformImpl(new AffineTransform());
|
|
|
|
// Set pixels in cairo, aligning the top-left of the background image
|
|
// to the top-left corner in device space
|
|
if (pc.getColorModel().equals(CairoSurface.cairoColorModel)
|
|
&& raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
|
|
{
|
|
// Use a fast copy if the paint context can uses a Cairo-compatible
|
|
// color model
|
|
setPaintPixels(nativePointer,
|
|
(int[])raster.getDataElements(0, 0, deviceWidth,
|
|
deviceHeight, null),
|
|
deviceWidth, deviceHeight, deviceWidth, false,
|
|
deviceX, deviceY);
|
|
}
|
|
|
|
else if (pc.getColorModel().equals(CairoSurface.cairoCM_opaque)
|
|
&& raster.getSampleModel().getTransferType() == DataBuffer.TYPE_INT)
|
|
{
|
|
// We can also optimize if the context uses a similar color model
|
|
// but without an alpha channel; we just add the alpha
|
|
int[] pixels = (int[])raster.getDataElements(0, 0, deviceWidth,
|
|
deviceHeight, null);
|
|
|
|
for (int i = 0; i < pixels.length; i++)
|
|
pixels[i] = 0xff000000 | (pixels[i] & 0x00ffffff);
|
|
|
|
setPaintPixels(nativePointer, pixels, deviceWidth, deviceHeight,
|
|
deviceWidth, false, deviceX, deviceY);
|
|
}
|
|
|
|
else
|
|
{
|
|
// Fall back on wrapping the raster in a BufferedImage, and
|
|
// use BufferedImage.getRGB() to do color-model conversion
|
|
WritableRaster wr = Raster.createWritableRaster(raster.getSampleModel(),
|
|
new Point(raster.getMinX(),
|
|
raster.getMinY()));
|
|
wr.setRect(raster);
|
|
|
|
BufferedImage img2 = new BufferedImage(pc.getColorModel(), wr,
|
|
pc.getColorModel().isAlphaPremultiplied(),
|
|
null);
|
|
|
|
setPaintPixels(nativePointer,
|
|
img2.getRGB(0, 0, deviceWidth, deviceHeight, null, 0,
|
|
deviceWidth),
|
|
deviceWidth, deviceHeight, deviceWidth, false,
|
|
deviceX, deviceY);
|
|
}
|
|
|
|
// Restore transform
|
|
setTransformImpl(oldTx);
|
|
}
|
|
|
|
public Stroke getStroke()
|
|
{
|
|
return stroke;
|
|
}
|
|
|
|
public void setStroke(Stroke st)
|
|
{
|
|
stroke = st;
|
|
if (stroke instanceof BasicStroke)
|
|
{
|
|
BasicStroke bs = (BasicStroke) stroke;
|
|
cairoSetLine(nativePointer, bs.getLineWidth(), bs.getEndCap(),
|
|
bs.getLineJoin(), bs.getMiterLimit());
|
|
|
|
float[] dashes = bs.getDashArray();
|
|
if (dashes != null)
|
|
{
|
|
double[] double_dashes = new double[dashes.length];
|
|
for (int i = 0; i < dashes.length; i++)
|
|
double_dashes[i] = dashes[i];
|
|
cairoSetDash(nativePointer, double_dashes, double_dashes.length,
|
|
(double) bs.getDashPhase());
|
|
}
|
|
else
|
|
cairoSetDash(nativePointer, new double[0], 0, 0.0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Utility method to find the bounds of a shape, including the stroke width.
|
|
*
|
|
* @param s the shape
|
|
* @return the bounds of the shape, including stroke width
|
|
*/
|
|
protected Rectangle findStrokedBounds(Shape s)
|
|
{
|
|
Rectangle r = s.getBounds();
|
|
|
|
if (stroke instanceof BasicStroke)
|
|
{
|
|
int strokeWidth = (int)Math.ceil(((BasicStroke)stroke).getLineWidth());
|
|
r.x -= strokeWidth / 2;
|
|
r.y -= strokeWidth / 2;
|
|
r.height += strokeWidth;
|
|
r.width += strokeWidth;
|
|
}
|
|
else
|
|
{
|
|
Shape s2 = stroke.createStrokedShape(s);
|
|
r = s2.getBounds();
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
public void setPaintMode()
|
|
{
|
|
setComposite(AlphaComposite.SrcOver);
|
|
}
|
|
|
|
public void setXORMode(Color c)
|
|
{
|
|
// FIXME: implement
|
|
}
|
|
|
|
public void setColor(Color c)
|
|
{
|
|
if (c == null)
|
|
c = Color.BLACK;
|
|
|
|
fg = c;
|
|
paint = c;
|
|
updateColor();
|
|
}
|
|
|
|
/**
|
|
* Set the current fg value as the cairo color.
|
|
*/
|
|
void updateColor()
|
|
{
|
|
if (fg == null)
|
|
fg = Color.BLACK;
|
|
cairoSetRGBAColor(nativePointer, fg.getRed() / 255.0,
|
|
fg.getGreen() / 255.0,fg.getBlue() / 255.0,
|
|
fg.getAlpha() / 255.0);
|
|
}
|
|
|
|
public Color getColor()
|
|
{
|
|
return fg;
|
|
}
|
|
|
|
public void clipRect(int x, int y, int width, int height)
|
|
{
|
|
if (clip == null)
|
|
setClip(new Rectangle(x, y, width, height));
|
|
else if (clip instanceof Rectangle)
|
|
{
|
|
computeIntersection(x, y, width, height, (Rectangle) clip);
|
|
setClip(clip);
|
|
}
|
|
else
|
|
clip(new Rectangle(x, y, width, height));
|
|
}
|
|
|
|
public Shape getClip()
|
|
{
|
|
if (clip == null)
|
|
return null;
|
|
else if (clip instanceof Rectangle2D)
|
|
return clip.getBounds2D(); //getClipInDevSpace();
|
|
else
|
|
{
|
|
GeneralPath p = new GeneralPath();
|
|
PathIterator pi = clip.getPathIterator(null);
|
|
p.append(pi, false);
|
|
return p;
|
|
}
|
|
}
|
|
|
|
public Rectangle getClipBounds()
|
|
{
|
|
if (clip == null)
|
|
return null;
|
|
else
|
|
return clip.getBounds();
|
|
}
|
|
|
|
protected Rectangle2D getClipInDevSpace()
|
|
{
|
|
Rectangle2D uclip = clip.getBounds2D();
|
|
if (transform == null)
|
|
return uclip;
|
|
else
|
|
{
|
|
Point2D pos = transform.transform(new Point2D.Double(uclip.getX(),
|
|
uclip.getY()),
|
|
(Point2D) null);
|
|
Point2D extent = transform.deltaTransform(new Point2D.Double(uclip
|
|
.getWidth(),
|
|
uclip
|
|
.getHeight()),
|
|
(Point2D) null);
|
|
return new Rectangle2D.Double(pos.getX(), pos.getY(), extent.getX(),
|
|
extent.getY());
|
|
}
|
|
}
|
|
|
|
public void setClip(int x, int y, int width, int height)
|
|
{
|
|
if( width < 0 || height < 0 )
|
|
return;
|
|
|
|
setClip(new Rectangle2D.Double(x, y, width, height));
|
|
}
|
|
|
|
public void setClip(Shape s)
|
|
{
|
|
// The first time the clip is set, save it as the original clip
|
|
// to reset to on s == null. We can rely on this being non-null
|
|
// because the constructor in subclasses is expected to set the
|
|
// initial clip properly.
|
|
if( firstClip )
|
|
{
|
|
originalClip = s;
|
|
firstClip = false;
|
|
}
|
|
|
|
clip = s;
|
|
cairoResetClip(nativePointer);
|
|
|
|
if (clip != null)
|
|
{
|
|
cairoNewPath(nativePointer);
|
|
if (clip instanceof Rectangle2D)
|
|
{
|
|
Rectangle2D r = (Rectangle2D) clip;
|
|
cairoRectangle(nativePointer, r.getX(), r.getY(), r.getWidth(),
|
|
r.getHeight());
|
|
}
|
|
else
|
|
walkPath(clip.getPathIterator(null), false);
|
|
|
|
cairoClip(nativePointer);
|
|
}
|
|
}
|
|
|
|
public void setBackground(Color c)
|
|
{
|
|
if (c == null)
|
|
c = Color.WHITE;
|
|
bg = c;
|
|
}
|
|
|
|
public Color getBackground()
|
|
{
|
|
return bg;
|
|
}
|
|
|
|
/**
|
|
* Return the current composite.
|
|
*/
|
|
public Composite getComposite()
|
|
{
|
|
if (comp == null)
|
|
return AlphaComposite.SrcOver;
|
|
else
|
|
return comp;
|
|
}
|
|
|
|
/**
|
|
* Sets the current composite context.
|
|
*/
|
|
public void setComposite(Composite comp)
|
|
{
|
|
if (this.comp == comp)
|
|
return;
|
|
|
|
this.comp = comp;
|
|
if (compCtx != null)
|
|
compCtx.dispose();
|
|
compCtx = null;
|
|
|
|
if (comp instanceof AlphaComposite)
|
|
{
|
|
AlphaComposite a = (AlphaComposite) comp;
|
|
cairoSetOperator(nativePointer, a.getRule());
|
|
}
|
|
|
|
else
|
|
{
|
|
cairoSetOperator(nativePointer, AlphaComposite.SRC_OVER);
|
|
|
|
if (comp != null)
|
|
{
|
|
// FIXME: this check is only required "if this Graphics2D
|
|
// context is drawing to a Component on the display screen".
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null)
|
|
sm.checkPermission(new AWTPermission("readDisplayPixels"));
|
|
|
|
compCtx = comp.createContext(getBufferCM(), getNativeCM(), hints);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Colour Model describing the native, raw image data for this
|
|
* specific peer.
|
|
*
|
|
* @return ColorModel the ColorModel of native data in this peer
|
|
*/
|
|
protected abstract ColorModel getNativeCM();
|
|
|
|
/**
|
|
* Returns the Color Model describing the buffer that this peer uses
|
|
* for custom composites.
|
|
*
|
|
* @return ColorModel the ColorModel of the composite buffer in this peer.
|
|
*/
|
|
protected ColorModel getBufferCM()
|
|
{
|
|
// This may be overridden by some subclasses
|
|
return getNativeCM();
|
|
}
|
|
|
|
///////////////////////// DRAWING PRIMITIVES ///////////////////////////////////
|
|
|
|
public void draw(Shape s)
|
|
{
|
|
if ((stroke != null && ! (stroke instanceof BasicStroke))
|
|
|| (comp instanceof AlphaComposite && ((AlphaComposite) comp).getAlpha() != 1.0))
|
|
{
|
|
// Cairo doesn't support stroking with alpha, so we create the stroked
|
|
// shape and fill with alpha instead
|
|
fill(stroke.createStrokedShape(s));
|
|
return;
|
|
}
|
|
|
|
if (customPaint)
|
|
{
|
|
Rectangle r = findStrokedBounds(s);
|
|
setCustomPaint(r);
|
|
}
|
|
|
|
createPath(s, true);
|
|
cairoStroke(nativePointer);
|
|
}
|
|
|
|
public void fill(Shape s)
|
|
{
|
|
createPath(s, false);
|
|
|
|
if (customPaint)
|
|
setCustomPaint(s.getBounds());
|
|
|
|
double alpha = 1.0;
|
|
if (comp instanceof AlphaComposite)
|
|
alpha = ((AlphaComposite) comp).getAlpha();
|
|
cairoFill(nativePointer, alpha);
|
|
}
|
|
|
|
private void createPath(Shape s, boolean isDraw)
|
|
{
|
|
cairoNewPath(nativePointer);
|
|
|
|
// Optimize rectangles, since there is a direct Cairo function
|
|
if (s instanceof Rectangle2D)
|
|
{
|
|
Rectangle2D r = (Rectangle2D) s;
|
|
|
|
// Pixels need to be shifted in draw operations to ensure that they
|
|
// light up entire pixels, but we also need to make sure the rectangle
|
|
// does not get distorted by this shifting operation
|
|
double x = shiftX(r.getX(),shiftDrawCalls && isDraw);
|
|
double y = shiftY(r.getY(), shiftDrawCalls && isDraw);
|
|
double w = Math.round(r.getWidth());
|
|
double h = Math.round(r.getHeight());
|
|
cairoRectangle(nativePointer, x, y, w, h);
|
|
}
|
|
|
|
// Lines are easy too
|
|
else if (s instanceof Line2D)
|
|
{
|
|
Line2D l = (Line2D) s;
|
|
cairoMoveTo(nativePointer, shiftX(l.getX1(), shiftDrawCalls && isDraw),
|
|
shiftY(l.getY1(), shiftDrawCalls && isDraw));
|
|
cairoLineTo(nativePointer, shiftX(l.getX2(), shiftDrawCalls && isDraw),
|
|
shiftY(l.getY2(), shiftDrawCalls && isDraw));
|
|
}
|
|
|
|
// We can optimize ellipses too; however we don't bother optimizing arcs:
|
|
// the iterator is fast enough (an ellipse requires 5 steps using the
|
|
// iterator, while most arcs are only 2-3)
|
|
else if (s instanceof Ellipse2D)
|
|
{
|
|
Ellipse2D e = (Ellipse2D) s;
|
|
|
|
double radius = Math.min(e.getHeight(), e.getWidth()) / 2;
|
|
|
|
// Cairo only draws circular shapes, but we can use a stretch to make
|
|
// them into ellipses
|
|
double xscale = 1, yscale = 1;
|
|
if (e.getHeight() != e.getWidth())
|
|
{
|
|
cairoSave(nativePointer);
|
|
|
|
if (e.getHeight() < e.getWidth())
|
|
xscale = e.getWidth() / (radius * 2);
|
|
else
|
|
yscale = e.getHeight() / (radius * 2);
|
|
|
|
if (xscale != 1 || yscale != 1)
|
|
cairoScale(nativePointer, xscale, yscale);
|
|
}
|
|
|
|
cairoArc(nativePointer,
|
|
shiftX(e.getCenterX() / xscale, shiftDrawCalls && isDraw),
|
|
shiftY(e.getCenterY() / yscale, shiftDrawCalls && isDraw),
|
|
radius, 0, Math.PI * 2);
|
|
|
|
if (xscale != 1 || yscale != 1)
|
|
cairoRestore(nativePointer);
|
|
}
|
|
|
|
// All other shapes are broken down and drawn in steps using the
|
|
// PathIterator
|
|
else
|
|
walkPath(s.getPathIterator(null), shiftDrawCalls && isDraw);
|
|
}
|
|
|
|
/**
|
|
* Note that the rest of the drawing methods go via fill() or draw() for the drawing,
|
|
* although subclasses may with to overload these methods where context-specific
|
|
* optimizations are possible (e.g. bitmaps and fillRect(int, int, int, int)
|
|
*/
|
|
|
|
public void clearRect(int x, int y, int width, int height)
|
|
{
|
|
if (bg != null)
|
|
cairoSetRGBAColor(nativePointer, bg.getRed() / 255.0,
|
|
bg.getGreen() / 255.0, bg.getBlue() / 255.0,
|
|
bg.getAlpha() / 255.0);
|
|
|
|
Composite oldcomp = comp;
|
|
setComposite(AlphaComposite.Src);
|
|
fillRect(x, y, width, height);
|
|
|
|
setComposite(oldcomp);
|
|
updateColor();
|
|
}
|
|
|
|
public void draw3DRect(int x, int y, int width, int height, boolean raised)
|
|
{
|
|
Stroke tmp = stroke;
|
|
setStroke(draw3DRectStroke);
|
|
super.draw3DRect(x, y, width, height, raised);
|
|
setStroke(tmp);
|
|
}
|
|
|
|
public void drawArc(int x, int y, int width, int height, int startAngle,
|
|
int arcAngle)
|
|
{
|
|
draw(new Arc2D.Double((double) x, (double) y, (double) width,
|
|
(double) height, (double) startAngle,
|
|
(double) arcAngle, Arc2D.OPEN));
|
|
}
|
|
|
|
public void drawLine(int x1, int y1, int x2, int y2)
|
|
{
|
|
// The coordinates being pairwise identical means one wants
|
|
// to draw a single pixel. This is emulated by drawing
|
|
// a one pixel sized rectangle.
|
|
if (x1 == x2 && y1 == y2)
|
|
fill(new Rectangle(x1, y1, 1, 1));
|
|
else
|
|
draw(new Line2D.Double(x1, y1, x2, y2));
|
|
}
|
|
|
|
public void drawRect(int x, int y, int width, int height)
|
|
{
|
|
draw(new Rectangle(x, y, width, height));
|
|
}
|
|
|
|
public void fillArc(int x, int y, int width, int height, int startAngle,
|
|
int arcAngle)
|
|
{
|
|
fill(new Arc2D.Double((double) x, (double) y, (double) width,
|
|
(double) height, (double) startAngle,
|
|
(double) arcAngle, Arc2D.PIE));
|
|
}
|
|
|
|
public void fillRect(int x, int y, int width, int height)
|
|
{
|
|
fill (new Rectangle(x, y, width, height));
|
|
}
|
|
|
|
public void fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
|
|
{
|
|
fill(new Polygon(xPoints, yPoints, nPoints));
|
|
}
|
|
|
|
public void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
|
|
{
|
|
draw(new Polygon(xPoints, yPoints, nPoints));
|
|
}
|
|
|
|
public void drawPolyline(int[] xPoints, int[] yPoints, int nPoints)
|
|
{
|
|
draw(new Polygon(xPoints, yPoints, nPoints));
|
|
}
|
|
|
|
public void drawOval(int x, int y, int width, int height)
|
|
{
|
|
drawArc(x, y, width, height, 0, 360);
|
|
}
|
|
|
|
public void drawRoundRect(int x, int y, int width, int height, int arcWidth,
|
|
int arcHeight)
|
|
{
|
|
draw(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
|
|
}
|
|
|
|
public void fillOval(int x, int y, int width, int height)
|
|
{
|
|
fillArc(x, y, width, height, 0, 360);
|
|
}
|
|
|
|
public void fillRoundRect(int x, int y, int width, int height, int arcWidth,
|
|
int arcHeight)
|
|
{
|
|
fill(new RoundRectangle2D.Double(x, y, width, height, arcWidth, arcHeight));
|
|
}
|
|
|
|
/**
|
|
* CopyArea - performs clipping to the native surface as a convenience
|
|
* (requires getRealBounds). Then calls copyAreaImpl.
|
|
*/
|
|
public void copyArea(int ox, int oy, int owidth, int oheight,
|
|
int odx, int ody)
|
|
{
|
|
Point2D pos = transform.transform(new Point2D.Double(ox, oy),
|
|
(Point2D) null);
|
|
Point2D dim = transform.transform(new Point2D.Double(ox + owidth,
|
|
oy + oheight),
|
|
(Point2D) null);
|
|
Point2D p2 = transform.transform(new Point2D.Double(ox + odx, oy + ody),
|
|
(Point2D) null);
|
|
int x = (int)pos.getX();
|
|
int y = (int)pos.getY();
|
|
int width = (int)(dim.getX() - pos.getX());
|
|
int height = (int)(dim.getY() - pos.getY());
|
|
int dx = (int)(p2.getX() - pos.getX());
|
|
int dy = (int)(p2.getY() - pos.getY());
|
|
|
|
Rectangle2D r = getRealBounds();
|
|
|
|
if( width <= 0 || height <= 0 )
|
|
return;
|
|
// Return if outside the surface
|
|
if( x + dx > r.getWidth() || y + dy > r.getHeight() )
|
|
return;
|
|
|
|
if( x + dx + width < r.getX() || y + dy + height < r.getY() )
|
|
return;
|
|
|
|
// Clip edges if necessary
|
|
if( x + dx < r.getX() ) // left
|
|
{
|
|
width = x + dx + width;
|
|
x = (int)r.getX() - dx;
|
|
}
|
|
|
|
if( y + dy < r.getY() ) // top
|
|
{
|
|
height = y + dy + height;
|
|
y = (int)r.getY() - dy;
|
|
}
|
|
|
|
if( x + dx + width >= r.getWidth() ) // right
|
|
width = (int)r.getWidth() - dx - x;
|
|
|
|
if( y + dy + height >= r.getHeight() ) // bottom
|
|
height = (int)r.getHeight() - dy - y;
|
|
|
|
copyAreaImpl(x, y, width, height, dx, dy);
|
|
}
|
|
|
|
///////////////////////// RENDERING HINTS ///////////////////////////////////
|
|
|
|
public void setRenderingHint(RenderingHints.Key hintKey, Object hintValue)
|
|
{
|
|
hints.put(hintKey, hintValue);
|
|
|
|
shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
|
|
|| hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
|
|
}
|
|
|
|
public Object getRenderingHint(RenderingHints.Key hintKey)
|
|
{
|
|
return hints.get(hintKey);
|
|
}
|
|
|
|
public void setRenderingHints(Map hints)
|
|
{
|
|
this.hints = new RenderingHints(getDefaultHints());
|
|
this.hints.add(new RenderingHints(hints));
|
|
|
|
shiftDrawCalls = hints.containsValue(RenderingHints.VALUE_STROKE_NORMALIZE)
|
|
|| hints.containsValue(RenderingHints.VALUE_STROKE_DEFAULT);
|
|
|
|
if (compCtx != null)
|
|
{
|
|
compCtx.dispose();
|
|
compCtx = comp.createContext(getNativeCM(), getNativeCM(), this.hints);
|
|
}
|
|
}
|
|
|
|
public void addRenderingHints(Map hints)
|
|
{
|
|
this.hints.add(new RenderingHints(hints));
|
|
}
|
|
|
|
public RenderingHints getRenderingHints()
|
|
{
|
|
return hints;
|
|
}
|
|
|
|
private int getInterpolation()
|
|
{
|
|
if (this.hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR))
|
|
return INTERPOLATION_NEAREST;
|
|
|
|
else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR))
|
|
return INTERPOLATION_BILINEAR;
|
|
|
|
else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BICUBIC))
|
|
return INTERPOLATION_BICUBIC;
|
|
|
|
else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED))
|
|
return ALPHA_INTERPOLATION_SPEED;
|
|
|
|
else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY))
|
|
return ALPHA_INTERPOLATION_QUALITY;
|
|
|
|
else if (hints.containsValue(RenderingHints.VALUE_ALPHA_INTERPOLATION_DEFAULT))
|
|
return ALPHA_INTERPOLATION_DEFAULT;
|
|
|
|
// Do bilinear interpolation as default
|
|
return INTERPOLATION_BILINEAR;
|
|
}
|
|
|
|
///////////////////////// IMAGE. METHODS ///////////////////////////////////
|
|
|
|
protected boolean drawImage(Image img, AffineTransform xform,
|
|
Color bgcolor, ImageObserver obs)
|
|
{
|
|
if (img == null)
|
|
return false;
|
|
|
|
if (xform == null)
|
|
xform = new AffineTransform();
|
|
|
|
// In this case, xform is an AffineTransform that transforms bounding
|
|
// box of the specified image from image space to user space. However
|
|
// when we pass this transform to cairo, cairo will use this transform
|
|
// to map "user coordinates" to "pixel" coordinates, which is the
|
|
// other way around. Therefore to get the "user -> pixel" transform
|
|
// that cairo wants from "image -> user" transform that we currently
|
|
// have, we will need to invert the transformation matrix.
|
|
AffineTransform invertedXform;
|
|
|
|
try
|
|
{
|
|
invertedXform = xform.createInverse();
|
|
}
|
|
catch (NoninvertibleTransformException e)
|
|
{
|
|
throw new ImagingOpException("Unable to invert transform "
|
|
+ xform.toString());
|
|
}
|
|
|
|
// Unrecognized image - convert to a BufferedImage
|
|
// Note - this can get us in trouble when the gdk lock is re-acquired.
|
|
// for example by VolatileImage. See ComponentGraphics for how we work
|
|
// around this.
|
|
img = AsyncImage.realImage(img, obs);
|
|
if( !(img instanceof BufferedImage) )
|
|
{
|
|
ImageProducer source = img.getSource();
|
|
if (source == null)
|
|
return false;
|
|
img = Toolkit.getDefaultToolkit().createImage(source);
|
|
}
|
|
|
|
BufferedImage b = (BufferedImage) img;
|
|
Raster raster;
|
|
double[] i2u = new double[6];
|
|
int width = b.getWidth();
|
|
int height = b.getHeight();
|
|
|
|
// If this BufferedImage has a BufferedImageGraphics object,
|
|
// use the cached CairoSurface that BIG is drawing onto
|
|
|
|
if( BufferedImageGraphics.bufferedImages.get( b ) != null )
|
|
raster = (Raster)BufferedImageGraphics.bufferedImages.get( b );
|
|
else
|
|
raster = b.getRaster();
|
|
|
|
invertedXform.getMatrix(i2u);
|
|
|
|
double alpha = 1.0;
|
|
if (comp instanceof AlphaComposite)
|
|
alpha = ((AlphaComposite) comp).getAlpha();
|
|
|
|
if(raster instanceof CairoSurface)
|
|
{
|
|
((CairoSurface)raster).drawSurface(nativePointer, i2u, alpha,
|
|
getInterpolation());
|
|
updateColor();
|
|
return true;
|
|
}
|
|
|
|
if( bgcolor != null )
|
|
{
|
|
Color oldColor = bg;
|
|
setBackground(bgcolor);
|
|
|
|
double[] origin = new double[] {0,0};
|
|
double[] dimensions = new double[] {width, height};
|
|
xform.transform(origin, 0, origin, 0, 1);
|
|
xform.deltaTransform(dimensions, 0, dimensions, 0, 1);
|
|
clearRect((int)origin[0], (int)origin[1],
|
|
(int)dimensions[0], (int)dimensions[1]);
|
|
|
|
setBackground(oldColor);
|
|
}
|
|
|
|
int[] pixels = b.getRGB(0, 0, width, height, null, 0, width);
|
|
|
|
// FIXME: The above method returns data in the standard ARGB colorspace,
|
|
// meaning data should NOT be alpha pre-multiplied; however Cairo expects
|
|
// data to be premultiplied.
|
|
|
|
drawPixels(nativePointer, pixels, width, height, width, i2u, alpha,
|
|
getInterpolation());
|
|
|
|
// Cairo seems to lose the current color which must be restored.
|
|
updateColor();
|
|
return true;
|
|
}
|
|
|
|
public void drawRenderedImage(RenderedImage image, AffineTransform xform)
|
|
{
|
|
drawRaster(image.getColorModel(), image.getData(), xform, null);
|
|
}
|
|
|
|
public void drawRenderableImage(RenderableImage image, AffineTransform xform)
|
|
{
|
|
drawRenderedImage(image.createRendering(new RenderContext(xform)), xform);
|
|
}
|
|
|
|
public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs)
|
|
{
|
|
return drawImage(img, xform, null, obs);
|
|
}
|
|
|
|
public void drawImage(BufferedImage image, BufferedImageOp op, int x, int y)
|
|
{
|
|
Image filtered = image;
|
|
if (op != null)
|
|
filtered = op.filter(image, null);
|
|
drawImage(filtered, new AffineTransform(1f, 0f, 0f, 1f, x, y), null, null);
|
|
}
|
|
|
|
public boolean drawImage(Image img, int x, int y, ImageObserver observer)
|
|
{
|
|
return drawImage(img, new AffineTransform(1f, 0f, 0f, 1f, x, y), null,
|
|
observer);
|
|
}
|
|
|
|
public boolean drawImage(Image img, int x, int y, Color bgcolor,
|
|
ImageObserver observer)
|
|
{
|
|
return drawImage(img, x, y, img.getWidth(observer),
|
|
img.getHeight(observer), bgcolor, observer);
|
|
}
|
|
|
|
public boolean drawImage(Image img, int x, int y, int width, int height,
|
|
Color bgcolor, ImageObserver observer)
|
|
{
|
|
double scaleX = width / (double) img.getWidth(observer);
|
|
double scaleY = height / (double) img.getHeight(observer);
|
|
if( scaleX == 0 || scaleY == 0 )
|
|
return true;
|
|
|
|
return drawImage(img, new AffineTransform(scaleX, 0f, 0f, scaleY, x, y),
|
|
bgcolor, observer);
|
|
}
|
|
|
|
public boolean drawImage(Image img, int x, int y, int width, int height,
|
|
ImageObserver observer)
|
|
{
|
|
return drawImage(img, x, y, width, height, null, observer);
|
|
}
|
|
|
|
public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
|
|
int sx1, int sy1, int sx2, int sy2, Color bgcolor,
|
|
ImageObserver observer)
|
|
{
|
|
if (img == null)
|
|
return false;
|
|
|
|
int sourceWidth = sx2 - sx1;
|
|
int sourceHeight = sy2 - sy1;
|
|
|
|
int destWidth = dx2 - dx1;
|
|
int destHeight = dy2 - dy1;
|
|
|
|
if(destWidth == 0 || destHeight == 0 || sourceWidth == 0 ||
|
|
sourceHeight == 0)
|
|
return true;
|
|
|
|
double scaleX = destWidth / (double) sourceWidth;
|
|
double scaleY = destHeight / (double) sourceHeight;
|
|
|
|
// FIXME: Avoid using an AT if possible here - it's at least twice as slow.
|
|
|
|
Shape oldClip = getClip();
|
|
int cx, cy, cw, ch;
|
|
if( dx1 < dx2 )
|
|
{ cx = dx1; cw = dx2 - dx1; }
|
|
else
|
|
{ cx = dx2; cw = dx1 - dx2; }
|
|
if( dy1 < dy2 )
|
|
{ cy = dy1; ch = dy2 - dy1; }
|
|
else
|
|
{ cy = dy2; ch = dy1 - dy2; }
|
|
|
|
clipRect( cx, cy, cw, ch );
|
|
|
|
AffineTransform tx = new AffineTransform();
|
|
tx.translate( dx1 - sx1*scaleX, dy1 - sy1*scaleY );
|
|
tx.scale( scaleX, scaleY );
|
|
|
|
boolean retval = drawImage(img, tx, bgcolor, observer);
|
|
setClip( oldClip );
|
|
return retval;
|
|
}
|
|
|
|
public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2,
|
|
int sx1, int sy1, int sx2, int sy2,
|
|
ImageObserver observer)
|
|
{
|
|
return drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, null, observer);
|
|
}
|
|
|
|
///////////////////////// TEXT METHODS ////////////////////////////////////
|
|
|
|
public void drawString(String str, float x, float y)
|
|
{
|
|
if (str == null || str.length() == 0)
|
|
return;
|
|
GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
|
|
TextLayout tl = (TextLayout) fontPeer.textLayoutCache.get(str);
|
|
if (tl == null)
|
|
{
|
|
tl = new TextLayout( str, getFont(), getFontRenderContext() );
|
|
fontPeer.textLayoutCache.put(str, tl);
|
|
}
|
|
tl.draw(this, x, y);
|
|
}
|
|
|
|
public void drawString(String str, int x, int y)
|
|
{
|
|
drawString (str, (float) x, (float) y);
|
|
}
|
|
|
|
public void drawString(AttributedCharacterIterator ci, int x, int y)
|
|
{
|
|
drawString (ci, (float) x, (float) y);
|
|
}
|
|
|
|
public void drawGlyphVector(GlyphVector gv, float x, float y)
|
|
{
|
|
double alpha = 1.0;
|
|
|
|
if( gv.getNumGlyphs() <= 0 )
|
|
return;
|
|
|
|
if (customPaint)
|
|
setCustomPaint(gv.getOutline().getBounds());
|
|
|
|
if (comp instanceof AlphaComposite)
|
|
alpha = ((AlphaComposite) comp).getAlpha();
|
|
if (gv instanceof FreetypeGlyphVector && alpha == 1.0)
|
|
{
|
|
int n = gv.getNumGlyphs ();
|
|
int[] codes = gv.getGlyphCodes (0, n, null);
|
|
float[] positions = gv.getGlyphPositions (0, n, null);
|
|
|
|
setFont (gv.getFont ());
|
|
GdkFontPeer fontPeer = (GdkFontPeer) font.getPeer();
|
|
synchronized (fontPeer)
|
|
{
|
|
cairoDrawGlyphVector(nativePointer, fontPeer,
|
|
x, y, n, codes, positions);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
translate(x, y);
|
|
fill(gv.getOutline());
|
|
translate(-x, -y);
|
|
}
|
|
}
|
|
|
|
public void drawString(AttributedCharacterIterator ci, float x, float y)
|
|
{
|
|
GlyphVector gv = getFont().createGlyphVector(getFontRenderContext(), ci);
|
|
drawGlyphVector(gv, x, y);
|
|
}
|
|
|
|
/**
|
|
* Should perhaps be contexct dependent, but this is left for now as an
|
|
* overloadable default implementation.
|
|
*/
|
|
public FontRenderContext getFontRenderContext()
|
|
{
|
|
return new FontRenderContext(transform, true, true);
|
|
}
|
|
|
|
// Until such time as pango is happy to talk directly to cairo, we
|
|
// actually need to redirect some calls from the GtkFontPeer and
|
|
// GtkFontMetrics into the drawing kit and ask cairo ourselves.
|
|
|
|
public FontMetrics getFontMetrics()
|
|
{
|
|
return getFontMetrics(getFont());
|
|
}
|
|
|
|
public FontMetrics getFontMetrics(Font f)
|
|
{
|
|
return ((GdkFontPeer) f.getPeer()).getFontMetrics(f);
|
|
}
|
|
|
|
public void setFont(Font f)
|
|
{
|
|
// Sun's JDK does not throw NPEs, instead it leaves the current setting
|
|
// unchanged. So do we.
|
|
if (f == null)
|
|
return;
|
|
|
|
if (f.getPeer() instanceof GdkFontPeer)
|
|
font = f;
|
|
else
|
|
font =
|
|
((ClasspathToolkit)(Toolkit.getDefaultToolkit()))
|
|
.getFont(f.getName(), f.getAttributes());
|
|
|
|
GdkFontPeer fontpeer = (GdkFontPeer) getFont().getPeer();
|
|
synchronized (fontpeer)
|
|
{
|
|
cairoSetFont(nativePointer, fontpeer);
|
|
}
|
|
}
|
|
|
|
public Font getFont()
|
|
{
|
|
if (font == null)
|
|
return new Font("SansSerif", Font.PLAIN, 12);
|
|
return font;
|
|
}
|
|
|
|
/////////////////////// MISC. PUBLIC METHODS /////////////////////////////////
|
|
|
|
public boolean hit(Rectangle rect, Shape s, boolean onStroke)
|
|
{
|
|
if( onStroke )
|
|
{
|
|
Shape stroked = stroke.createStrokedShape( s );
|
|
return stroked.intersects( (double)rect.x, (double)rect.y,
|
|
(double)rect.width, (double)rect.height );
|
|
}
|
|
return s.intersects( (double)rect.x, (double)rect.y,
|
|
(double)rect.width, (double)rect.height );
|
|
}
|
|
|
|
public String toString()
|
|
{
|
|
return (getClass().getName()
|
|
+ "[font=" + getFont().toString()
|
|
+ ",color=" + fg.toString()
|
|
+ "]");
|
|
}
|
|
|
|
///////////////////////// PRIVATE METHODS ///////////////////////////////////
|
|
|
|
/**
|
|
* All the drawImage() methods eventually get delegated here if the image
|
|
* is not a Cairo surface.
|
|
*
|
|
* @param bgcolor - if non-null draws the background color before
|
|
* drawing the image.
|
|
*/
|
|
private boolean drawRaster(ColorModel cm, Raster r,
|
|
AffineTransform imageToUser, Color bgcolor)
|
|
{
|
|
if (r == null)
|
|
return false;
|
|
|
|
SampleModel sm = r.getSampleModel();
|
|
DataBuffer db = r.getDataBuffer();
|
|
|
|
if (db == null || sm == null)
|
|
return false;
|
|
|
|
if (cm == null)
|
|
cm = ColorModel.getRGBdefault();
|
|
|
|
double[] i2u = new double[6];
|
|
if (imageToUser != null)
|
|
imageToUser.getMatrix(i2u);
|
|
else
|
|
{
|
|
i2u[0] = 1;
|
|
i2u[1] = 0;
|
|
i2u[2] = 0;
|
|
i2u[3] = 1;
|
|
i2u[4] = 0;
|
|
i2u[5] = 0;
|
|
}
|
|
|
|
int[] pixels = findSimpleIntegerArray(cm, r);
|
|
|
|
if (pixels == null)
|
|
{
|
|
// FIXME: I don't think this code will work correctly with a non-RGB
|
|
// MultiPixelPackedSampleModel. Although this entire method should
|
|
// probably be rewritten to better utilize Cairo's different supported
|
|
// data formats.
|
|
if (sm instanceof MultiPixelPackedSampleModel)
|
|
{
|
|
pixels = r.getPixels(0, 0, r.getWidth(), r.getHeight(), pixels);
|
|
for (int i = 0; i < pixels.length; i++)
|
|
pixels[i] = cm.getRGB(pixels[i]);
|
|
}
|
|
else
|
|
{
|
|
pixels = new int[r.getWidth() * r.getHeight()];
|
|
for (int i = 0; i < pixels.length; i++)
|
|
pixels[i] = cm.getRGB(db.getElem(i));
|
|
}
|
|
}
|
|
|
|
// Change all transparent pixels in the image to the specified bgcolor,
|
|
// or (if there's no alpha) fill in an alpha channel so that it paints
|
|
// correctly.
|
|
if (cm.hasAlpha())
|
|
{
|
|
if (bgcolor != null && cm.hasAlpha())
|
|
for (int i = 0; i < pixels.length; i++)
|
|
{
|
|
if (cm.getAlpha(pixels[i]) == 0)
|
|
pixels[i] = bgcolor.getRGB();
|
|
}
|
|
}
|
|
else
|
|
for (int i = 0; i < pixels.length; i++)
|
|
pixels[i] |= 0xFF000000;
|
|
|
|
double alpha = 1.0;
|
|
if (comp instanceof AlphaComposite)
|
|
alpha = ((AlphaComposite) comp).getAlpha();
|
|
drawPixels(nativePointer, pixels, r.getWidth(), r.getHeight(),
|
|
r.getWidth(), i2u, alpha, getInterpolation());
|
|
|
|
// Cairo seems to lose the current color which must be restored.
|
|
updateColor();
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Shifts an x-coordinate by 0.5 in device space.
|
|
*/
|
|
private double shiftX(double coord, boolean doShift)
|
|
{
|
|
if (doShift)
|
|
{
|
|
double shift = 0.5;
|
|
if (!transform.isIdentity())
|
|
shift /= transform.getScaleX();
|
|
return Math.round(coord) + shift;
|
|
}
|
|
else
|
|
return coord;
|
|
}
|
|
|
|
/**
|
|
* Shifts a y-coordinate by 0.5 in device space.
|
|
*/
|
|
private double shiftY(double coord, boolean doShift)
|
|
{
|
|
if (doShift)
|
|
{
|
|
double shift = 0.5;
|
|
if (!transform.isIdentity())
|
|
shift /= transform.getScaleY();
|
|
return Math.round(coord) + shift;
|
|
}
|
|
else
|
|
return coord;
|
|
}
|
|
|
|
/**
|
|
* Adds a pathIterator to the current Cairo path, also sets the cairo winding rule.
|
|
*/
|
|
private void walkPath(PathIterator p, boolean doShift)
|
|
{
|
|
double x = 0;
|
|
double y = 0;
|
|
double[] coords = new double[6];
|
|
|
|
cairoSetFillRule(nativePointer, p.getWindingRule());
|
|
for (; ! p.isDone(); p.next())
|
|
{
|
|
int seg = p.currentSegment(coords);
|
|
switch (seg)
|
|
{
|
|
case PathIterator.SEG_MOVETO:
|
|
x = shiftX(coords[0], doShift);
|
|
y = shiftY(coords[1], doShift);
|
|
cairoMoveTo(nativePointer, x, y);
|
|
break;
|
|
case PathIterator.SEG_LINETO:
|
|
x = shiftX(coords[0], doShift);
|
|
y = shiftY(coords[1], doShift);
|
|
cairoLineTo(nativePointer, x, y);
|
|
break;
|
|
case PathIterator.SEG_QUADTO:
|
|
// splitting a quadratic bezier into a cubic:
|
|
// see: http://pfaedit.sourceforge.net/bezier.html
|
|
double x1 = x + (2.0 / 3.0) * (shiftX(coords[0], doShift) - x);
|
|
double y1 = y + (2.0 / 3.0) * (shiftY(coords[1], doShift) - y);
|
|
|
|
double x2 = x1 + (1.0 / 3.0) * (shiftX(coords[2], doShift) - x);
|
|
double y2 = y1 + (1.0 / 3.0) * (shiftY(coords[3], doShift) - y);
|
|
|
|
x = shiftX(coords[2], doShift);
|
|
y = shiftY(coords[3], doShift);
|
|
cairoCurveTo(nativePointer, x1, y1, x2, y2, x, y);
|
|
break;
|
|
case PathIterator.SEG_CUBICTO:
|
|
x = shiftX(coords[4], doShift);
|
|
y = shiftY(coords[5], doShift);
|
|
cairoCurveTo(nativePointer, shiftX(coords[0], doShift),
|
|
shiftY(coords[1], doShift),
|
|
shiftX(coords[2], doShift),
|
|
shiftY(coords[3], doShift), x, y);
|
|
break;
|
|
case PathIterator.SEG_CLOSE:
|
|
cairoClosePath(nativePointer);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Used by setRenderingHints()
|
|
*/
|
|
private Map getDefaultHints()
|
|
{
|
|
HashMap defaultHints = new HashMap();
|
|
|
|
defaultHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
|
|
RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
|
|
|
|
defaultHints.put(RenderingHints.KEY_STROKE_CONTROL,
|
|
RenderingHints.VALUE_STROKE_DEFAULT);
|
|
|
|
defaultHints.put(RenderingHints.KEY_FRACTIONALMETRICS,
|
|
RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
|
|
|
|
defaultHints.put(RenderingHints.KEY_ANTIALIASING,
|
|
RenderingHints.VALUE_ANTIALIAS_OFF);
|
|
|
|
defaultHints.put(RenderingHints.KEY_RENDERING,
|
|
RenderingHints.VALUE_RENDER_DEFAULT);
|
|
|
|
return defaultHints;
|
|
}
|
|
|
|
/**
|
|
* Used by drawRaster and GdkPixbufDecoder
|
|
*/
|
|
public static int[] findSimpleIntegerArray (ColorModel cm, Raster raster)
|
|
{
|
|
if (cm == null || raster == null)
|
|
return null;
|
|
|
|
if (! cm.getColorSpace().isCS_sRGB())
|
|
return null;
|
|
|
|
if (! (cm instanceof DirectColorModel))
|
|
return null;
|
|
|
|
DirectColorModel dcm = (DirectColorModel) cm;
|
|
|
|
if (dcm.getRedMask() != 0x00FF0000 || dcm.getGreenMask() != 0x0000FF00
|
|
|| dcm.getBlueMask() != 0x000000FF)
|
|
return null;
|
|
|
|
if (! (raster instanceof WritableRaster))
|
|
return null;
|
|
|
|
if (raster.getSampleModel().getDataType() != DataBuffer.TYPE_INT)
|
|
return null;
|
|
|
|
if (! (raster.getDataBuffer() instanceof DataBufferInt))
|
|
return null;
|
|
|
|
DataBufferInt db = (DataBufferInt) raster.getDataBuffer();
|
|
|
|
if (db.getNumBanks() != 1)
|
|
return null;
|
|
|
|
// Finally, we have determined that this is a single bank, [A]RGB-int
|
|
// buffer in sRGB space. It's worth checking all this, because it means
|
|
// that cairo can paint directly into the data buffer, which is very
|
|
// fast compared to all the normal copying and converting.
|
|
|
|
return db.getData();
|
|
}
|
|
|
|
/**
|
|
* Helper method to transform the clip. This is called by the various
|
|
* transformation-manipulation methods to update the clip (which is in
|
|
* userspace) accordingly.
|
|
*
|
|
* The transform usually is the inverse transform that was applied to the
|
|
* graphics object.
|
|
*
|
|
* @param t the transform to apply to the clip
|
|
*/
|
|
private void updateClip(AffineTransform t)
|
|
{
|
|
if (clip == null)
|
|
return;
|
|
|
|
if (! (clip instanceof GeneralPath))
|
|
clip = new GeneralPath(clip);
|
|
|
|
GeneralPath p = (GeneralPath) clip;
|
|
p.transform(t);
|
|
}
|
|
|
|
private static Rectangle computeIntersection(int x, int y, int w, int h,
|
|
Rectangle rect)
|
|
{
|
|
int x2 = (int) rect.x;
|
|
int y2 = (int) rect.y;
|
|
int w2 = (int) rect.width;
|
|
int h2 = (int) rect.height;
|
|
|
|
int dx = (x > x2) ? x : x2;
|
|
int dy = (y > y2) ? y : y2;
|
|
int dw = (x + w < x2 + w2) ? (x + w - dx) : (x2 + w2 - dx);
|
|
int dh = (y + h < y2 + h2) ? (y + h - dy) : (y2 + h2 - dy);
|
|
|
|
if (dw >= 0 && dh >= 0)
|
|
rect.setBounds(dx, dy, dw, dh);
|
|
else
|
|
rect.setBounds(0, 0, 0, 0);
|
|
|
|
return rect;
|
|
}
|
|
}
|