javafx.scene.canvas.GraphicsContext Maven / Gradle / Ivy
Show all versions of openjfx-78-backport Show documentation
/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.scene.canvas;
import com.sun.javafx.geom.Arc2D;
import com.sun.javafx.geom.Path2D;
import com.sun.javafx.geom.PathIterator;
import com.sun.javafx.geom.transform.Affine2D;
import com.sun.javafx.geom.transform.NoninvertibleTransformException;
import com.sun.javafx.image.BytePixelGetter;
import com.sun.javafx.image.BytePixelSetter;
import com.sun.javafx.image.ByteToBytePixelConverter;
import com.sun.javafx.image.IntPixelGetter;
import com.sun.javafx.image.IntToBytePixelConverter;
import com.sun.javafx.image.PixelConverter;
import com.sun.javafx.image.PixelGetter;
import com.sun.javafx.image.PixelUtils;
import com.sun.javafx.image.impl.ByteBgraPre;
import com.sun.javafx.sg.GrowableDataBuffer;
import com.sun.javafx.sg.PGCanvas;
import com.sun.javafx.tk.Toolkit;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.LinkedList;
import javafx.geometry.VPos;
import javafx.scene.effect.Blend;
import javafx.scene.effect.BlendMode;
import javafx.scene.effect.Effect;
import javafx.scene.image.Image;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.PixelWriter;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.FillRule;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.scene.text.Font;
import javafx.scene.text.TextAlignment;
import javafx.scene.transform.Affine;
/**
* This class is used to issue draw calls to a {@code Canvas} using a buffer.
*
* Each call pushes the necessary parameters onto the buffer
* where it is executed on the image of the {@code Canvas} node.
*
* A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer.
* If it is not attached to any scene, then it can be modified by any thread,
* as long as it is only used from one thread at a time. Once a {@code Canvas}
* node is attached to a scene, it must be modified on the JavaFX Application
* Thread.
*
* Calling any method on the {@code GraphicsContext} is considered modifying
* its corresponding {@code Canvas} and is subject to the same threading
* rules.
*
* A {@code GraphicsContext} also manages a stack of state objects that can
* be saved or restored at anytime.
*
*
Example:
*
*
*
import javafx.scene.*;
import javafx.scene.paint.*;
import javafx.scene.canvas.*;
Group root = new Group();
Scene s = new Scene(root, 300, 300, Color.BLACK);
final Canvas canvas = new Canvas(250,250);
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.BLUE);
gc.fillRect(75,75,100,100);
root.getChildren().add(canvas);
*
*
*
* @since JavaFX 2.2
*/
public final class GraphicsContext {
Canvas theCanvas;
Path2D path;
boolean pathDirty;
State curState;
LinkedList stateStack;
LinkedList clipStack;
GraphicsContext(Canvas theCanvas) {
this.theCanvas = theCanvas;
this.path = new Path2D();
pathDirty = true;
this.curState = new State();
this.stateStack = new LinkedList();
this.clipStack = new LinkedList();
}
static class State {
double globalAlpha;
BlendMode blendop;
Affine2D transform;
Paint fill;
Paint stroke;
double linewidth;
StrokeLineCap linecap;
StrokeLineJoin linejoin;
double miterlimit;
int numClipPaths;
Font font;
TextAlignment textalign;
VPos textbaseline;
Effect effect;
FillRule fillRule;
State() {
this(1.0, BlendMode.SRC_OVER,
new Affine2D(),
Color.BLACK, Color.BLACK,
1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0,
0, Font.getDefault(), TextAlignment.LEFT, VPos.BASELINE,
null, FillRule.NON_ZERO);
}
State(State copy) {
this(copy.globalAlpha, copy.blendop,
new Affine2D(copy.transform),
copy.fill, copy.stroke,
copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit,
copy.numClipPaths,
copy.font, copy.textalign, copy.textbaseline,
copy.effect, copy.fillRule);
}
State(double globalAlpha, BlendMode blendop,
Affine2D transform, Paint fill, Paint stroke,
double linewidth, StrokeLineCap linecap,
StrokeLineJoin linejoin, double miterlimit,
int numClipPaths,
Font font, TextAlignment align, VPos baseline,
Effect effect, FillRule fillRule)
{
this.globalAlpha = globalAlpha;
this.blendop = blendop;
this.transform = transform;
this.fill = fill;
this.stroke = stroke;
this.linewidth = linewidth;
this.linecap = linecap;
this.linejoin = linejoin;
this.miterlimit = miterlimit;
this.numClipPaths = numClipPaths;
this.font = font;
this.textalign = align;
this.textbaseline = baseline;
this.effect = effect;
this.fillRule = fillRule;
}
State copy() {
return new State(this);
}
void restore(GraphicsContext ctx) {
ctx.setGlobalAlpha(globalAlpha);
ctx.setGlobalBlendMode(blendop);
ctx.setTransform(transform.getMxx(), transform.getMyx(),
transform.getMxy(), transform.getMyy(),
transform.getMxt(), transform.getMyt());
ctx.setFill(fill);
ctx.setStroke(stroke);
ctx.setLineWidth(linewidth);
ctx.setLineCap(linecap);
ctx.setLineJoin(linejoin);
ctx.setMiterLimit(miterlimit);
GrowableDataBuffer buf = ctx.getBuffer();
while (ctx.curState.numClipPaths > numClipPaths) {
ctx.curState.numClipPaths--;
ctx.clipStack.removeLast();
buf.putByte(PGCanvas.POP_CLIP);
}
ctx.setFillRule(fillRule);
ctx.setFont(font);
ctx.setTextAlign(textalign);
ctx.setTextBaseline(textbaseline);
ctx.setEffect(effect);
}
}
private GrowableDataBuffer getBuffer() {
return theCanvas.getBuffer();
}
private static float coords[] = new float[6];
private static final byte pgtype[] = {
PGCanvas.MOVETO,
PGCanvas.LINETO,
PGCanvas.QUADTO,
PGCanvas.CUBICTO,
PGCanvas.CLOSEPATH,
};
private static final int numsegs[] = { 2, 2, 4, 6, 0, };
private void markPathDirty() {
pathDirty = true;
}
private void writePath(byte command) {
updateTransform();
GrowableDataBuffer buf = getBuffer();
if (pathDirty) {
buf.putByte(PGCanvas.PATHSTART);
PathIterator pi = path.getPathIterator(null);
while (!pi.isDone()) {
int pitype = pi.currentSegment(coords);
buf.putByte(pgtype[pitype]);
for (int i = 0; i < numsegs[pitype]; i++) {
buf.putFloat(coords[i]);
}
pi.next();
}
buf.putByte(PGCanvas.PATHEND);
pathDirty = false;
}
buf.putByte(command);
}
private void writePaint(Paint p, byte command) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(command);
buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p));
}
private void writeArcType(ArcType closure) {
byte type;
switch (closure) {
case OPEN: type = PGCanvas.ARC_OPEN; break;
case CHORD: type = PGCanvas.ARC_CHORD; break;
case ROUND: type = PGCanvas.ARC_PIE; break;
default: return; // ignored for consistency with other attributes
}
writeParam(type, PGCanvas.ARC_TYPE);
}
private void writeRectParams(GrowableDataBuffer buf,
double x, double y, double w, double h,
byte command)
{
buf.putByte(command);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) w);
buf.putFloat((float) h);
}
private void writeOp4(double x, double y, double w, double h, byte command) {
updateTransform();
writeRectParams(getBuffer(), x, y, w, h, command);
}
private void writeOp6(double x, double y, double w, double h,
double v1, double v2, byte command)
{
updateTransform();
GrowableDataBuffer buf = getBuffer();
buf.putByte(command);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) w);
buf.putFloat((float) h);
buf.putFloat((float) v1);
buf.putFloat((float) v2);
}
private float polybuf[] = new float[512];
private void flushPolyBuf(GrowableDataBuffer buf,
float polybuf[], int n, byte command)
{
curState.transform.transform(polybuf, 0, polybuf, 0, n/2);
for (int i = 0; i < n; i += 2) {
buf.putByte(command);
buf.putFloat(polybuf[i]);
buf.putFloat(polybuf[i+1]);
command = PGCanvas.LINETO;
}
}
private void writePoly(double xPoints[], double yPoints[], int nPoints,
boolean close, byte command)
{
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.PATHSTART);
int pos = 0;
byte polycmd = PGCanvas.MOVETO;
for (int i = 0; i < nPoints; i++) {
if (pos >= polybuf.length) {
flushPolyBuf(buf, polybuf, pos, polycmd);
polycmd = PGCanvas.LINETO;
}
polybuf[pos++] = (float) xPoints[i];
polybuf[pos++] = (float) yPoints[i];
}
flushPolyBuf(buf, polybuf, pos, polycmd);
if (close) {
buf.putByte(PGCanvas.CLOSEPATH);
}
buf.putByte(PGCanvas.PATHEND);
buf.putByte(command);
// Now that we have changed the PG layer path, we need to mark our path dirty.
markPathDirty();
}
private void writeImage(Image img,
double dx, double dy, double dw, double dh)
{
if (img.getProgress() < 1.0) return;
Object platformImg = img.impl_getPlatformImage();
if (platformImg == null) return;
updateTransform();
GrowableDataBuffer buf = getBuffer();
writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_IMAGE);
buf.putObject(platformImg);
}
private void writeImage(Image img,
double dx, double dy, double dw, double dh,
double sx, double sy, double sw, double sh)
{
if (img.getProgress() < 1.0) return;
Object platformImg = img.impl_getPlatformImage();
if (platformImg == null) return;
updateTransform();
GrowableDataBuffer buf = getBuffer();
writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_SUBIMAGE);
buf.putFloat((float) sx);
buf.putFloat((float) sy);
buf.putFloat((float) sw);
buf.putFloat((float) sh);
buf.putObject(platformImg);
}
private void writeText(String text, double x, double y, double maxWidth,
byte command)
{
updateTransform();
GrowableDataBuffer buf = getBuffer();
buf.putByte(command);
buf.putFloat((float) x);
buf.putFloat((float) y);
buf.putFloat((float) maxWidth);
buf.putObject(text);
}
private void writeParam(double v, byte command) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(command);
buf.putFloat((float) v);
}
private void writeParam(byte v, byte command) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(command);
buf.putByte(v);
}
private boolean txdirty;
private void updateTransform() {
if (txdirty) {
txdirty = false;
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.TRANSFORM);
buf.putDouble(curState.transform.getMxx());
buf.putDouble(curState.transform.getMxy());
buf.putDouble(curState.transform.getMxt());
buf.putDouble(curState.transform.getMyx());
buf.putDouble(curState.transform.getMyy());
buf.putDouble(curState.transform.getMyt());
}
}
/**
* Gets the {@code Canvas} that the {@code GraphicsContext} is issuing draw
* commands to. There is only ever one {@code Canvas} for a
* {@code GraphicsContext}.
*
* @return Canvas the canvas that this {@code GraphicsContext} is issuing draw
* commands to.
*/
public Canvas getCanvas() {
return theCanvas;
}
/**
* Saves the following attributes onto a stack.
*
* - Global Alpha
* - Global Blend Operation
* - Transform
* - Fill Paint
* - Stroke Paint
* - Line Width
* - Line Cap
* - Line Join
* - Miter Limit
* - Number of Clip Paths
* - Font
* - Text Align
* - Text Baseline
* - Effect
* - Fill Rule
*
* This method does NOT alter the current state in any way. Also, not that
* the current path is not saved.
*/
public void save() {
stateStack.push(curState.copy());
}
/**
* Pops the state off of the stack, setting the following attributes to their
* value at the time when that state was pushed onto the stack. If the stack
* is empty then nothing is changed.
*
*
* - Global Alpha
* - Global Blend Operation
* - Transform
* - Fill Paint
* - Stroke Paint
* - Line Width
* - Line Cap
* - Line Join
* - Miter Limit
* - Number of Clip Paths
* - Font
* - Text Align
* - Text Baseline
* - Effect
* - Fill Rule
*
*/
public void restore() {
if (!stateStack.isEmpty()) {
State savedState = stateStack.pop();
savedState.restore(this);
txdirty = true;
}
}
/**
* Translates the current transform by x, y.
* @param x value to translate along the x axis.
* @param y value to translate along the y axis.
*/
public void translate(double x, double y) {
curState.transform.translate(x, y);
txdirty = true;
}
/**
* Scales the current transform by x, y.
* @param x value to scale in the x axis.
* @param y value to scale in the y axis.
*/
public void scale(double x, double y) {
curState.transform.scale(x, y);
txdirty = true;
}
/**
* Rotates the current transform in degrees.
* @param degrees value in degrees to rotate the current transform.
*/
public void rotate(double degrees) {
curState.transform.rotate(Math.toRadians(degrees));
txdirty = true;
}
private static Affine2D scratchTX = new Affine2D();
/**
* Concatenates the input with the current transform.
*
* @param mxx - the X coordinate scaling element of the 3x4 matrix
* @param myx - the Y coordinate shearing element of the 3x4 matrix
* @param mxy - the X coordinate shearing element of the 3x4 matrix
* @param myy - the Y coordinate scaling element of the 3x4 matrix
* @param mxt - the X coordinate translation element of the 3x4 matrix
* @param myt - the Y coordinate translation element of the 3x4 matrix
*/
public void transform(double mxx, double myx,
double mxy, double myy,
double mxt, double myt)
{
scratchTX.setTransform(mxx, myx,
mxy, myy,
mxt, myt);
curState.transform.concatenate(scratchTX);
txdirty = true;
}
/**
* Concatenates the input with the current transform. Only 2D transforms are
* supported. The only values used are the X and Y scaling, translation, and
* shearing components of a transform.
*
* @param xform The affine to be concatenated with the current transform.
*/
public void transform(Affine xform) {
scratchTX.setTransform(xform.getMxx(), xform.getMyx(),
xform.getMxy(), xform.getMyy(),
xform.getTx(), xform.getTy());
curState.transform.concatenate(scratchTX);
txdirty = true;
}
/**
* Sets the current transform.
* @param mxx - the X coordinate scaling element of the 3x4 matrix
* @param myx - the Y coordinate shearing element of the 3x4 matrix
* @param mxy - the X coordinate shearing element of the 3x4 matrix
* @param myy - the Y coordinate scaling element of the 3x4 matrix
* @param mxt - the X coordinate translation element of the 3x4 matrix
* @param myt - the Y coordinate translation element of the 3x4 matrix
*/
public void setTransform(double mxx, double myx,
double mxy, double myy,
double mxt, double myt)
{
curState.transform.setTransform(mxx, myx,
mxy, myy,
mxt, myt);
txdirty = true;
}
/**
* Sets the current transform. Only 2D transforms are supported. The only
* values used are the X and Y scaling, translation, and shearing components
* of a transform.
*
* @param xform The affine to be copied and used as the current transform.
*/
public void setTransform(Affine xform) {
curState.transform.setTransform(xform.getMxx(), xform.getMyx(),
xform.getMxy(), xform.getMyy(),
xform.getTx(), xform.getTy());
txdirty = true;
}
/**
* Returns a copy of the current transform.
*
* @param xform A transform object that will be used to hold the result.
* If xform is non null, then this method will copy the current transform
* into that object. If xform is null a new transform object will be
* constructed. In either case, the return value is a copy of the current
* transform.
*
* @return A copy of the current transform.
*/
public Affine getTransform(Affine xform) {
if (xform == null) {
xform = new Affine();
}
xform.setMxx(curState.transform.getMxx());
xform.setMxy(curState.transform.getMxy());
xform.setMxz(0);
xform.setTx(curState.transform.getMxt());
xform.setMyx(curState.transform.getMyx());
xform.setMyy(curState.transform.getMyy());
xform.setMyz(0);
xform.setTy(curState.transform.getMyt());
xform.setMzx(0);
xform.setMzy(0);
xform.setMzz(1);
xform.setTz(0);
return xform;
}
/**
* Returns a copy of the current transform.
*
* @return a copy of the transform of the current state.
*/
public Affine getTransform() {
return getTransform(null);
}
/**
* Sets the global alpha of the current state.
*
* @param alpha value in the range {@code 0.0-1.0}. The value is clamped if it is
* out of range.
*/
public void setGlobalAlpha(double alpha) {
if (curState.globalAlpha != alpha) {
curState.globalAlpha = alpha;
alpha = (alpha > 1.0) ? 1.0 : (alpha < 0.0) ? 0.0 : alpha;
writeParam(alpha, PGCanvas.GLOBAL_ALPHA);
}
}
/**
* Gets the current global alpha.
*
* @return the current global alpha.
*/
public double getGlobalAlpha() {
return curState.globalAlpha;
}
private static Blend TMP_BLEND = new Blend(BlendMode.SRC_OVER);
/**
* Sets the global blend mode.
*
* @param op the {@code BlendMode} that will be set.
*/
public void setGlobalBlendMode(BlendMode op) {
if (op != null && op != curState.blendop) {
GrowableDataBuffer buf = getBuffer();
curState.blendop = op;
TMP_BLEND.setMode(op);
TMP_BLEND.impl_sync();
buf.putByte(PGCanvas.COMP_MODE);
buf.putObject(((com.sun.scenario.effect.Blend)TMP_BLEND.impl_getImpl()).getMode());
}
}
/**
* Gets the global blend mode.
*
* @return the global {@code BlendMode} of the current state.
*/
public BlendMode getGlobalBlendMode() {
return curState.blendop;
}
/**
* Sets the current fill attribute. This method affects the paint used for any
* method with "fill" in it. For Example, fillRect(...), fillOval(...).
*
* @param p The {@code Paint} to be used as the fill {@code Paint}.
*/
public void setFill(Paint p) {
if (curState.fill != p) {
curState.fill = p;
writePaint(p, PGCanvas.FILL_PAINT);
}
}
/**
* Gets the current fill attribute. This method affects the paint used for any
* method with "fill" in it. For Example, fillRect(...), fillOval(...).
*
* @return p The {@code Paint} to be used as the fill {@code Paint}.
*/
public Paint getFill() {
return curState.fill;
}
/**
* Sets the current stroke.
*
* @param p The Paint to be used as the stroke Paint.
*/
public void setStroke(Paint p) {
if (curState.stroke != p) {
curState.stroke = p;
writePaint(p, PGCanvas.STROKE_PAINT);
}
}
/**
* Gets the current stroke.
*
* @return the {@code Paint} to be used as the stroke {@code Paint}.
*/
public Paint getStroke() {
return curState.stroke;
}
/**
* Sets the current line width.
*
* @param lw value in the range {0-positive infinity}, with any other value
* being ignored and leaving the value unchanged.
*/
public void setLineWidth(double lw) {
// Per W3C spec: On setting, zero, negative, infinite, and NaN
// values must be ignored, leaving the value unchanged
if (lw > 0 && lw < Double.POSITIVE_INFINITY) {
if (curState.linewidth != lw) {
curState.linewidth = lw;
writeParam(lw, PGCanvas.LINE_WIDTH);
}
}
}
/**
* Gets the current line width.
*
* @return value between 0 and infinity.
*/
public double getLineWidth() {
return curState.linewidth;
}
/**
* Sets the current stroke line cap.
*
* @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square.
*/
public void setLineCap(StrokeLineCap cap) {
if (curState.linecap != cap) {
byte v;
switch (cap) {
case BUTT: v = PGCanvas.CAP_BUTT; break;
case ROUND: v = PGCanvas.CAP_ROUND; break;
case SQUARE: v = PGCanvas.CAP_SQUARE; break;
default: return;
}
curState.linecap = cap;
writeParam(v, PGCanvas.LINE_CAP);
}
}
/**
* Gets the current stroke line cap.
*
* @return {@code StrokeLineCap} with a value of Butt, Round, or Square.
*/
public StrokeLineCap getLineCap() {
return curState.linecap;
}
/**
* Sets the current stroke line join.
*
* @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
*/
public void setLineJoin(StrokeLineJoin join) {
if (curState.linejoin != join) {
byte v;
switch (join) {
case MITER: v = PGCanvas.JOIN_MITER; break;
case BEVEL: v = PGCanvas.JOIN_BEVEL; break;
case ROUND: v = PGCanvas.JOIN_ROUND; break;
default: return;
}
curState.linejoin = join;
writeParam(v, PGCanvas.LINE_JOIN);
}
}
/**
* Gets the current stroke line join.
*
* @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
*/
public StrokeLineJoin getLineJoin() {
return curState.linejoin;
}
/**
* Sets the current miter limit.
*
* @param ml miter limit value between 0 and positive infinity with
* any other value being ignored and leaving the value unchanged.
*/
public void setMiterLimit(double ml) {
// Per W3C spec: On setting, zero, negative, infinite, and NaN
// values must be ignored, leaving the value unchanged
if (ml > 0.0 && ml < Double.POSITIVE_INFINITY) {
if (curState.miterlimit != ml) {
curState.miterlimit = ml;
writeParam(ml, PGCanvas.MITER_LIMIT);
}
}
}
/**
* Gets the current miter limit.
*
* @return the miter limit value in the range {@code 0.0-positive infinity}
*/
public double getMiterLimit() {
return curState.miterlimit;
}
/**
* Sets the current Font.
*
* @param f the Font
*/
public void setFont(Font f) {
if (curState.font != f) {
curState.font = f;
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.FONT);
buf.putObject(f.impl_getNativeFont());
}
}
/**
* Gets the current Font.
*
* @return the Font
*/
public Font getFont() {
return curState.font;
}
/**
* Defines horizontal text alignment, relative to the text {@code x} origin.
*
* Let horizontal bounds represent the logical width of a single line of
* text. Where each line of text has a separate horizontal bounds.
*
* Then TextAlignment is specified as:
*
* - Left: the left edge of the horizontal bounds will be at {@code x}.
*
- Center: the center, halfway between left and right edge, of the
* horizontal bounds will be at {@code x}.
*
- Right: the right edge of the horizontal bounds will be at {@code x}.
*
*
*
* Note: Canvas does not support line wrapping, therefore the text
* alignment Justify is identical to left aligned text.
*
*
* @param align {@code TextAlignment} with values of Left, Center, Right.
*/
public void setTextAlign(TextAlignment align) {
if (curState.textalign != align) {
byte a;
switch (align) {
case LEFT: a = PGCanvas.ALIGN_LEFT; break;
case CENTER: a = PGCanvas.ALIGN_CENTER; break;
case RIGHT: a = PGCanvas.ALIGN_RIGHT; break;
case JUSTIFY: a = PGCanvas.ALIGN_JUSTIFY; break;
default: return;
}
curState.textalign = align;
writeParam(a, PGCanvas.TEXT_ALIGN);
}
}
/**
* Gets the current {@code TextAlignment}.
*
* @return {@code TextAlignment} with values of Left, Center, Right, or
* Justify.
*/
public TextAlignment getTextAlign() {
return curState.textalign;
}
/**
* Sets the current Text Baseline.
*
* @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom
*/
public void setTextBaseline(VPos baseline) {
if (curState.textbaseline != baseline) {
byte b;
switch (baseline) {
case TOP: b = PGCanvas.BASE_TOP; break;
case CENTER: b = PGCanvas.BASE_MIDDLE; break;
case BASELINE: b = PGCanvas.BASE_ALPHABETIC; break;
case BOTTOM: b = PGCanvas.BASE_BOTTOM; break;
default: return;
}
curState.textbaseline = baseline;
writeParam(b, PGCanvas.TEXT_BASELINE);
}
}
/**
* Gets the current Text Baseline.
*
* @return {@code VPos} with values of Top, Center, Baseline, or Bottom
*/
public VPos getTextBaseline() {
return curState.textbaseline;
}
/**
* Fills the given string of text at position x, y (0,0 at top left)
* with the current fill paint attribute.
*
* @param text the string of text.
* @param x position on the x axis.
* @param y position on the y axis.
*/
public void fillText(String text, double x, double y) {
writeText(text, x, y, 0, PGCanvas.FILL_TEXT);
}
/**
* draws the given string of text at position x, y (0,0 at top left)
* with the current stroke paint attribute.
*
* @param text the string of text.
* @param x position on the x axis.
* @param y position on the y axis.
*/
public void strokeText(String text, double x, double y) {
writeText(text, x, y, 0, PGCanvas.STROKE_TEXT);
}
/**
* Fills text and includes a maximum width of the string.
*
* If the width of the text extends past max width, then it will be sized
* to fit.
*
* @param text the string of text.
* @param x position on the x axis.
* @param y position on the y axis.
* @param maxWidth maximum width the text string can have.
*/
public void fillText(String text, double x, double y, double maxWidth) {
if (maxWidth <= 0) return;
writeText(text, x, y, maxWidth, PGCanvas.FILL_TEXT);
}
/**
* Draws text with stroke paint and includes a maximum width of the string.
*
* If the width of the text extends past max width, then it will be sized
* to fit.
*
* @param text the string of text.
* @param x position on the x axis.
* @param y position on the y axis.
* @param maxWidth maximum width the text string can have.
*/
public void strokeText(String text, double x, double y, double maxWidth) {
if (maxWidth <= 0) return;
writeText(text, x, y, maxWidth, PGCanvas.STROKE_TEXT);
}
/**
* Set the filling rule constant for determining the interior of the path.
* The value must be one of the following constants:
* {@code FillRile.EVEN_ODD} or {@code FillRule.NON_ZERO}.
* The default value is {@code FillRule.NON_ZERO}.
*
* @defaultValue FillRule.NON_ZERO
*/
public void setFillRule(FillRule fillRule) {
if (curState.fillRule != fillRule) {
byte b;
if (fillRule == FillRule.EVEN_ODD) {
b = PGCanvas.FILL_RULE_EVEN_ODD;
} else {
b = PGCanvas.FILL_RULE_NON_ZERO;
}
curState.fillRule = fillRule;
writeParam(b, PGCanvas.FILL_RULE);
}
}
/**
* Get the filling rule constant for determining the interior of the path.
* The default value is {@code FillRule.NON_ZERO}.
*
* @return current fill rule.
*/
public FillRule getFillRule() {
return curState.fillRule;
}
/**
* Starts a Path
*/
public void beginPath() {
path.reset();
markPathDirty();
}
/**
* Issues a move command for the current path to the given x,y coordinate.
*
* @param x0 the X position for the move to command.
* @param y0 the Y position for the move to command.
*/
public void moveTo(double x0, double y0) {
coords[0] = (float) x0;
coords[1] = (float) y0;
curState.transform.transform(coords, 0, coords, 0, 1);
path.moveTo(coords[0], coords[1]);
markPathDirty();
}
/**
* Adds segments to the current path to make a line at the given x,y
* coordinate.
*
* @param x1 the X coordinate of the ending point of the line.
* @param y1 the Y coordinate of the ending point of the line.
*/
public void lineTo(double x1, double y1) {
coords[0] = (float) x1;
coords[1] = (float) y1;
curState.transform.transform(coords, 0, coords, 0, 1);
path.lineTo(coords[0], coords[1]);
markPathDirty();
}
/**
* Adds segments to the current path to make a quadratic curve.
*
* @param xc the X coordinate of the control point
* @param yc the Y coordinate of the control point
* @param x1 the X coordinate of the end point
* @param y1 the Y coordinate of the end point
*/
public void quadraticCurveTo(double xc, double yc, double x1, double y1) {
coords[0] = (float) xc;
coords[1] = (float) yc;
coords[2] = (float) x1;
coords[3] = (float) y1;
curState.transform.transform(coords, 0, coords, 0, 2);
path.quadTo(coords[0], coords[1], coords[2], coords[3]);
markPathDirty();
}
/**
* Adds segments to the current path to make a cubic bezier curve.
*
* @param xc1 the X coordinate of first bezier control point.
* @param yc1 the Y coordinate of the first bezier control point.
* @param xc2 the X coordinate of the second bezier control point.
* @param yc2 the Y coordinate of the second bezier control point.
* @param x1 the X coordinate of the end point.
* @param y1 the Y coordinate of the end point.
*/
public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) {
coords[0] = (float) xc1;
coords[1] = (float) yc1;
coords[2] = (float) xc2;
coords[3] = (float) yc2;
coords[4] = (float) x1;
coords[5] = (float) y1;
curState.transform.transform(coords, 0, coords, 0, 3);
path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
markPathDirty();
}
/**
* Adds segments to the current path to make an arc.
*
* @param x1 the X coordinate of the first point of the arc.
* @param y1 the Y coordinate of the first point of the arc.
* @param x2 the X coordinate of the second point of the arc.
* @param y2 the Y coordinate of the second point of the arc.
* @param radius the radius of the arc in the range {0.0-positive infinity}.
*/
public void arcTo(double x1, double y1, double x2, double y2, double radius) {
if (path.getNumCommands() == 0) {
moveTo(x1, y1);
lineTo(x1, y1);
} else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2,
(float) radius))
{
lineTo(x1, y1);
}
}
private static double lenSq(double x0, double y0, double x1, double y1) {
x1 -= x0;
y1 -= y0;
return x1 * x1 + y1 * y1;
}
private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) {
float x0, y0;
if (curState.transform.isTranslateOrIdentity()) {
x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
} else {
coords[0] = path.getCurrentX();
coords[1] = path.getCurrentY();
try {
curState.transform.inverseTransform(coords, 0, coords, 0, 1);
} catch (NoninvertibleTransformException e) {
return false;
}
x0 = coords[0];
y0 = coords[1];
}
// call x1,y1 the corner point
// If 2*theta is the angle described by p0->p1->p2
// then theta is the angle described by p0->p1->centerpt and
// centerpt->p1->p2
// We know that the distance from the arc center to the tangent points
// is r, and if A is the distance from the corner to the tangent point
// then we know:
// tan(theta) = r/A
// A = r / sin(theta)
// B = A * cos(theta) = r * (sin/cos) = r * tan
// We use the cosine rule on the triangle to get the 2*theta angle:
// cosB = (a^2 + c^2 - b^2) / (2ac)
// where a and c are the adjacent sides and b is the opposite side
// i.e. a = p0->p1, c=p1->p2, b=p0->p2
// Then we can use the tan^2 identity to compute B:
// tan^2 = (1 - cos(2theta)) / (1 + cos(2theta))
double lsq01 = lenSq(x0, y0, x1, y1);
double lsq12 = lenSq(x1, y1, x2, y2);
double lsq02 = lenSq(x0, y0, x2, y2);
double len01 = Math.sqrt(lsq01);
double len12 = Math.sqrt(lsq12);
double cosnum = lsq01 + lsq12 - lsq02;
double cosden = 2.0 * len01 * len12;
if (cosden == 0.0 || radius <= 0f) {
return false;
}
double cos_2theta = cosnum / cosden;
double tansq_den = (1.0 + cos_2theta);
if (tansq_den == 0.0) {
return false;
}
double tansq_theta = (1.0 - cos_2theta) / tansq_den;
double A = radius / Math.sqrt(tansq_theta);
double tx0 = x1 + (A / len01) * (x0 - x1);
double ty0 = y1 + (A / len01) * (y0 - y1);
double tx1 = x1 + (A / len12) * (x2 - x1);
double ty1 = y1 + (A / len12) * (y2 - y1);
// The midpoint between the two tangent points
double mx = (tx0 + tx1) / 2.0;
double my = (ty0 + ty1) / 2.0;
// similar triangles tell us that:
// len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m)
// len(m,center) = lensq(m,tangent)/len(corner,m)
// center = m + (m - p1) * len(m,center) / len(corner,m)
// = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m))
double lenratioden = lenSq(mx, my, x1, y1);
if (lenratioden == 0.0) {
return false;
}
double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden;
double cx = mx + (mx - x1) * lenratio;
double cy = my + (my - y1) * lenratio;
if (!(cx == cx && cy == cy)) {
return false;
}
// Looks like we are good to draw, first we have to get to the
// initial tangent point with a line segment.
if (tx0 != x0 || ty0 != y0) {
lineTo(tx0, ty0);
}
// We need sin(arc/2), cos(arc/2)
// and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers
// We have tan(theta) = tan(tri/2)
// arc = 180-tri
// arc/2 = (180-tri)/2 = 90-(tri/2)
// sin(arc/2) = sin(90-(tri/2)) = cos(tri/2)
// cos(arc/2) = cos(90-(tri/2)) = sin(tri/2)
// 2theta = tri, therefore theta = tri/2
// cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0
// sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0
// sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0)
// cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0)
// We compute cos(arc/2) here as we need it in either case below
double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0);
boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx);
// If the arc covers more than 90 degrees then we must use 2
// cubic beziers to get a decent approximation.
// arc = 180-tri
// arc = 180-2*theta
// arc > 90 implies 2*theta < 90
// 2*theta < 90 implies cos_2theta > 0
// So, we need 2 cubics if cos_2theta > 0
if (cos_2theta <= 0.0) {
// 1 cubic bezier
double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0);
double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc);
if (ccw) cv = -cv;
double cpx0 = tx0 - cv * (ty0 - cy);
double cpy0 = ty0 + cv * (tx0 - cx);
double cpx1 = tx1 + cv * (ty1 - cy);
double cpy1 = ty1 - cv * (tx1 - cx);
bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
} else {
// 2 cubic beziers
// We need sin(arc/4) and cos(arc/4)
// We computed cos(arc/2), so we can compute them as follows:
// sin(arc/4) = sqrt((1 - cos(arc/2)) / 2)
// cos(arc/4) = sart((1 + cos(arc/2)) / 2)
double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0);
double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0);
double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc);
if (ccw) cv = -cv;
double midratio = radius / Math.sqrt(lenratioden);
double midarcx = cx + (x1 - mx) * midratio;
double midarcy = cy + (y1 - my) * midratio;
double cpx0 = tx0 - cv * (ty0 - cy);
double cpy0 = ty0 + cv * (tx0 - cx);
double cpx1 = midarcx + cv * (midarcy - cy);
double cpy1 = midarcy - cv * (midarcx - cx);
bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy);
cpx0 = midarcx - cv * (midarcy - cy);
cpy0 = midarcy + cv * (midarcx - cx);
cpx1 = tx1 + cv * (ty1 - cy);
cpy1 = ty1 - cv * (tx1 - cx);
bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
}
return true;
}
private static final Arc2D TEMP_ARC = new Arc2D();
/**
* Adds path elements to the current path to make an arc that uses Euclidean
* degrees. This Euclidean orientation sweeps from East to North, then West,
* then South, then back to East.
*
* @param centerX the center x position of the arc.
* @param centerY the center y position of the arc.
* @param radiusX the x radius of the arc.
* @param radiusY the y radius of the arc.
* @param startAngle the starting angle of the arc in the range {@code 0-360.0}
* @param length the length of the baseline of the arc.
*/
public void arc(double centerX, double centerY,
double radiusX, double radiusY,
double startAngle, double length)
{
TEMP_ARC.setArc((float)(centerX - radiusX), // x
(float)(centerY - radiusY), // y
(float)(radiusX * 2.0), // w
(float)(radiusY * 2.0), // h
(float)startAngle,
(float)length,
Arc2D.OPEN);
path.append(TEMP_ARC.getPathIterator(curState.transform), true);
markPathDirty();
}
/**
* Adds path elements to the current path to make a rectangle.
*
* @param x x position of the upper left corner of the rectangle.
* @param y y position of the upper left corner of the rectangle.
* @param w width of the rectangle.
* @param h height of the rectangle.
*/
public void rect(double x, double y, double w, double h) {
coords[0] = (float) x;
coords[1] = (float) y;
coords[2] = (float) w;
coords[3] = (float) 0;
coords[4] = (float) 0;
coords[5] = (float) h;
curState.transform.deltaTransform(coords, 0, coords, 0, 3);
float x0 = coords[0] + (float) curState.transform.getMxt();
float y0 = coords[1] + (float) curState.transform.getMyt();
float dx1 = coords[2];
float dy1 = coords[3];
float dx2 = coords[4];
float dy2 = coords[5];
path.moveTo(x0, y0);
path.lineTo(x0+dx1, y0+dy1);
path.lineTo(x0+dx1+dx2, y0+dy1+dy2);
path.lineTo(x0+dx2, y0+dy2);
path.closePath();
markPathDirty();
// path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto
}
/**
* Appends an SVG Path string to the current path. If there is no current
* path the string must then start with either type of move command.
*
* @param svgpath the SVG Path string.
*/
public void appendSVGPath(String svgpath) {
boolean prependMoveto = true;
boolean skipMoveto = true;
for (int i = 0; i < svgpath.length(); i++) {
switch (svgpath.charAt(i)) {
case ' ':
case '\t':
case '\r':
case '\n':
continue;
case 'M':
prependMoveto = skipMoveto = false;
break;
case 'm':
if (path.getNumCommands() == 0) {
// An initial relative moveTo becomes absolute
prependMoveto = false;
}
// Even if we prepend an initial moveTo in the temp
// path, we do not want to delete the resulting initial
// moveTo because the relative moveto will be folded
// into it by an optimization in the Path2D object.
skipMoveto = false;
break;
}
break;
}
Path2D p2d = new Path2D();
if (prependMoveto && path.getNumCommands() > 0) {
float x0, y0;
if (curState.transform.isTranslateOrIdentity()) {
x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
} else {
coords[0] = path.getCurrentX();
coords[1] = path.getCurrentY();
try {
curState.transform.inverseTransform(coords, 0, coords, 0, 1);
} catch (NoninvertibleTransformException e) {
}
x0 = coords[0];
y0 = coords[1];
}
p2d.moveTo(x0, y0);
} else {
skipMoveto = false;
}
p2d.appendSVGPath(svgpath);
PathIterator pi = p2d.getPathIterator(curState.transform);
if (skipMoveto) {
// We need to delete the initial moveto and let the path
// extend from the actual existing geometry.
pi.next();
}
path.append(pi, false);
}
/**
* Closes the path.
*/
public void closePath() {
path.closePath();
markPathDirty();
}
/**
* Fills the path with the current fill paint.
*/
public void fill() {
writePath(PGCanvas.FILL_PATH);
}
/**
* Strokes the path with the current stroke paint.
*/
public void stroke() {
writePath(PGCanvas.STROKE_PATH);
}
/**
* Clips using the current path
*/
public void clip() {
Path2D clip = new Path2D(path);
clipStack.addLast(clip);
curState.numClipPaths++;
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.PUSH_CLIP);
buf.putObject(clip);
}
/**
* Returns true if the the given x,y point is inside the path.
*
* @param x the X coordinate to use for the check.
* @param y the Y coordinate to use for the check.
* @return true if the point given is inside the path, false
* otherwise.
*/
public boolean isPointInPath(double x, double y) {
// TODO: HTML5 considers points on the path to be inside, but we
// implement a halfin-halfout approach...
return path.contains((float) x, (float) y);
}
/**
* Clears a portion of the canvas with a transparent color value.
*
* @param x X position of the upper left corner of the rectangle.
* @param y Y position of the upper left corner of the rectangle.
* @param w width of the rectangle.
* @param h height of the rectangle.
*/
public void clearRect(double x, double y, double w, double h) {
if (w != 0 && h != 0) {
writeOp4(x, y, w, h, PGCanvas.CLEAR_RECT);
}
}
/**
* Fills a rectangle using the current fill paint.
*
* @param x the X position of the upper left corner of the rectangle.
* @param y the Y position of the upper left corner of the rectangle.
* @param w the width of the rectangle.
* @param h the height of the rectangle.
*/
public void fillRect(double x, double y, double w, double h) {
if (w != 0 && h != 0) {
writeOp4(x, y, w, h, PGCanvas.FILL_RECT);
}
}
/**
* Strokes a rectangle using the current stroke paint.
*
* @param x the X position of the upper left corner of the rectangle.
* @param y the Y position of the upper left corner of the rectangle.
* @param w the width of the rectangle.
* @param h the height of the rectangle.
*/
public void strokeRect(double x, double y, double w, double h) {
if (w != 0 || h != 0) {
writeOp4(x, y, w, h, PGCanvas.STROKE_RECT);
}
}
/**
* Fills an oval using the current fill paint.
*
* @param x the X coordinate of the upper left bound of the oval.
* @param y the Y coordinate of the upper left bound of the oval.
* @param w the width at the center of the oval.
* @param h the height at the center of the oval.
*/
public void fillOval(double x, double y, double w, double h) {
if (w != 0 && h != 0) {
writeOp4(x, y, w, h, PGCanvas.FILL_OVAL);
}
}
/**
* Strokes a rectangle using the current stroke paint.
*
* @param x the X coordinate of the upper left bound of the oval.
* @param y the Y coordinate of the upper left bound of the oval.
* @param w the width at the center of the oval.
* @param h the height at the center of the oval.
*/
public void strokeOval(double x, double y, double w, double h) {
if (w != 0 || h != 0) {
writeOp4(x, y, w, h, PGCanvas.STROKE_OVAL);
}
}
/**
* Fills an arc using the current fill paint.
*
* @param x the X coordinate of the arc.
* @param y the Y coordinate of the arc.
* @param w the width of the arc.
* @param h the height of the arc.
* @param startAngle the starting angle of the arc in degrees.
* @param arcExtent the angular extent of the arc in degrees.
* @param closure closure type (Round, Chord, Open).
*/
public void fillArc(double x, double y, double w, double h,
double startAngle, double arcExtent, ArcType closure)
{
if (w != 0 && h != 0) {
writeArcType(closure);
writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.FILL_ARC);
}
}
/**
* Strokes an Arc using the current stroke paint.
*
* @param x the X coordinate of the arc.
* @param y the Y coordinate of the arc.
* @param w the width of the arc.
* @param h the height of the arc.
* @param startAngle the starting angle of the arc in degrees.
* @param arcExtent arcExtent the angular extent of the arc in degrees.
* @param closure closure type (Round, Chord, Open).
*/
public void strokeArc(double x, double y, double w, double h,
double startAngle, double arcExtent, ArcType closure)
{
if (w != 0 && h != 0) {
writeArcType(closure);
writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.STROKE_ARC);
}
}
/**
* Fills a rounded rectangle using the current fill paint.
*
* @param x the X coordinate of the upper left bound of the oval.
* @param y the Y coordinate of the upper left bound of the oval.
* @param w the width at the center of the oval.
* @param h the height at the center of the oval.
* @param arcWidth the arc width of the rectangle corners.
* @param arcHeight the arc height of the rectangle corners.
*/
public void fillRoundRect(double x, double y, double w, double h,
double arcWidth, double arcHeight)
{
if (w != 0 && h != 0) {
writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.FILL_ROUND_RECT);
}
}
/**
* Strokes a rounded rectangle using the current stroke paint.
*
* @param x the X coordinate of the upper left bound of the oval.
* @param y the Y coordinate of the upper left bound of the oval.
* @param w the width at the center of the oval.
* @param h the height at the center of the oval.
* @param arcWidth the arc width of the rectangle corners.
* @param arcHeight the arc height of the rectangle corners.
*/
public void strokeRoundRect(double x, double y, double w, double h,
double arcWidth, double arcHeight)
{
if (w != 0 && h != 0) {
writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.STROKE_ROUND_RECT);
}
}
/**
* Strokes a line using the current stroke paint.
*
* @param x1 the X coordinate of the starting point of the line.
* @param y1 the Y coordinate of the starting point of the line.
* @param x2 the X coordinate of the ending point of the line.
* @param y2 the Y coordinate of the ending point of the line.
*/
public void strokeLine(double x1, double y1, double x2, double y2) {
writeOp4(x1, y1, x2, y2, PGCanvas.STROKE_LINE);
}
/**
* Fills a polygon with the given points using the currently set fill paint.
*
* @param xPoints array containing the x coordinates of the polygon's points.
* @param yPoints array containing the y coordinates of the polygon's points.
* @param nPoints the number of points that make the polygon.
*/
public void fillPolygon(double xPoints[], double yPoints[], int nPoints) {
if (nPoints >= 3) {
writePoly(xPoints, yPoints, nPoints, true, PGCanvas.FILL_PATH);
}
}
/**
* Strokes a polygon with the given points using the currently set stroke paint.
*
* @param xPoints array containing the x coordinates of the polygon's points.
* @param yPoints array containing the y coordinates of the polygon's points.
* @param nPoints the number of points that make the polygon.
*/
public void strokePolygon(double xPoints[], double yPoints[], int nPoints) {
if (nPoints >= 2) {
writePoly(xPoints, yPoints, nPoints, true, PGCanvas.STROKE_PATH);
}
}
/**
* Draws a polyline with the given points using the currently set stroke
* paint attribute.
*
* @param xPoints array containing the x coordinates of the polyline's points.
* @param yPoints array containing the y coordinates of the polyline's points.
* @param nPoints the number of points that make the polyline.
*/
public void strokePolyline(double xPoints[], double yPoints[], int nPoints) {
if (nPoints >= 2) {
writePoly(xPoints, yPoints, nPoints, false, PGCanvas.STROKE_PATH);
}
}
/**
* Draws an image at the given x, y position using the width
* and height of the given image.
*
* @param img the image to be drawn.
* @param x the X coordinate on the destination for the upper left of the image.
* @param y the Y coordinate on the destination for the upper left of the image.
*/
public void drawImage(Image img, double x, double y) {
double sw = img.getWidth();
double sh = img.getHeight();
writeImage(img, x, y, sw, sh);
}
/**
* Draws an image into the given destination rectangle of the canvas. The
* Image is scaled to fit into the destination rectagnle.
*
* @param img the image to be drawn.
* @param x the X coordinate on the destination for the upper left of the image.
* @param y the Y coordinate on the destination for the upper left of the image.
* @param w the width of the destination rectangle.
* @param h the height of the destination rectangle.
*/
public void drawImage(Image img, double x, double y, double w, double h) {
writeImage(img, x, y, w, h);
}
/**
* Draws the current source rectangle of the given image to the given
* destination rectangle of the Canvas.
*
* @param img the image to be drawn.
* @param sx the source rectangle's X coordinate position.
* @param sy the source rectangle's Y coordinate position.
* @param sw the source rectangle's width.
* @param sh the source rectangle's height.
* @param dx the destination rectangle's X coordinate position.
* @param dy the destination rectangle's Y coordinate position.
* @param dw the destination rectangle's width.
* @param dh the destination rectangle's height.
*/
public void drawImage(Image img,
double sx, double sy, double sw, double sh,
double dx, double dy, double dw, double dh)
{
writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh);
}
private PixelWriter writer;
/**
* Returns a {@link PixelWriter} object that can be used to modify
* the pixels of the {@link Canvas} associated with this
* {@code GraphicsContext}.
* All coordinates in the {@code PixelWriter} methods on the returned
* object will be in device space since they refer directly to pixels.
*
* @return the {@code PixelWriter} for modifying the pixels of this
* {@code Canvas}
*/
public PixelWriter getPixelWriter() {
if (writer == null) {
writer = new PixelWriter() {
@Override
public PixelFormat getPixelFormat() {
return PixelFormat.getByteBgraPreInstance();
}
private BytePixelSetter getSetter() {
return ByteBgraPre.setter;
}
@Override
public void setArgb(int x, int y, int argb) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.PUT_ARGB);
buf.putInt(x);
buf.putInt(y);
buf.putInt(argb);
}
@Override
public void setColor(int x, int y, Color c) {
int a = (int) Math.round(c.getOpacity() * 255.0);
int r = (int) Math.round(c.getRed() * 255.0);
int g = (int) Math.round(c.getGreen() * 255.0);
int b = (int) Math.round(c.getBlue() * 255.0);
setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b);
}
private void writePixelBuffer(int x, int y, int w, int h,
byte[] pixels)
{
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.PUT_ARGBPRE_BUF);
buf.putInt(x);
buf.putInt(y);
buf.putInt(w);
buf.putInt(h);
buf.putObject(pixels);
}
private int[] checkBounds(int x, int y, int w, int h,
PixelFormat pf, int scan)
{
// assert (w >= 0 && h >= 0) - checked by caller
int cw = (int) theCanvas.getWidth();
int ch = (int) theCanvas.getHeight();
if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) {
return null;
}
int offset = 0;
if (x < 0) {
w += x;
if (w < 0) return null;
if (pf != null) {
switch (pf.getType()) {
case BYTE_BGRA:
case BYTE_BGRA_PRE:
offset -= x * 4;
break;
case BYTE_RGB:
offset -= x * 3;
break;
case BYTE_INDEXED:
case INT_ARGB:
case INT_ARGB_PRE:
offset -= x;
break;
default:
throw new InternalError("unknown Pixel Format");
}
}
x = 0;
}
if (y < 0) {
h += y;
if (h < 0) return null;
offset -= y * scan;
y = 0;
}
if (x + w > cw) {
w = cw - x;
if (w < 0) return null;
}
if (y + h > ch) {
h = ch - y;
if (h < 0) return null;
}
return new int[] {
x, y, w, h, offset
};
}
@Override
public void
setPixels(int x, int y, int w, int h,
PixelFormat pixelformat,
T buffer, int scan)
{
if (w <= 0 || h <= 0) return;
int offset = buffer.position();
int adjustments[] = checkBounds(x, y, w, h,
pixelformat, scan);
if (adjustments != null) {
x = adjustments[0];
y = adjustments[1];
w = adjustments[2];
h = adjustments[3];
offset += adjustments[4];
}
byte pixels[] = new byte[w * h * 4];
ByteBuffer dst = ByteBuffer.wrap(pixels);
PixelGetter getter = PixelUtils.getGetter(pixelformat);
PixelConverter converter =
PixelUtils.getConverter(getter, getSetter());
converter.convert(buffer, offset, scan,
dst, 0, w * 4,
w, h);
writePixelBuffer(x, y, w, h, pixels);
}
@Override
public void setPixels(int x, int y, int w, int h,
PixelFormat pixelformat,
byte[] buffer, int offset, int scanlineStride)
{
if (w <= 0 || h <= 0) return;
int adjustments[] = checkBounds(x, y, w, h,
pixelformat, scanlineStride);
if (adjustments != null) {
x = adjustments[0];
y = adjustments[1];
w = adjustments[2];
h = adjustments[3];
offset += adjustments[4];
}
byte pixels[] = new byte[w * h * 4];
BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat);
ByteToBytePixelConverter converter =
PixelUtils.getB2BConverter(getter, getSetter());
converter.convert(buffer, offset, scanlineStride,
pixels, 0, w * 4,
w, h);
writePixelBuffer(x, y, w, h, pixels);
}
@Override
public void setPixels(int x, int y, int w, int h,
PixelFormat pixelformat,
int[] buffer, int offset, int scanlineStride)
{
if (w <= 0 || h <= 0) return;
int adjustments[] = checkBounds(x, y, w, h,
pixelformat, scanlineStride);
if (adjustments != null) {
x = adjustments[0];
y = adjustments[1];
w = adjustments[2];
h = adjustments[3];
offset += adjustments[4];
}
byte pixels[] = new byte[w * h * 4];
IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat);
IntToBytePixelConverter converter =
PixelUtils.getI2BConverter(getter, getSetter());
converter.convert(buffer, offset, scanlineStride,
pixels, 0, w * 4,
w, h);
writePixelBuffer(x, y, w, h, pixels);
}
@Override
public void setPixels(int dstx, int dsty, int w, int h,
PixelReader reader, int srcx, int srcy)
{
if (w <= 0 || h <= 0) return;
int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0);
if (adjustments != null) {
int newx = adjustments[0];
int newy = adjustments[1];
srcx += newx - dstx;
srcy += newy - dsty;
dstx = newx;
dsty = newy;
w = adjustments[2];
h = adjustments[3];
}
byte pixels[] = new byte[w * h * 4];
reader.getPixels(srcx, srcy, w, h,
PixelFormat.getByteBgraPreInstance(),
pixels, 0, w * 4);
writePixelBuffer(dstx, dsty, w, h, pixels);
}
};
}
return writer;
}
/**
* Sets the effect to be applied after the next draw call, or null to
* disable effects.
* @param e the effect to use, or null to disable effects
*/
public void setEffect(Effect e) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.EFFECT);
if (e == null) {
curState.effect = null;
buf.putObject(null);
} else {
curState.effect = e.impl_copy();
curState.effect.impl_sync();
buf.putObject(curState.effect.impl_getImpl());
}
}
/**
* Gets a copy of the effect to be applied after the next draw call.
* A null return value means that no effect will be applied after future
* rendering calls.
* @param e an {@code Effect} object that may be used to store the
* copy of the current effect, if it is of a compatible type
* @return the current effect used for all rendering calls,
* or null if there is no current effect
*/
public Effect getEffect(Effect e) {
return curState.effect == null ? null : curState.effect.impl_copy();
}
/**
* Applies the given effect to the entire canvas.
* @param e the effect to apply onto the entire destination.
*/
public void applyEffect(Effect e) {
GrowableDataBuffer buf = getBuffer();
buf.putByte(PGCanvas.FX_APPLY_EFFECT);
Effect effect = e.impl_copy();
effect.impl_sync();
buf.putObject(effect.impl_getImpl());
}
}