/* * 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.IllegalPathStateException; 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.*; import com.sun.javafx.image.impl.ByteBgraPre; import com.sun.javafx.sg.prism.GrowableDataBuffer; import com.sun.javafx.sg.prism.NGCanvas; import com.sun.javafx.tk.Toolkit; import javafx.geometry.NodeOrientation; 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; import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.util.LinkedList; /** * 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
* 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: *
* * Note: Canvas does not support line wrapping, therefore the text * alignment Justify is identical to left aligned text. *
* A {@code null} value will be ignored and the current value will remain unchanged.
*
* @param align {@code TextAlignment} with values of Left, Center, Right or null.
*/
public void setTextAlign(TextAlignment align) {
if (align != null && curState.textalign != align) {
byte a;
switch (align) {
case LEFT: a = NGCanvas.ALIGN_LEFT; break;
case CENTER: a = NGCanvas.ALIGN_CENTER; break;
case RIGHT: a = NGCanvas.ALIGN_RIGHT; break;
case JUSTIFY: a = NGCanvas.ALIGN_JUSTIFY; break;
default: return;
}
curState.textalign = align;
writeParam(a, NGCanvas.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.
* A {@code null} value will be ignored and the current value will remain unchanged.
*
* @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom or null.
*/
public void setTextBaseline(VPos baseline) {
if (baseline != null && curState.textbaseline != baseline) {
byte b;
switch (baseline) {
case TOP: b = NGCanvas.BASE_TOP; break;
case CENTER: b = NGCanvas.BASE_MIDDLE; break;
case BASELINE: b = NGCanvas.BASE_ALPHABETIC; break;
case BOTTOM: b = NGCanvas.BASE_BOTTOM; break;
default: return;
}
curState.textbaseline = baseline;
writeParam(b, NGCanvas.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.
* A {@code null} text value will be ignored.
*
* @param text the string of text or null.
* @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, NGCanvas.FILL_TEXT);
}
/**
* draws the given string of text at position x, y (0,0 at top left)
* with the current stroke paint attribute.
* A {@code null} text value will be ignored.
*
* @param text the string of text or null.
* @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, NGCanvas.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.
* A {@code null} text value will be ignored.
*
* @param text the string of text or null.
* @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, NGCanvas.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.
* A {@code null} text value will be ignored.
*
* @param text the string of text or null.
* @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, NGCanvas.STROKE_TEXT);
}
/**
* Set the filling rule constant for determining the interior of the path.
* A {@code null} value will be ignored and the current value will remain unchanged.
*
* @param fillRule {@code FillRule} with a value of Even_odd or Non_zero or null.
*/
public void setFillRule(FillRule fillRule) {
if (fillRule != null && curState.fillRule != fillRule) {
byte b;
if (fillRule == FillRule.EVEN_ODD) {
b = NGCanvas.FILL_RULE_EVEN_ODD;
} else {
b = NGCanvas.FILL_RULE_NON_ZERO;
}
curState.fillRule = fillRule;
writeParam(b, NGCanvas.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);
if (path.getNumCommands() == 0) {
path.moveTo(coords[0], coords[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);
if (path.getNumCommands() == 0) {
path.moveTo(coords[0], coords[1]);
}
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);
if (path.getNumCommands() == 0) {
path.moveTo(coords[0], coords[1]);
}
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;
}
/**
* 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)
{
Arc2D arc = new Arc2D((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(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.
* A {@code null} value or incorrect SVG path will be ignored.
* @param svgpath the SVG Path string.
*
*/
public void appendSVGPath(String svgpath) {
if (svgpath == null) return;
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;
}
try {
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);
} catch (IllegalArgumentException | IllegalPathStateException ex) {
//Ignore incorrect path
}
}
/**
* Closes the path.
*/
public void closePath() {
if (path.getNumCommands() > 0) {
path.closePath();
markPathDirty();
}
}
/**
* Fills the path with the current fill paint.
*/
public void fill() {
writePath(NGCanvas.FILL_PATH);
}
/**
* Strokes the path with the current stroke paint.
*/
public void stroke() {
writePath(NGCanvas.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(NGCanvas.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) {
resetIfCovers(null, x, y, w, h);
writeOp4(x, y, w, h, NGCanvas.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) {
resetIfCovers(this.curState.fill, x, y, w, h);
writeOp4(x, y, w, h, NGCanvas.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, NGCanvas.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, NGCanvas.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, NGCanvas.STROKE_OVAL);
}
}
/**
* Fills an arc using the current fill paint. A {@code null} ArcType or
* non positive width or height will cause the render command to be ignored.
*
* @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) or null.
*/
public void fillArc(double x, double y, double w, double h,
double startAngle, double arcExtent, ArcType closure)
{
if (w != 0 && h != 0 && closure != null) {
writeArcType(closure);
writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.FILL_ARC);
}
}
/**
* Strokes an Arc using the current stroke paint. A {@code null} ArcType or
* non positive width or height will cause the render command to be ignored.
*
* @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) or null
*/
public void strokeArc(double x, double y, double w, double h,
double startAngle, double arcExtent, ArcType closure)
{
if (w != 0 && h != 0 && closure != null) {
writeArcType(closure);
writeOp6(x, y, w, h, startAngle, arcExtent, NGCanvas.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, NGCanvas.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, NGCanvas.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, NGCanvas.STROKE_LINE);
}
/**
* Fills a polygon with the given points using the currently set fill paint.
* A {@code null} value for one of the arrays will be ignored and nothing will be drawn.
*
* @param xPoints array containing the x coordinates of the polygon's points or null.
* @param yPoints array containing the y coordinates of the polygon's points or null.
* @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, NGCanvas.FILL_PATH);
}
}
/**
* Strokes a polygon with the given points using the currently set stroke paint.
* A {@code null} value for one of the arrays will be ignored and nothing will be drawn.
*
* @param xPoints array containing the x coordinates of the polygon's points or null.
* @param yPoints array containing the y coordinates of the polygon's points or null.
* @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, NGCanvas.STROKE_PATH);
}
}
/**
* Draws a polyline with the given points using the currently set stroke
* paint attribute.
* A {@code null} value for one of the arrays will be ignored and nothing will be drawn.
*
* @param xPoints array containing the x coordinates of the polyline's points or null.
* @param yPoints array containing the y coordinates of the polyline's points or null.
* @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, NGCanvas.STROKE_PATH);
}
}
/**
* Draws an image at the given x, y position using the width
* and height of the given image.
* A {@code null} image value or an image still in progress will be ignored.
*
* @param img the image to be drawn or null.
* @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) {
if (img == null) return;
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.
* A {@code null} image value or an image still in progress will be ignored.
*
* @param img the image to be drawn or null.
* @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.
* A {@code null} image value or an image still in progress will be ignored.
*
* @param img the image to be drawn or null.
* @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(NGCanvas.PUT_ARGB);
buf.putInt(x);
buf.putInt(y);
buf.putInt(argb);
}
@Override
public void setColor(int x, int y, Color c) {
if (c == null) throw new NullPointerException("Color cannot be null");
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(NGCanvas.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) Math.ceil(theCanvas.getWidth());
int ch = (int) Math.ceil(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