/* * Canvas.java * Transform * * Copyright (c) 2009-2010 Flagstone Software Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Flagstone Software Ltd. nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.flagstone.transform.util.shape; import java.util.ArrayList; import java.util.List; import com.flagstone.transform.datatype.Bounds; import com.flagstone.transform.fillstyle.FillStyle; import com.flagstone.transform.linestyle.LineStyle; import com.flagstone.transform.linestyle.LineStyle1; import com.flagstone.transform.shape.Curve; import com.flagstone.transform.shape.DefineShape2; import com.flagstone.transform.shape.DefineShape3; import com.flagstone.transform.shape.Line; import com.flagstone.transform.shape.Shape; import com.flagstone.transform.shape.ShapeRecord; import com.flagstone.transform.shape.ShapeStyle; /** * <p> * The Canvas class is used to create shape definitions. Arbitrary paths can be * created using a series of move, line or curve segments. Drawing operations * using both absolute coordinates and coordinates relative to the current point * (updated after every operation) are supported. * </p> * * <p> * For curves both cubic and quadratic curves are supported. Flash only supports * quadratic curves so cubic curves are approximated by a series of line * segments using (converting cubic to quadratic curves is mathematically * difficult). The smoothness of cubic curves is controlled by the flatness * attribute which can be used to limit the number of line segments that are * drawn. * </p> * * <p> * As a path is drawn the maximum and minimum x and y coordinates are recorded * so that the bounding rectangle that completely encloses the shape can be * defined. This is used when creating shape definitions using the DefineShape, * DefineShape2 or DefineShape3 classes. * <p> * * <p> * When drawing paths whether coordinates are specified in twips or pixels is * set when the Canvas object is created. When specifying coordinates in pixels * all coordinates are converted internally to twips to perform the actual * drawing. * </p> */ @SuppressWarnings({"PMD.TooManyMethods", "PMD.TooManyFields" }) public final class Canvas { /** * Value used in the algorithm to convert quadratic Bezier curves in to * a set of straight lines that approximate the curve. */ private static final double FLATTEN_LIMIT = 0.25; /** Number of twips in a pixel. */ private static final int TWIPS_PER_PIXEL = 20; /** Index of the start point in the array of cubic Bezier points. */ private static final int START = 0; /** Index of the first control point in the array of cubic points. */ private static final int CTRLA = 1; /** Index of the second control point in the array of cubic points. */ private static final int CTRLB = 2; /** Index of the anchor point in the array of cubic points. */ private static final int ANCHOR = 3; /** Divisor for averaging the distance between points. */ private static final int MID = 2; /** Number of points used to define a cubic Bezier curve. */ private static final int CUBIC_POINTS = 4; /** Factor used to calculate the control point for a quadratic curve. */ private static final double CTRL_AVG = 2.0; /** Factor used to calculate the anchor point for a quadratic curve. */ private static final double ANCHOR_AVG = 3.0; /** Whether coordinate used to draw a path a specified in pixels. */ private transient boolean pixels; /** Indicates whether a path is currently being drawn. */ private transient boolean pathInProgress = false; /** X coordinates of a cubic Bezier curve. */ private final transient double[] cubicX = new double[CUBIC_POINTS]; /** Y coordinates of a cubic Bezier curve. */ private final transient double[] cubicY = new double[CUBIC_POINTS]; /** The x-coordinate of the initial point on the path. */ private transient int initialX; /** The y-coordinate of the initial point on the path. */ private transient int initialY; /** The x-coordinate of the current point on the path. */ private transient int currentX; /** The y-coordinate of the current point on the path. */ private transient int currentY; /** The x-coordinate for the last control point when drawing a curve. */ private transient int controlX; /** The y-coordinate for the last control point when drawing a curve. */ private transient int controlY; /** The minimum x-coordinate, accounting for line width. */ private transient int minX; /** The minimum y-coordinate, accounting for line width. */ private transient int minY; /** The maximum x-coordinate, accounting for line width. */ private transient int maxX; /** The maximum y-coordinate, accounting for line width. */ private transient int maxY; /** The current line width. */ private transient int lineWidth; /** The list of ShapeRecords make make up the current path. */ private final transient List<ShapeRecord> objects; /** The list of line styles available. */ private final transient List<LineStyle> lineStyles; /** The list of fill styles available. */ private final transient List<FillStyle> fillStyles; /** * Creates a new Canvas object with no path defined. */ public Canvas() { objects = new ArrayList<ShapeRecord>(); lineStyles = new ArrayList<LineStyle>(); fillStyles = new ArrayList<FillStyle>(); } /** * Are the coordinates used when drawing a path are expressed in * pixels (true) or twips (false). * @return true if coordinates are expressed in pixels, false if * they are twips. */ public boolean isPixels() { return pixels; } /** * Sets whether the coordinates used when drawing a path are expressed in * pixels (true) or twips (false). * * Flash coordinates are specified in twips (1 twip equals 1/1440th of an * inch or 1/20th of a point). Allowing coordinates to be specified in * pixels simplifies the drawing process avoiding the conversion to twips by * multiplying each value by 20. * * @param arePixels true if coordinates are expressed in pixels, false if * they are twips. */ public void setPixels(final boolean arePixels) { pixels = arePixels; } /** * Generates the bounding box that encloses the current path. * * @return the bounding box that encloses the current shape. */ public Bounds getBounds() { return new Bounds(minX, minY, maxX, maxY); } /** * Get a copy of the list of line styles. * * @return the list of line styles. */ public List<LineStyle> getLineStyles() { final List<LineStyle> list = new ArrayList<LineStyle>(lineStyles .size()); for (final LineStyle style : lineStyles) { list.add(style.copy()); } return list; } /** * Get a copy of the list of fill styles. * * @return the list of fill styles. */ public List<FillStyle> getFillStyles() { final List<FillStyle> list = new ArrayList<FillStyle>(fillStyles .size()); for (final FillStyle style : fillStyles) { list.add(style.copy()); } return list; } /** * Returns the Shape object containing the objects used to draw the current * path. * * @return an Shape object contain the Line, Curve and ShapeStyle objects * used to construct the current path. */ public Shape getShape() { final List<ShapeRecord> list = new ArrayList<ShapeRecord>(objects .size()); for (final ShapeRecord record : objects) { list.add(record.copy()); } return new Shape(list); } /** * Set the style used to draw lines. * * @param style * a line style. */ public void setLineStyle(final LineStyle1 style) { int index; if (lineStyles.contains(style)) { index = lineStyles.indexOf(style); } else { index = lineStyles.size(); lineStyles.add(style.copy()); } lineWidth = style.getWidth(); objects.add(new ShapeStyle().setLineStyle(index + 1)); } /** * Set the style used to fill enclosed areas. * * @param style * a fill style. */ public void setFillStyle(final FillStyle style) { int index; if (fillStyles.contains(style)) { index = fillStyles.indexOf(style); } else { index = fillStyles.size(); fillStyles.add(style.copy()); } objects.add(new ShapeStyle().setFillStyle(index + 1)); } /** * Set the style used to fill overlapping enclosed areas. * * @param style * a fill style. */ public void setAltStyle(final FillStyle style) { int index; if (fillStyles.contains(style)) { index = fillStyles.indexOf(style); } else { index = fillStyles.size(); fillStyles.add(style.copy()); } objects.add(new ShapeStyle().setAltFillStyle(index + 1)); } /** * Generates a shape containing the current path and styles. * * The shape is constructed with copies of the line and fill styles and the * shape representing the path drawn. This allows the number of styles to be * changed without affecting previously created shapes. * * @param identifier * an unique identifier for the shape. * @return this object. */ public DefineShape2 defineShape(final int identifier) { return new DefineShape2(identifier, getBounds(), getFillStyles(), getLineStyles(), getShape()); } /** * Generates a transparent shape containing the current path and styles. * * The shape is constructed with copies of the line and fille styles and the * shape representing the path drawn. This allows the number of styles to be * changed without affecting previously created shapes. * * @param identifier * an unique identifier for the shape. * @return this object. */ public DefineShape3 defineTransparentShape(final int identifier) { return new DefineShape3(identifier, getBounds(), getFillStyles(), getLineStyles(), getShape()); } /** * Creates a new path, discarding any path elements drawn. */ public void clear() { pathInProgress = false; setInitial(0, 0); setCurrent(0, 0); setControl(0, 0); setBounds(0, 0, 0, 0); lineStyles.clear(); fillStyles.clear(); objects.clear(); lineWidth = 0; } /** * Closes the current path by drawing a line from the current point to the * starting point of the path. */ public void close() { final int deltaX = initialX - currentX; final int deltaY = initialY - currentY; if ((deltaX != 0) || (deltaY != 0)) { objects.add(new Line(deltaX, deltaY)); } setCurrent(initialX, initialY); pathInProgress = false; } /** * Move to the point (x,y). * * @param xCoord * the x-coordinate of the point to move to. * @param yCoord * the y-coordinate of the point to move to. */ public void move(final int xCoord, final int yCoord) { final int pointX = pixels ? xCoord * TWIPS_PER_PIXEL : xCoord; final int pointY = pixels ? yCoord * TWIPS_PER_PIXEL : yCoord; objects.add(new ShapeStyle().setMove(pointX, pointY)); setControl((currentX + pointX) / 2, (currentY + pointY) / 2); setCurrent(pointX, pointY); setInitial(pointX, pointY); } /** * Move to the point (x,y). Use only when creating font definitions. * * @param xCoord * the x-coordinate of the point to move to. * @param yCoord * the y-coordinate of the point to move to. */ public void moveForFont(final int xCoord, final int yCoord) { final int pointX = pixels ? xCoord * TWIPS_PER_PIXEL : xCoord; final int pointY = pixels ? yCoord * TWIPS_PER_PIXEL : yCoord; final ShapeStyle style = new ShapeStyle().setMove(pointX, pointY); if (objects.isEmpty()) { style.setFillStyle(1); } objects.add(style); setControl((currentX + pointX) / 2, (currentY + pointY) / 2); setCurrent(pointX, pointY); setInitial(pointX, pointY); } /** * Move relative to the current point. * * @param xCoord * the distance along the x-axis. * @param yCoord * the distance along the y-axis. */ public void rmove(final int xCoord, final int yCoord) { final int pointX = pixels ? xCoord * TWIPS_PER_PIXEL : xCoord; final int pointY = pixels ? yCoord * TWIPS_PER_PIXEL : yCoord; objects.add(new ShapeStyle().setMove(pointX + currentX, pointY + currentY)); setControl(currentX + pointX / 2, currentY + pointY / 2); setCurrent(currentX + pointX, currentY + pointY); } /** * draw a line from the current point to the point (x,y). * * @param xCoord * the x-coordinate of the end of the line. * @param yCoord * the y-coordinate of the end of the line. */ public void line(final int xCoord, final int yCoord) { final int pointX = (pixels ? xCoord * TWIPS_PER_PIXEL : xCoord) - currentX; final int pointY = (pixels ? yCoord * TWIPS_PER_PIXEL : yCoord) - currentY; objects.add(new Line(pointX, pointY)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(currentX + pointX / 2, currentY + pointY / 2); setCurrent(currentX + pointX, currentY + pointY); } /** * Draw a line relative to the current point. * * @param xCoord * the distance along the x-axis to the end of the line. * @param yCoord * the distance along the y-axis to the end of the line. */ public void rline(final int xCoord, final int yCoord) { final int pointX = pixels ? xCoord * TWIPS_PER_PIXEL : xCoord; final int pointY = pixels ? yCoord * TWIPS_PER_PIXEL : yCoord; objects.add(new Line(pointX, pointY)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(currentX + pointX / 2, currentY + pointY / 2); setCurrent(currentX + pointX, currentY + pointY); } /** * Draw a quadratic bezier curve from the current point to the point (x,y) * with the control point (x1, y1). * * @param acontrolX * the x-coordinate of the control point. * @param acontrolY * the y-coordinate of the control point. * @param aanchorX * the x-coordinate of the end of the curve. * @param aanchorY * the y-coordinate of the end of the curve. */ public void curve(final int acontrolX, final int acontrolY, final int aanchorX, final int aanchorY) { int rcontrolX; int rcontrolY; int ranchorX; int ranchorY; if (pixels) { rcontrolX = acontrolX * TWIPS_PER_PIXEL - currentX; rcontrolY = acontrolY * TWIPS_PER_PIXEL - currentY; ranchorX = aanchorX * TWIPS_PER_PIXEL - currentX - rcontrolX; ranchorY = aanchorY * TWIPS_PER_PIXEL - currentY - rcontrolY; } else { rcontrolX = acontrolX - currentX; rcontrolY = acontrolY - currentY; ranchorX = aanchorX - currentX - rcontrolX; ranchorY = aanchorY - currentY - rcontrolY; } objects.add(new Curve(rcontrolX, rcontrolY, ranchorX, ranchorY)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(currentX + rcontrolX, currentY + rcontrolY); setCurrent(currentX + rcontrolX + ranchorX, currentY + rcontrolY + ranchorY); } /** * Draw a quadratic bezier curve relative to the current point to the point. * * @param rcontrolX * the distance along the x-axis from the current point to the * control point. * @param rcontrolY * the distance along the y-axis from the current point to the * control point. * @param ranchorX * the distance along the x-axis from the current point to the * end of the curve. * @param ranchorY * the distance along the y-axis from the current point to the * end of the curve. */ public void rcurve(final int rcontrolX, final int rcontrolY, final int ranchorX, final int ranchorY) { int px1; int py1; int px2; int py2; if (pixels) { px1 = rcontrolX * TWIPS_PER_PIXEL; py1 = rcontrolY * TWIPS_PER_PIXEL; px2 = ranchorX * TWIPS_PER_PIXEL; py2 = ranchorY * TWIPS_PER_PIXEL; } else { px1 = rcontrolX; py1 = rcontrolY; px2 = ranchorX; py2 = ranchorY; } objects.add(new Curve(px1, py1, px2, py2)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(currentX + px1, currentY + py1); setCurrent(currentX + px1 + px2, currentY + py1 + py2); } /** * Draw a cubic bezier curve from the current point to the point (x,y) with * the off-curve control points (x1, y1) and (x2, y2). * * IMPORTANT: Converting cubic bezier curves to the quadratic bezier curves * supported by Flash is mathematically difficult. The cubic curve is * approximated by a series of straight line segments. * * @param cax * the x-coordinate of the first control point. * @param cay * the y-coordinate of the first control point. * @param cbx * the x-coordinate of the second control point. * @param cby * the y-coordinate of the second control point. * @param anx * the x-coordinate of the end of the curve. * @param any * the y-coordinate of the end of the curve. */ public void curve(final int cax, final int cay, final int cbx, final int cby, final int anx, final int any) { cubicX[0] = currentX; cubicY[0] = currentY; if (pixels) { cubicX[CTRLA] = cax * TWIPS_PER_PIXEL; cubicY[CTRLA] = cay * TWIPS_PER_PIXEL; cubicX[CTRLB] = cbx * TWIPS_PER_PIXEL; cubicY[CTRLB] = cby * TWIPS_PER_PIXEL; cubicX[ANCHOR] = anx * TWIPS_PER_PIXEL; cubicY[ANCHOR] = any * TWIPS_PER_PIXEL; } else { cubicX[CTRLA] = cax; cubicY[CTRLA] = cay; cubicX[CTRLB] = cbx; cubicY[CTRLB] = cby; cubicX[ANCHOR] = anx; cubicY[ANCHOR] = any; } flatten(); } /** * Draw a cubic bezier curve relative to the current point. * * IMPORTANT: Converting cubic bezier curves to the quadratic bezier curves * supported by Flash is mathematically difficult. The cubic curve is * approximated by a series of straight line segments. * * @param controlAX * the distance along the x-axis from the current point to the * first control point. * @param controlAY * the distance along the y-axis from the current point to the * first control point. * @param controlBX * the distance along the x-axis from the current point to the * second control point. * @param controlBY * the distance along the y-axis from the current point to the * second control point. * @param anchorX * the distance along the x-axis from the current point to the * end of the curve. * @param anchorY * the distance along the y-axis from the current point to the * end of the curve. */ public void rcurve(final int controlAX, final int controlAY, final int controlBX, final int controlBY, final int anchorX, final int anchorY) { cubicX[0] = currentX; cubicY[0] = currentY; if (pixels) { cubicX[CTRLA] = currentX + controlAX * TWIPS_PER_PIXEL; cubicY[CTRLA] = currentY + controlAY * TWIPS_PER_PIXEL; cubicX[CTRLB] = currentX + controlBX * TWIPS_PER_PIXEL; cubicY[CTRLB] = currentY + controlBY * TWIPS_PER_PIXEL; cubicX[ANCHOR] = currentX + anchorX * TWIPS_PER_PIXEL; cubicY[ANCHOR] = currentY + anchorY * TWIPS_PER_PIXEL; } else { cubicX[CTRLA] = currentX + controlAX; cubicY[CTRLA] = currentY + controlAY; cubicX[CTRLB] = currentX + controlBX; cubicY[CTRLB] = currentY + controlBY; cubicX[ANCHOR] = currentX + anchorX; cubicY[ANCHOR] = currentY + anchorY; } flatten(); } /** * Draw a quadratic bezier curve from the current point to the point (x,y) * using the control point for the previously drawn curve. * * If no curve has been drawn previously then a control point midway along * the previous line or move is used. * * @param xCoord * the x-coordinate of the end of the curve. * @param yCoord * the y-coordinate of the end of the curve. */ public void reflect(final int xCoord, final int yCoord) { final int rcontrolX = currentX - controlX; final int rcontrolY = currentY - controlY; final int pointX = (pixels ? xCoord * TWIPS_PER_PIXEL : xCoord) - currentX; final int pointY = (pixels ? yCoord * TWIPS_PER_PIXEL : yCoord) - currentY; objects.add(new Curve(rcontrolX, rcontrolY, pointX, pointY)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(rcontrolX + currentX, rcontrolY + currentY); setCurrent(pointX + currentX, pointY + currentY); } /** * Draw a quadratic bezier curve relative to the current point to the point * using the control point for the previously drawn curve. * * If no curve has been drawn previously then a control point midway along * the previous line or move is used. * * @param xCoord * the distance along the x-axis from the current point to the * end of the curve. * @param yCoord * the distance along the y-axis from the current point to the * end of the curve. */ public void rreflect(final int xCoord, final int yCoord) { final int rcontrolX = currentX - controlX; final int rcontrolY = currentY - controlY; final int pointX = pixels ? xCoord * TWIPS_PER_PIXEL : xCoord; final int pointY = pixels ? yCoord * TWIPS_PER_PIXEL : yCoord; objects.add(new Curve(rcontrolX, rcontrolY, pointX, pointY)); if (!pathInProgress) { setInitial(currentX, currentY); pathInProgress = true; } setControl(rcontrolX + currentX, rcontrolY + currentY); setCurrent(pointX + currentX, pointY + currentY); } /** * Draw a cubic bezier curve from the current point to the point (x,y). The * first control point is the one defined for the previously drawn curve. * The second control point is the coordinates (x2, y2). * * If no curve has been drawn previously then a control point midway along * the previous line or move is used. * * @param ctrlX * the x-coordinate of the control point. * @param ctrlY * the y-coordinate of the control point. * @param anchorX * the x-coordinate of the end of the curve. * @param anchorY * the y-coordinate of the end of the curve. */ public void reflect(final int ctrlX, final int ctrlY, final int anchorX, final int anchorY) { final int acontrolX = currentX - controlX; final int acontrolY = currentY - controlY; int bcontrolX; int bcontrolY; int pointX; int pointY; if (pixels) { bcontrolX = ctrlX * TWIPS_PER_PIXEL - currentX; bcontrolY = ctrlY * TWIPS_PER_PIXEL - currentY; pointX = anchorX * TWIPS_PER_PIXEL - currentX; pointY = anchorY * TWIPS_PER_PIXEL - currentY; } else { bcontrolX = ctrlX - currentX; bcontrolY = ctrlY - currentY; pointX = anchorX - currentX; pointY = anchorY - currentY; } rcurve(acontrolX, acontrolY, bcontrolX, bcontrolY, pointX, pointY); } /** * Draw a cubic bezier curve relative to the current point. The first * control point is the one defined for the previously drawn curve. The * second control point is the relative point (x2, y2). * * If no curve has been drawn previously then a control point midway along * the previous line or move is used. * * @param ctrlX * the distance along the x-axis from the current point to the * second control point. * @param ctrlY * the distance along the y-axis from the current point to the * second control point. * @param anchorX * the distance along the x-axis from the current point to the * end of the curve. * @param anchorY * the distance along the y-axis from the current point to the * end of the curve. */ public void rreflect(final int ctrlX, final int ctrlY, final int anchorX, final int anchorY) { final int acontrolX = currentX - controlX; final int acontrolY = currentY - controlY; int bcontrolX; int bcontrolY; int pointX; int pointY; if (pixels) { bcontrolX = ctrlX * TWIPS_PER_PIXEL; bcontrolY = ctrlY * TWIPS_PER_PIXEL; pointX = anchorX * TWIPS_PER_PIXEL; pointY = anchorY * TWIPS_PER_PIXEL; } else { bcontrolX = ctrlX; bcontrolY = ctrlY; pointX = anchorX; pointY = anchorY; } rcurve(acontrolX, acontrolY, bcontrolX, bcontrolY, pointX, pointY); } /** * Draws a closed shape with vertices defines by pairs of coordinates from * the array argument. The first pair of points in the array specifies a * move. Line segments a drawn relative to the current point which is * updated after each segment is drawn. * * If the number of points is an odd number then the last point will be * ignored. * * @param points * and array of coordinate pairs. The first pair of points * defines the coordinates of a move operation, successive pairs * define the coordinates for relative lines. */ public void rpolygon(final int[] points) { int length = points.length; if (length % 2 == 1) { length -= 1; } rmove(points[0], points[1]); for (int i = 2; i < length; i += 2) { rline(points[i], points[i + 1]); } close(); } /** * Draws a closed shape with vertices defines by pairs of coordinates from * the array argument. The first pair of points in the array specifies a * move. Line segments a drawn using absolute coordinates. The current point * which is updated after each segment is drawn. * * If the number of points is an odd number then the last point will be * ignored. * * @param points * and array of coordinate pairs. The first pair of points * defines the coordinates of a move operation, successive pairs * define the coordinates of the lines. */ public void polygon(final int[] points) { int length = points.length; if (length % 2 == 1) { length -= 1; } move(points[0], points[1]); for (int i = 2; i < length; i += 2) { line(points[i], points[i + 1]); } close(); } /** * Set the initial point. * @param xCoord the x-coordinate of the initial point. * @param yCoord the y-coordinate of the initial point. */ private void setInitial(final int xCoord, final int yCoord) { initialX = xCoord; initialY = yCoord; } /** * Set the current point. * @param xCoord the x-coordinate of the current point. * @param yCoord the y-coordinate of the current point. */ private void setCurrent(final int xCoord, final int yCoord) { currentX = xCoord; currentY = yCoord; if ((xCoord - lineWidth / 2) < minX) { minX = xCoord - lineWidth / 2; } if ((yCoord - lineWidth / 2) < minY) { minY = yCoord - lineWidth / 2; } if ((xCoord + lineWidth / 2) > maxX) { maxX = xCoord + lineWidth / 2; } if ((yCoord + lineWidth / 2) > maxY) { maxY = yCoord + lineWidth / 2; } } /** * Set the control point on a quadratic curve. * @param xCoord the x-coordinate of the control point. * @param yCoord the y-coordinate of the control point. */ private void setControl(final int xCoord, final int yCoord) { controlX = xCoord; controlY = yCoord; if ((xCoord - lineWidth / 2) < minX) { minX = xCoord - lineWidth / 2; } if ((yCoord - lineWidth / 2) < minY) { minY = yCoord - lineWidth / 2; } if ((xCoord + lineWidth / 2) > maxX) { maxX = xCoord + lineWidth / 2; } if ((yCoord + lineWidth / 2) > maxY) { maxY = yCoord + lineWidth / 2; } } /** * Set the bounds for the shape being drawn. * * @param xmin * x-coordinate of the top left corner. * @param ymin * y-coordinate of the top left corner. * @param xmax * x-coordinate of bottom right corner. * @param ymax * y-coordinate of bottom right corner. */ private void setBounds(final int xmin, final int ymin, final int xmax, final int ymax) { minX = xmin; minY = ymin; maxX = xmax; maxY = ymax; } /** * Flatten a cubic Bezier curve into a series of straight line segments. */ private void flatten() { final double[] quadX = {0.0, 0.0, 0.0, 0.0}; final double[] quadY = {0.0, 0.0, 0.0, 0.0}; double delta; double pointAX; double pointAY; double pointBX; double pointBY; while (true) { pointAX = CTRL_AVG * cubicX[START] + cubicX[ANCHOR] - ANCHOR_AVG * cubicX[CTRLA]; pointAX *= pointAX; pointBX = CTRL_AVG * cubicX[ANCHOR] + cubicX[START] - ANCHOR_AVG * cubicX[CTRLB]; pointBX *= pointBX; if (pointAX < pointBX) { pointAX = pointBX; } pointAY = CTRL_AVG * cubicY[START] + cubicY[ANCHOR] - ANCHOR_AVG * cubicY[CTRLA]; pointAY *= pointAY; pointBY = CTRL_AVG * cubicY[ANCHOR] + cubicY[START] - ANCHOR_AVG * cubicY[CTRLB]; pointBY *= pointBY; if (pointAY < pointBY) { pointAY = pointBY; } if ((pointAX + pointAY) < FLATTEN_LIMIT) { objects.add(new Line((int) (cubicX[ANCHOR]) - currentX, (int) (cubicY[ANCHOR]) - currentY)); setControl((int) cubicX[CTRLA], (int) cubicY[CTRLA]); setControl((int) cubicX[CTRLB], (int) cubicY[CTRLB]); setCurrent((int) cubicX[ANCHOR], (int) cubicY[ANCHOR]); break; } else { quadX[ANCHOR] = cubicX[ANCHOR]; delta = (cubicX[CTRLA] + cubicX[CTRLB]) / MID; cubicX[1] = (cubicX[START] + cubicX[CTRLA]) / MID; quadX[2] = (cubicX[CTRLB] + cubicX[ANCHOR]) / MID; cubicX[2] = (cubicX[CTRLA] + delta) / MID; quadX[1] = (delta + quadX[CTRLB]) / MID; cubicX[ANCHOR] = (cubicX[CTRLB] + quadX[CTRLA]) / MID; quadX[0] = (cubicX[CTRLB] + quadX[CTRLA]) / MID; quadY[ANCHOR] = cubicY[ANCHOR]; delta = (cubicY[CTRLA] + cubicY[CTRLB]) / MID; cubicY[1] = (cubicY[START] + cubicY[CTRLA]) / MID; quadY[2] = (cubicY[CTRLB] + cubicY[ANCHOR]) / MID; cubicY[2] = (cubicY[CTRLA] + delta) / MID; quadY[1] = (delta + quadY[CTRLB]) / MID; cubicY[ANCHOR] = (cubicY[CTRLB] + quadY[CTRLA]) / MID; quadY[0] = (cubicY[CTRLB] + quadY[CTRLA]) / MID; flatten(); cubicX[START] = quadX[START]; cubicY[START] = quadY[START]; cubicX[CTRLA] = quadX[CTRLA]; cubicY[CTRLA] = quadY[CTRLA]; cubicX[CTRLB] = quadX[CTRLB]; cubicY[CTRLB] = quadY[CTRLB]; cubicX[ANCHOR] = quadX[ANCHOR]; cubicY[ANCHOR] = quadY[ANCHOR]; } } } }