| package processing.core; |
| |
| import java.awt.Paint; |
| import java.awt.PaintContext; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.awt.image.ColorModel; |
| import java.awt.image.Raster; |
| import java.awt.image.WritableRaster; |
| import java.util.HashMap; |
| |
| import processing.xml.XMLElement; |
| |
| |
| /** |
| * SVG stands for Scalable Vector Graphics, a portable graphics format. It is |
| * a vector format so it allows for infinite resolution and relatively small |
| * file sizes. Most modern media software can view SVG files, including Adobe |
| * products, Firefox, etc. Illustrator and Inkscape can edit SVG files. |
| * <p> |
| * We have no intention of turning this into a full-featured SVG library. |
| * The goal of this project is a basic shape importer that is small enough |
| * to be included with applets, meaning that its download size should be |
| * in the neighborhood of 25-30k. Starting with release 0149, this library |
| * has been incorporated into the core via the loadShape() command, because |
| * vector shape data is just as important as the image data from loadImage(). |
| * <p> |
| * For more sophisticated import/export, consider the |
| * <A HREF="http://xmlgraphics.apache.org/batik/">Batik</A> |
| * library from the Apache Software Foundation. Future improvements to this |
| * library may focus on this properly supporting a specific subset of SVG, |
| * for instance the simpler SVG profiles known as |
| * <A HREF="http://www.w3.org/TR/SVGMobile/">SVG Tiny or Basic</A>, |
| * although we still would not support the interactivity options. |
| * |
| * <p> <hr noshade> <p> |
| * |
| * A minimal example program using SVG: |
| * (assuming a working moo.svg is in your data folder) |
| * |
| * <PRE> |
| * PShape moo; |
| * |
| * void setup() { |
| * size(400, 400); |
| * moo = loadShape("moo.svg"); |
| * } |
| * void draw() { |
| * background(255); |
| * shape(moo, mouseX, mouseY); |
| * } |
| * </PRE> |
| * |
| * This code is based on the Candy library written by Michael Chang, which was |
| * later revised and expanded for use as a Processing core library by Ben Fry. |
| * Thanks to Ricard Marxer Pinon for help with better Inkscape support in 0154. |
| * |
| * <p> <hr noshade> <p> |
| * |
| * Late October 2008 revisions from ricardmp, incorporated by fry (0154) |
| * <UL> |
| * <LI>Better style attribute handling, enabling better Inkscape support. |
| * </UL> |
| * |
| * October 2008 revisions by fry (Processing 0149, pre-1.0) |
| * <UL> |
| * <LI> Candy is no longer a separate library, and is instead part of core. |
| * <LI> Loading now works through loadShape() |
| * <LI> Shapes are now drawn using the new PGraphics shape() method. |
| * </UL> |
| * |
| * August 2008 revisions by fry (Processing 0149) |
| * <UL> |
| * <LI> Major changes to rework around PShape. |
| * <LI> Now implementing more of the "transform" attribute. |
| * </UL> |
| * |
| * February 2008 revisions by fry (Processing 0136) |
| * <UL> |
| * <LI> Added support for quadratic curves in paths (Q, q, T, and t operators) |
| * <LI> Support for reading SVG font data (though not rendering it yet) |
| * </UL> |
| * |
| * Revisions for "Candy 2" November 2006 by fry |
| * <UL> |
| * <LI> Switch to the new processing.xml library |
| * <LI> Several bug fixes for parsing of shape data |
| * <LI> Support for linear and radial gradients |
| * <LI> Support for additional types of shapes |
| * <LI> Added compound shapes (shapes with interior points) |
| * <LI> Added methods to get shapes from an internal table |
| * </UL> |
| * |
| * Revision 10/31/06 by flux |
| * <UL> |
| * <LI> Now properly supports Processing 0118 |
| * <LI> Fixed a bunch of things for Casey's students and general buggity. |
| * <LI> Will now properly draw #FFFFFFFF colors (were being represented as -1) |
| * <LI> SVGs without <g> tags are now properly caught and loaded |
| * <LI> Added a method customStyle() for overriding SVG colors/styles |
| * <LI> Added a method SVGStyle() to go back to using SVG colors/styles |
| * </UL> |
| * |
| * Some SVG objects and features may not yet be supported. |
| * Here is a partial list of non-included features |
| * <UL> |
| * <LI> Rounded rectangles |
| * <LI> Drop shadow objects |
| * <LI> Typography |
| * <LI> <STRIKE>Layers</STRIKE> added for Candy 2 |
| * <LI> Patterns |
| * <LI> Embedded images |
| * </UL> |
| * |
| * For those interested, the SVG specification can be found |
| * <A HREF="http://www.w3.org/TR/SVG">here</A>. |
| */ |
| public class PShapeSVG extends PShape { |
| XMLElement element; |
| |
| float opacity; |
| float strokeOpacity; |
| float fillOpacity; |
| |
| |
| Gradient strokeGradient; |
| Paint strokeGradientPaint; |
| String strokeName; // id of another object, gradients only? |
| |
| Gradient fillGradient; |
| Paint fillGradientPaint; |
| String fillName; // id of another object |
| |
| |
| /** |
| * Initializes a new SVG Object with the given filename. |
| */ |
| public PShapeSVG(PApplet parent, String filename) { |
| // this will grab the root document, starting <svg ...> |
| // the xml version and initial comments are ignored |
| this(new XMLElement(parent, filename)); |
| } |
| |
| |
| /** |
| * Initializes a new SVG Object from the given XMLElement. |
| */ |
| public PShapeSVG(XMLElement svg) { |
| this(null, svg); |
| |
| if (!svg.getName().equals("svg")) { |
| throw new RuntimeException("root is not <svg>, it's <" + svg.getName() + ">"); |
| } |
| |
| // not proper parsing of the viewBox, but will cover us for cases where |
| // the width and height of the object is not specified |
| String viewBoxStr = svg.getStringAttribute("viewBox"); |
| if (viewBoxStr != null) { |
| int[] viewBox = PApplet.parseInt(PApplet.splitTokens(viewBoxStr)); |
| width = viewBox[2]; |
| height = viewBox[3]; |
| } |
| |
| // TODO if viewbox is not same as width/height, then use it to scale |
| // the original objects. for now, viewbox only used when width/height |
| // are empty values (which by the spec means w/h of "100%" |
| String unitWidth = svg.getStringAttribute("width"); |
| String unitHeight = svg.getStringAttribute("height"); |
| if (unitWidth != null) { |
| width = parseUnitSize(unitWidth); |
| height = parseUnitSize(unitHeight); |
| } else { |
| if ((width == 0) || (height == 0)) { |
| //throw new RuntimeException("width/height not specified"); |
| PGraphics.showWarning("The width and/or height is not " + |
| "readable in the <svg> tag of this file."); |
| // For the spec, the default is 100% and 100%. For purposes |
| // here, insert a dummy value because this is prolly just a |
| // font or something for which the w/h doesn't matter. |
| width = 1; |
| height = 1; |
| } |
| } |
| |
| //root = new Group(null, svg); |
| parseChildren(svg); // ? |
| } |
| |
| |
| public PShapeSVG(PShapeSVG parent, XMLElement properties) { |
| //super(GROUP); |
| |
| if (parent == null) { |
| // set values to their defaults according to the SVG spec |
| stroke = false; |
| strokeColor = 0xff000000; |
| strokeWeight = 1; |
| strokeCap = PConstants.SQUARE; // equivalent to BUTT in svg spec |
| strokeJoin = PConstants.MITER; |
| strokeGradient = null; |
| strokeGradientPaint = null; |
| strokeName = null; |
| |
| fill = true; |
| fillColor = 0xff000000; |
| fillGradient = null; |
| fillGradientPaint = null; |
| fillName = null; |
| |
| //hasTransform = false; |
| //transformation = null; //new float[] { 1, 0, 0, 1, 0, 0 }; |
| |
| strokeOpacity = 1; |
| fillOpacity = 1; |
| opacity = 1; |
| |
| } else { |
| stroke = parent.stroke; |
| strokeColor = parent.strokeColor; |
| strokeWeight = parent.strokeWeight; |
| strokeCap = parent.strokeCap; |
| strokeJoin = parent.strokeJoin; |
| strokeGradient = parent.strokeGradient; |
| strokeGradientPaint = parent.strokeGradientPaint; |
| strokeName = parent.strokeName; |
| |
| fill = parent.fill; |
| fillColor = parent.fillColor; |
| fillGradient = parent.fillGradient; |
| fillGradientPaint = parent.fillGradientPaint; |
| fillName = parent.fillName; |
| |
| //hasTransform = parent.hasTransform; |
| //transformation = parent.transformation; |
| |
| opacity = parent.opacity; |
| } |
| |
| element = properties; |
| name = properties.getStringAttribute("id"); |
| |
| String displayStr = properties.getStringAttribute("display", "inline"); |
| visible = !displayStr.equals("none"); |
| |
| String transformStr = properties.getStringAttribute("transform"); |
| if (transformStr != null) { |
| matrix = parseMatrix(transformStr); |
| } |
| |
| parseColors(properties); |
| parseChildren(properties); |
| } |
| |
| |
| protected void parseChildren(XMLElement graphics) { |
| XMLElement[] elements = graphics.getChildren(); |
| children = new PShape[elements.length]; |
| childCount = 0; |
| |
| for (XMLElement elem : elements) { |
| PShape kid = parseChild(elem); |
| if (kid != null) { |
| addChild(kid); |
| } |
| } |
| } |
| |
| |
| /** |
| * Parse a child XML element. |
| * Override this method to add parsing for more SVG elements. |
| */ |
| protected PShape parseChild(XMLElement elem) { |
| String name = elem.getName(); |
| PShapeSVG shape = null; |
| |
| if (name.equals("g")) { |
| //return new BaseObject(this, elem); |
| shape = new PShapeSVG(this, elem); |
| |
| } else if (name.equals("defs")) { |
| // generally this will contain gradient info, so may |
| // as well just throw it into a group element for parsing |
| //return new BaseObject(this, elem); |
| shape = new PShapeSVG(this, elem); |
| |
| } else if (name.equals("line")) { |
| //return new Line(this, elem); |
| //return new BaseObject(this, elem, LINE); |
| shape = new PShapeSVG(this, elem); |
| shape.parseLine(); |
| |
| } else if (name.equals("circle")) { |
| //return new BaseObject(this, elem, ELLIPSE); |
| shape = new PShapeSVG(this, elem); |
| shape.parseEllipse(true); |
| |
| } else if (name.equals("ellipse")) { |
| //return new BaseObject(this, elem, ELLIPSE); |
| shape = new PShapeSVG(this, elem); |
| shape.parseEllipse(false); |
| |
| } else if (name.equals("rect")) { |
| //return new BaseObject(this, elem, RECT); |
| shape = new PShapeSVG(this, elem); |
| shape.parseRect(); |
| |
| } else if (name.equals("polygon")) { |
| //return new BaseObject(this, elem, POLYGON); |
| shape = new PShapeSVG(this, elem); |
| shape.parsePoly(true); |
| |
| } else if (name.equals("polyline")) { |
| //return new BaseObject(this, elem, POLYGON); |
| shape = new PShapeSVG(this, elem); |
| shape.parsePoly(false); |
| |
| } else if (name.equals("path")) { |
| //return new BaseObject(this, elem, PATH); |
| shape = new PShapeSVG(this, elem); |
| shape.parsePath(); |
| |
| } else if (name.equals("radialGradient")) { |
| return new RadialGradient(this, elem); |
| |
| } else if (name.equals("linearGradient")) { |
| return new LinearGradient(this, elem); |
| |
| } else if (name.equals("text")) { |
| PGraphics.showWarning("Text in SVG files is not currently supported, " + |
| "convert text to outlines instead."); |
| |
| } else if (name.equals("filter")) { |
| PGraphics.showWarning("Filters are not supported."); |
| |
| } else if (name.equals("mask")) { |
| PGraphics.showWarning("Masks are not supported."); |
| |
| } else { |
| PGraphics.showWarning("Ignoring <" + name + "> tag."); |
| } |
| return shape; |
| } |
| |
| |
| protected void parseLine() { |
| kind = LINE; |
| family = PRIMITIVE; |
| params = new float[] { |
| element.getFloatAttribute("x1"), |
| element.getFloatAttribute("y1"), |
| element.getFloatAttribute("x2"), |
| element.getFloatAttribute("y2"), |
| }; |
| // x = params[0]; |
| // y = params[1]; |
| // width = params[2]; |
| // height = params[3]; |
| } |
| |
| |
| /** |
| * Handles parsing ellipse and circle tags. |
| * @param circle true if this is a circle and not an ellipse |
| */ |
| protected void parseEllipse(boolean circle) { |
| kind = ELLIPSE; |
| family = PRIMITIVE; |
| params = new float[4]; |
| |
| params[0] = element.getFloatAttribute("cx"); |
| params[1] = element.getFloatAttribute("cy"); |
| |
| float rx, ry; |
| if (circle) { |
| rx = ry = element.getFloatAttribute("r"); |
| } else { |
| rx = element.getFloatAttribute("rx"); |
| ry = element.getFloatAttribute("ry"); |
| } |
| params[0] -= rx; |
| params[1] -= ry; |
| |
| params[2] = rx*2; |
| params[3] = ry*2; |
| } |
| |
| |
| protected void parseRect() { |
| kind = RECT; |
| family = PRIMITIVE; |
| params = new float[] { |
| element.getFloatAttribute("x"), |
| element.getFloatAttribute("y"), |
| element.getFloatAttribute("width"), |
| element.getFloatAttribute("height"), |
| }; |
| } |
| |
| |
| /** |
| * Parse a polyline or polygon from an SVG file. |
| * @param close true if shape is closed (polygon), false if not (polyline) |
| */ |
| protected void parsePoly(boolean close) { |
| family = PATH; |
| this.close = close; |
| |
| String pointsAttr = element.getStringAttribute("points"); |
| if (pointsAttr != null) { |
| String[] pointsBuffer = PApplet.splitTokens(pointsAttr); |
| vertexCount = pointsBuffer.length; |
| vertices = new float[vertexCount][2]; |
| for (int i = 0; i < vertexCount; i++) { |
| String pb[] = PApplet.split(pointsBuffer[i], ','); |
| vertices[i][X] = Float.valueOf(pb[0]).floatValue(); |
| vertices[i][Y] = Float.valueOf(pb[1]).floatValue(); |
| } |
| } |
| } |
| |
| |
| protected void parsePath() { |
| family = PATH; |
| kind = 0; |
| |
| String pathData = element.getStringAttribute("d"); |
| if (pathData == null) return; |
| char[] pathDataChars = pathData.toCharArray(); |
| |
| StringBuffer pathBuffer = new StringBuffer(); |
| boolean lastSeparate = false; |
| |
| for (int i = 0; i < pathDataChars.length; i++) { |
| char c = pathDataChars[i]; |
| boolean separate = false; |
| |
| if (c == 'M' || c == 'm' || |
| c == 'L' || c == 'l' || |
| c == 'H' || c == 'h' || |
| c == 'V' || c == 'v' || |
| c == 'C' || c == 'c' || // beziers |
| c == 'S' || c == 's' || |
| c == 'Q' || c == 'q' || // quadratic beziers |
| c == 'T' || c == 't' || |
| c == 'Z' || c == 'z' || // closepath |
| c == ',') { |
| separate = true; |
| if (i != 0) { |
| pathBuffer.append("|"); |
| } |
| } |
| if (c == 'Z' || c == 'z') { |
| separate = false; |
| } |
| if (c == '-' && !lastSeparate) { |
| // allow for 'e' notation in numbers, e.g. 2.10e-9 |
| // http://dev.processing.org/bugs/show_bug.cgi?id=1408 |
| if (i == 0 || pathDataChars[i-1] != 'e') { |
| pathBuffer.append("|"); |
| } |
| } |
| if (c != ',') { |
| pathBuffer.append(c); //"" + pathDataBuffer.charAt(i)); |
| } |
| if (separate && c != ',' && c != '-') { |
| pathBuffer.append("|"); |
| } |
| lastSeparate = separate; |
| } |
| |
| // use whitespace constant to get rid of extra spaces and CR or LF |
| String[] pathDataKeys = |
| PApplet.splitTokens(pathBuffer.toString(), "|" + WHITESPACE); |
| vertices = new float[pathDataKeys.length][2]; |
| vertexCodes = new int[pathDataKeys.length]; |
| |
| float cx = 0; |
| float cy = 0; |
| |
| int i = 0; |
| while (i < pathDataKeys.length) { |
| char c = pathDataKeys[i].charAt(0); |
| switch (c) { |
| |
| case 'M': // M - move to (absolute) |
| cx = PApplet.parseFloat(pathDataKeys[i + 1]); |
| cy = PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathMoveto(cx, cy); |
| i += 3; |
| break; |
| |
| case 'm': // m - move to (relative) |
| cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| cy = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathMoveto(cx, cy); |
| i += 3; |
| break; |
| |
| case 'L': |
| cx = PApplet.parseFloat(pathDataKeys[i + 1]); |
| cy = PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathLineto(cx, cy); |
| i += 3; |
| break; |
| |
| case 'l': |
| cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| cy = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathLineto(cx, cy); |
| i += 3; |
| break; |
| |
| // horizontal lineto absolute |
| case 'H': |
| cx = PApplet.parseFloat(pathDataKeys[i + 1]); |
| parsePathLineto(cx, cy); |
| i += 2; |
| break; |
| |
| // horizontal lineto relative |
| case 'h': |
| cx = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| parsePathLineto(cx, cy); |
| i += 2; |
| break; |
| |
| case 'V': |
| cy = PApplet.parseFloat(pathDataKeys[i + 1]); |
| parsePathLineto(cx, cy); |
| i += 2; |
| break; |
| |
| case 'v': |
| cy = cy + PApplet.parseFloat(pathDataKeys[i + 1]); |
| parsePathLineto(cx, cy); |
| i += 2; |
| break; |
| |
| // C - curve to (absolute) |
| case 'C': { |
| float ctrlX1 = PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY1 = PApplet.parseFloat(pathDataKeys[i + 2]); |
| float ctrlX2 = PApplet.parseFloat(pathDataKeys[i + 3]); |
| float ctrlY2 = PApplet.parseFloat(pathDataKeys[i + 4]); |
| float endX = PApplet.parseFloat(pathDataKeys[i + 5]); |
| float endY = PApplet.parseFloat(pathDataKeys[i + 6]); |
| parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 7; |
| } |
| break; |
| |
| // c - curve to (relative) |
| case 'c': { |
| float ctrlX1 = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY1 = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| float ctrlX2 = cx + PApplet.parseFloat(pathDataKeys[i + 3]); |
| float ctrlY2 = cy + PApplet.parseFloat(pathDataKeys[i + 4]); |
| float endX = cx + PApplet.parseFloat(pathDataKeys[i + 5]); |
| float endY = cy + PApplet.parseFloat(pathDataKeys[i + 6]); |
| parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 7; |
| } |
| break; |
| |
| // S - curve to shorthand (absolute) |
| case 'S': { |
| float ppx = vertices[vertexCount-2][X]; |
| float ppy = vertices[vertexCount-2][Y]; |
| float px = vertices[vertexCount-1][X]; |
| float py = vertices[vertexCount-1][Y]; |
| float ctrlX1 = px + (px - ppx); |
| float ctrlY1 = py + (py - ppy); |
| float ctrlX2 = PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY2 = PApplet.parseFloat(pathDataKeys[i + 2]); |
| float endX = PApplet.parseFloat(pathDataKeys[i + 3]); |
| float endY = PApplet.parseFloat(pathDataKeys[i + 4]); |
| parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 5; |
| } |
| break; |
| |
| // s - curve to shorthand (relative) |
| case 's': { |
| float ppx = vertices[vertexCount-2][X]; |
| float ppy = vertices[vertexCount-2][Y]; |
| float px = vertices[vertexCount-1][X]; |
| float py = vertices[vertexCount-1][Y]; |
| float ctrlX1 = px + (px - ppx); |
| float ctrlY1 = py + (py - ppy); |
| float ctrlX2 = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY2 = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| float endX = cx + PApplet.parseFloat(pathDataKeys[i + 3]); |
| float endY = cy + PApplet.parseFloat(pathDataKeys[i + 4]); |
| parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 5; |
| } |
| break; |
| |
| // Q - quadratic curve to (absolute) |
| case 'Q': { |
| float ctrlX = PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY = PApplet.parseFloat(pathDataKeys[i + 2]); |
| float endX = PApplet.parseFloat(pathDataKeys[i + 3]); |
| float endY = PApplet.parseFloat(pathDataKeys[i + 4]); |
| parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 5; |
| } |
| break; |
| |
| // q - quadratic curve to (relative) |
| case 'q': { |
| float ctrlX = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| float ctrlY = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| float endX = cx + PApplet.parseFloat(pathDataKeys[i + 3]); |
| float endY = cy + PApplet.parseFloat(pathDataKeys[i + 4]); |
| parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 5; |
| } |
| break; |
| |
| // T - quadratic curve to shorthand (absolute) |
| // The control point is assumed to be the reflection of the |
| // control point on the previous command relative to the |
| // current point. (If there is no previous command or if the |
| // previous command was not a Q, q, T or t, assume the control |
| // point is coincident with the current point.) |
| case 'T': { |
| float ppx = vertices[vertexCount-2][X]; |
| float ppy = vertices[vertexCount-2][Y]; |
| float px = vertices[vertexCount-1][X]; |
| float py = vertices[vertexCount-1][Y]; |
| float ctrlX = px + (px - ppx); |
| float ctrlY = py + (py - ppy); |
| float endX = PApplet.parseFloat(pathDataKeys[i + 1]); |
| float endY = PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 3; |
| } |
| break; |
| |
| // t - quadratic curve to shorthand (relative) |
| case 't': { |
| float ppx = vertices[vertexCount-2][X]; |
| float ppy = vertices[vertexCount-2][Y]; |
| float px = vertices[vertexCount-1][X]; |
| float py = vertices[vertexCount-1][Y]; |
| float ctrlX = px + (px - ppx); |
| float ctrlY = py + (py - ppy); |
| float endX = cx + PApplet.parseFloat(pathDataKeys[i + 1]); |
| float endY = cy + PApplet.parseFloat(pathDataKeys[i + 2]); |
| parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); |
| cx = endX; |
| cy = endY; |
| i += 3; |
| } |
| break; |
| |
| case 'Z': |
| case 'z': |
| close = true; |
| i++; |
| break; |
| |
| default: |
| String parsed = |
| PApplet.join(PApplet.subset(pathDataKeys, 0, i), ","); |
| String unparsed = |
| PApplet.join(PApplet.subset(pathDataKeys, i), ","); |
| System.err.println("parsed: " + parsed); |
| System.err.println("unparsed: " + unparsed); |
| if (pathDataKeys[i].equals("a") || pathDataKeys[i].equals("A")) { |
| String msg = "Sorry, elliptical arc support for SVG files " + |
| "is not yet implemented (See bug #996 for details)"; |
| throw new RuntimeException(msg); |
| } |
| throw new RuntimeException("shape command not handled: " + pathDataKeys[i]); |
| } |
| } |
| } |
| |
| |
| // private void parsePathCheck(int num) { |
| // if (vertexCount + num-1 >= vertices.length) { |
| // //vertices = (float[][]) PApplet.expand(vertices); |
| // float[][] temp = new float[vertexCount << 1][2]; |
| // System.arraycopy(vertices, 0, temp, 0, vertexCount); |
| // vertices = temp; |
| // } |
| // } |
| |
| private void parsePathVertex(float x, float y) { |
| if (vertexCount == vertices.length) { |
| //vertices = (float[][]) PApplet.expand(vertices); |
| float[][] temp = new float[vertexCount << 1][2]; |
| System.arraycopy(vertices, 0, temp, 0, vertexCount); |
| vertices = temp; |
| } |
| vertices[vertexCount][X] = x; |
| vertices[vertexCount][Y] = y; |
| vertexCount++; |
| } |
| |
| |
| private void parsePathCode(int what) { |
| if (vertexCodeCount == vertexCodes.length) { |
| vertexCodes = PApplet.expand(vertexCodes); |
| } |
| vertexCodes[vertexCodeCount++] = what; |
| } |
| |
| |
| private void parsePathMoveto(float px, float py) { |
| if (vertexCount > 0) { |
| parsePathCode(BREAK); |
| } |
| parsePathCode(VERTEX); |
| parsePathVertex(px, py); |
| } |
| |
| |
| private void parsePathLineto(float px, float py) { |
| parsePathCode(VERTEX); |
| parsePathVertex(px, py); |
| } |
| |
| |
| private void parsePathCurveto(float x1, float y1, |
| float x2, float y2, |
| float x3, float y3) { |
| parsePathCode(BEZIER_VERTEX); |
| parsePathVertex(x1, y1); |
| parsePathVertex(x2, y2); |
| parsePathVertex(x3, y3); |
| } |
| |
| private void parsePathQuadto(float x1, float y1, |
| float cx, float cy, |
| float x2, float y2) { |
| parsePathCode(BEZIER_VERTEX); |
| // x1/y1 already covered by last moveto, lineto, or curveto |
| parsePathVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f)); |
| parsePathVertex(x2 + ((cx-x2)*2/3.0f), y2 + ((cy-y2)*2/3.0f)); |
| parsePathVertex(x2, y2); |
| } |
| |
| |
| /** |
| * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D |
| * is rotated relative to the SVG definition, so parameters are rearranged |
| * here. More about the transformation matrices in |
| * <a href="http://www.w3.org/TR/SVG/coords.html#TransformAttribute">this section</a> |
| * of the SVG documentation. |
| * @param matrixStr text of the matrix param. |
| * @return a good old-fashioned PMatrix2D |
| */ |
| static protected PMatrix2D parseMatrix(String matrixStr) { |
| String[] pieces = PApplet.match(matrixStr, "\\s*(\\w+)\\((.*)\\)"); |
| if (pieces == null) { |
| System.err.println("Could not parse transform " + matrixStr); |
| return null; |
| } |
| float[] m = PApplet.parseFloat(PApplet.splitTokens(pieces[2], ", ")); |
| if (pieces[1].equals("matrix")) { |
| return new PMatrix2D(m[0], m[2], m[4], m[1], m[3], m[5]); |
| |
| } else if (pieces[1].equals("translate")) { |
| float tx = m[0]; |
| float ty = (m.length == 2) ? m[1] : m[0]; |
| //return new float[] { 1, 0, tx, 0, 1, ty }; |
| return new PMatrix2D(1, 0, tx, 0, 1, ty); |
| |
| } else if (pieces[1].equals("scale")) { |
| float sx = m[0]; |
| float sy = (m.length == 2) ? m[1] : m[0]; |
| //return new float[] { sx, 0, 0, 0, sy, 0 }; |
| return new PMatrix2D(sx, 0, 0, 0, sy, 0); |
| |
| } else if (pieces[1].equals("rotate")) { |
| float angle = m[0]; |
| |
| if (m.length == 1) { |
| float c = PApplet.cos(angle); |
| float s = PApplet.sin(angle); |
| // SVG version is cos(a) sin(a) -sin(a) cos(a) 0 0 |
| return new PMatrix2D(c, -s, 0, s, c, 0); |
| |
| } else if (m.length == 3) { |
| PMatrix2D mat = new PMatrix2D(0, 1, m[1], 1, 0, m[2]); |
| mat.rotate(m[0]); |
| mat.translate(-m[1], -m[2]); |
| return mat; //.get(null); |
| } |
| |
| } else if (pieces[1].equals("skewX")) { |
| return new PMatrix2D(1, 0, 1, PApplet.tan(m[0]), 0, 0); |
| |
| } else if (pieces[1].equals("skewY")) { |
| return new PMatrix2D(1, 0, 1, 0, PApplet.tan(m[0]), 0); |
| } |
| return null; |
| } |
| |
| |
| protected void parseColors(XMLElement properties) { |
| if (properties.hasAttribute("opacity")) { |
| String opacityText = properties.getStringAttribute("opacity"); |
| setOpacity(opacityText); |
| } |
| |
| if (properties.hasAttribute("stroke")) { |
| String strokeText = properties.getStringAttribute("stroke"); |
| setStroke(strokeText); |
| } |
| |
| if (properties.hasAttribute("stroke-width")) { |
| // if NaN (i.e. if it's 'inherit') then default back to the inherit setting |
| String lineweight = properties.getStringAttribute("stroke-width"); |
| setStrokeWeight(lineweight); |
| } |
| |
| if (properties.hasAttribute("stroke-linejoin")) { |
| String linejoin = properties.getStringAttribute("stroke-linejoin"); |
| setStrokeJoin(linejoin); |
| } |
| |
| if (properties.hasAttribute("stroke-linecap")) { |
| String linecap = properties.getStringAttribute("stroke-linecap"); |
| setStrokeCap(linecap); |
| } |
| |
| |
| // fill defaults to black (though stroke defaults to "none") |
| // http://www.w3.org/TR/SVG/painting.html#FillProperties |
| if (properties.hasAttribute("fill")) { |
| String fillText = properties.getStringAttribute("fill"); |
| setFill(fillText); |
| |
| } |
| |
| if (properties.hasAttribute("style")) { |
| String styleText = properties.getStringAttribute("style"); |
| String[] styleTokens = PApplet.splitTokens(styleText, ";"); |
| |
| //PApplet.println(styleTokens); |
| for (int i = 0; i < styleTokens.length; i++) { |
| String[] tokens = PApplet.splitTokens(styleTokens[i], ":"); |
| //PApplet.println(tokens); |
| |
| tokens[0] = PApplet.trim(tokens[0]); |
| |
| if (tokens[0].equals("fill")) { |
| setFill(tokens[1]); |
| |
| } else if(tokens[0].equals("fill-opacity")) { |
| setFillOpacity(tokens[1]); |
| |
| } else if(tokens[0].equals("stroke")) { |
| setStroke(tokens[1]); |
| |
| } else if(tokens[0].equals("stroke-width")) { |
| setStrokeWeight(tokens[1]); |
| |
| } else if(tokens[0].equals("stroke-linecap")) { |
| setStrokeCap(tokens[1]); |
| |
| } else if(tokens[0].equals("stroke-linejoin")) { |
| setStrokeJoin(tokens[1]); |
| |
| } else if(tokens[0].equals("stroke-opacity")) { |
| setStrokeOpacity(tokens[1]); |
| |
| } else if(tokens[0].equals("opacity")) { |
| setOpacity(tokens[1]); |
| |
| } else { |
| // Other attributes are not yet implemented |
| } |
| } |
| } |
| } |
| |
| void setOpacity(String opacityText) { |
| opacity = PApplet.parseFloat(opacityText); |
| strokeColor = ((int) (opacity * 255)) << 24 | strokeColor & 0xFFFFFF; |
| fillColor = ((int) (opacity * 255)) << 24 | fillColor & 0xFFFFFF; |
| } |
| |
| void setStrokeWeight(String lineweight) { |
| strokeWeight = PApplet.parseFloat(lineweight); |
| } |
| |
| void setStrokeOpacity(String opacityText) { |
| strokeOpacity = PApplet.parseFloat(opacityText); |
| strokeColor = ((int) (strokeOpacity * 255)) << 24 | strokeColor & 0xFFFFFF; |
| } |
| |
| void setStroke(String strokeText) { |
| int opacityMask = strokeColor & 0xFF000000; |
| if (strokeText.equals("none")) { |
| stroke = false; |
| } else if (strokeText.startsWith("#")) { |
| stroke = true; |
| strokeColor = opacityMask | |
| (Integer.parseInt(strokeText.substring(1), 16)) & 0xFFFFFF; |
| } else if (strokeText.startsWith("rgb")) { |
| stroke = true; |
| strokeColor = opacityMask | parseRGB(strokeText); |
| } else if (strokeText.startsWith("url(#")) { |
| strokeName = strokeText.substring(5, strokeText.length() - 1); |
| Object strokeObject = findChild(strokeName); |
| if (strokeObject instanceof Gradient) { |
| strokeGradient = (Gradient) strokeObject; |
| strokeGradientPaint = calcGradientPaint(strokeGradient); //, opacity); |
| } else { |
| System.err.println("url " + strokeName + " refers to unexpected data"); |
| } |
| } |
| } |
| |
| void setStrokeJoin(String linejoin) { |
| if (linejoin.equals("inherit")) { |
| // do nothing, will inherit automatically |
| |
| } else if (linejoin.equals("miter")) { |
| strokeJoin = PConstants.MITER; |
| |
| } else if (linejoin.equals("round")) { |
| strokeJoin = PConstants.ROUND; |
| |
| } else if (linejoin.equals("bevel")) { |
| strokeJoin = PConstants.BEVEL; |
| } |
| } |
| |
| void setStrokeCap(String linecap) { |
| if (linecap.equals("inherit")) { |
| // do nothing, will inherit automatically |
| |
| } else if (linecap.equals("butt")) { |
| strokeCap = PConstants.SQUARE; |
| |
| } else if (linecap.equals("round")) { |
| strokeCap = PConstants.ROUND; |
| |
| } else if (linecap.equals("square")) { |
| strokeCap = PConstants.PROJECT; |
| } |
| } |
| |
| void setFillOpacity(String opacityText) { |
| fillOpacity = PApplet.parseFloat(opacityText); |
| fillColor = ((int) (fillOpacity * 255)) << 24 | fillColor & 0xFFFFFF; |
| } |
| |
| void setFill(String fillText) { |
| int opacityMask = fillColor & 0xFF000000; |
| if (fillText.equals("none")) { |
| fill = false; |
| } else if (fillText.startsWith("#")) { |
| fill = true; |
| fillColor = opacityMask | |
| (Integer.parseInt(fillText.substring(1), 16)) & 0xFFFFFF; |
| //System.out.println("hex for fill is " + PApplet.hex(fillColor)); |
| } else if (fillText.startsWith("rgb")) { |
| fill = true; |
| fillColor = opacityMask | parseRGB(fillText); |
| } else if (fillText.startsWith("url(#")) { |
| fillName = fillText.substring(5, fillText.length() - 1); |
| //PApplet.println("looking for " + fillName); |
| Object fillObject = findChild(fillName); |
| //PApplet.println("found " + fillObject); |
| if (fillObject instanceof Gradient) { |
| fill = true; |
| fillGradient = (Gradient) fillObject; |
| fillGradientPaint = calcGradientPaint(fillGradient); //, opacity); |
| //PApplet.println("got filla " + fillObject); |
| } else { |
| System.err.println("url " + fillName + " refers to unexpected data"); |
| } |
| } |
| } |
| |
| |
| static protected int parseRGB(String what) { |
| int leftParen = what.indexOf('(') + 1; |
| int rightParen = what.indexOf(')'); |
| String sub = what.substring(leftParen, rightParen); |
| int[] values = PApplet.parseInt(PApplet.splitTokens(sub, ", ")); |
| return (values[0] << 16) | (values[1] << 8) | (values[2]); |
| } |
| |
| |
| static protected HashMap<String, String> parseStyleAttributes(String style) { |
| HashMap<String, String> table = new HashMap<String, String>(); |
| String[] pieces = style.split(";"); |
| for (int i = 0; i < pieces.length; i++) { |
| String[] parts = pieces[i].split(":"); |
| table.put(parts[0], parts[1]); |
| } |
| return table; |
| } |
| |
| |
| /** |
| * Parse a size that may have a suffix for its units. |
| * Ignoring cases where this could also be a percentage. |
| * The <A HREF="http://www.w3.org/TR/SVG/coords.html#Units">units</A> spec: |
| * <UL> |
| * <LI>"1pt" equals "1.25px" (and therefore 1.25 user units) |
| * <LI>"1pc" equals "15px" (and therefore 15 user units) |
| * <LI>"1mm" would be "3.543307px" (3.543307 user units) |
| * <LI>"1cm" equals "35.43307px" (and therefore 35.43307 user units) |
| * <LI>"1in" equals "90px" (and therefore 90 user units) |
| * </UL> |
| */ |
| static protected float parseUnitSize(String text) { |
| int len = text.length() - 2; |
| |
| if (text.endsWith("pt")) { |
| return PApplet.parseFloat(text.substring(0, len)) * 1.25f; |
| } else if (text.endsWith("pc")) { |
| return PApplet.parseFloat(text.substring(0, len)) * 15; |
| } else if (text.endsWith("mm")) { |
| return PApplet.parseFloat(text.substring(0, len)) * 3.543307f; |
| } else if (text.endsWith("cm")) { |
| return PApplet.parseFloat(text.substring(0, len)) * 35.43307f; |
| } else if (text.endsWith("in")) { |
| return PApplet.parseFloat(text.substring(0, len)) * 90; |
| } else if (text.endsWith("px")) { |
| return PApplet.parseFloat(text.substring(0, len)); |
| } else { |
| return PApplet.parseFloat(text); |
| } |
| } |
| |
| |
| // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . |
| |
| |
| static class Gradient extends PShapeSVG { |
| AffineTransform transform; |
| |
| float[] offset; |
| int[] color; |
| int count; |
| |
| public Gradient(PShapeSVG parent, XMLElement properties) { |
| super(parent, properties); |
| |
| XMLElement elements[] = properties.getChildren(); |
| offset = new float[elements.length]; |
| color = new int[elements.length]; |
| |
| // <stop offset="0" style="stop-color:#967348"/> |
| for (int i = 0; i < elements.length; i++) { |
| XMLElement elem = elements[i]; |
| String name = elem.getName(); |
| if (name.equals("stop")) { |
| offset[count] = elem.getFloatAttribute("offset"); |
| String style = elem.getStringAttribute("style"); |
| HashMap<String, String> styles = parseStyleAttributes(style); |
| |
| String colorStr = styles.get("stop-color"); |
| if (colorStr == null) colorStr = "#000000"; |
| String opacityStr = styles.get("stop-opacity"); |
| if (opacityStr == null) opacityStr = "1"; |
| int tupacity = (int) (PApplet.parseFloat(opacityStr) * 255); |
| color[count] = (tupacity << 24) | |
| Integer.parseInt(colorStr.substring(1), 16); |
| count++; |
| } |
| } |
| offset = PApplet.subset(offset, 0, count); |
| color = PApplet.subset(color, 0, count); |
| } |
| } |
| |
| |
| class LinearGradient extends Gradient { |
| float x1, y1, x2, y2; |
| |
| public LinearGradient(PShapeSVG parent, XMLElement properties) { |
| super(parent, properties); |
| |
| this.x1 = properties.getFloatAttribute("x1"); |
| this.y1 = properties.getFloatAttribute("y1"); |
| this.x2 = properties.getFloatAttribute("x2"); |
| this.y2 = properties.getFloatAttribute("y2"); |
| |
| String transformStr = |
| properties.getStringAttribute("gradientTransform"); |
| |
| if (transformStr != null) { |
| float t[] = parseMatrix(transformStr).get(null); |
| this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); |
| |
| Point2D t1 = transform.transform(new Point2D.Float(x1, y1), null); |
| Point2D t2 = transform.transform(new Point2D.Float(x2, y2), null); |
| |
| this.x1 = (float) t1.getX(); |
| this.y1 = (float) t1.getY(); |
| this.x2 = (float) t2.getX(); |
| this.y2 = (float) t2.getY(); |
| } |
| } |
| } |
| |
| |
| class RadialGradient extends Gradient { |
| float cx, cy, r; |
| |
| public RadialGradient(PShapeSVG parent, XMLElement properties) { |
| super(parent, properties); |
| |
| this.cx = properties.getFloatAttribute("cx"); |
| this.cy = properties.getFloatAttribute("cy"); |
| this.r = properties.getFloatAttribute("r"); |
| |
| String transformStr = |
| properties.getStringAttribute("gradientTransform"); |
| |
| if (transformStr != null) { |
| float t[] = parseMatrix(transformStr).get(null); |
| this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); |
| |
| Point2D t1 = transform.transform(new Point2D.Float(cx, cy), null); |
| Point2D t2 = transform.transform(new Point2D.Float(cx + r, cy), null); |
| |
| this.cx = (float) t1.getX(); |
| this.cy = (float) t1.getY(); |
| this.r = (float) (t2.getX() - t1.getX()); |
| } |
| } |
| } |
| |
| |
| |
| class LinearGradientPaint implements Paint { |
| float x1, y1, x2, y2; |
| float[] offset; |
| int[] color; |
| int count; |
| float opacity; |
| |
| public LinearGradientPaint(float x1, float y1, float x2, float y2, |
| float[] offset, int[] color, int count, |
| float opacity) { |
| this.x1 = x1; |
| this.y1 = y1; |
| this.x2 = x2; |
| this.y2 = y2; |
| this.offset = offset; |
| this.color = color; |
| this.count = count; |
| this.opacity = opacity; |
| } |
| |
| public PaintContext createContext(ColorModel cm, |
| Rectangle deviceBounds, Rectangle2D userBounds, |
| AffineTransform xform, RenderingHints hints) { |
| Point2D t1 = xform.transform(new Point2D.Float(x1, y1), null); |
| Point2D t2 = xform.transform(new Point2D.Float(x2, y2), null); |
| return new LinearGradientContext((float) t1.getX(), (float) t1.getY(), |
| (float) t2.getX(), (float) t2.getY()); |
| } |
| |
| public int getTransparency() { |
| return TRANSLUCENT; // why not.. rather than checking each color |
| } |
| |
| public class LinearGradientContext implements PaintContext { |
| int ACCURACY = 2; |
| float tx1, ty1, tx2, ty2; |
| |
| public LinearGradientContext(float tx1, float ty1, float tx2, float ty2) { |
| this.tx1 = tx1; |
| this.ty1 = ty1; |
| this.tx2 = tx2; |
| this.ty2 = ty2; |
| } |
| |
| public void dispose() { } |
| |
| public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } |
| |
| public Raster getRaster(int x, int y, int w, int h) { |
| WritableRaster raster = |
| getColorModel().createCompatibleWritableRaster(w, h); |
| |
| int[] data = new int[w * h * 4]; |
| |
| // make normalized version of base vector |
| float nx = tx2 - tx1; |
| float ny = ty2 - ty1; |
| float len = (float) Math.sqrt(nx*nx + ny*ny); |
| if (len != 0) { |
| nx /= len; |
| ny /= len; |
| } |
| |
| int span = (int) PApplet.dist(tx1, ty1, tx2, ty2) * ACCURACY; |
| if (span <= 0) { |
| //System.err.println("span is too small"); |
| // annoying edge case where the gradient isn't legit |
| int index = 0; |
| for (int j = 0; j < h; j++) { |
| for (int i = 0; i < w; i++) { |
| data[index++] = 0; |
| data[index++] = 0; |
| data[index++] = 0; |
| data[index++] = 255; |
| } |
| } |
| |
| } else { |
| int[][] interp = new int[span][4]; |
| int prev = 0; |
| for (int i = 1; i < count; i++) { |
| int c0 = color[i-1]; |
| int c1 = color[i]; |
| int last = (int) (offset[i] * (span-1)); |
| //System.out.println("last is " + last); |
| for (int j = prev; j <= last; j++) { |
| float btwn = PApplet.norm(j, prev, last); |
| interp[j][0] = (int) PApplet.lerp((c0 >> 16) & 0xff, (c1 >> 16) & 0xff, btwn); |
| interp[j][1] = (int) PApplet.lerp((c0 >> 8) & 0xff, (c1 >> 8) & 0xff, btwn); |
| interp[j][2] = (int) PApplet.lerp(c0 & 0xff, c1 & 0xff, btwn); |
| interp[j][3] = (int) (PApplet.lerp((c0 >> 24) & 0xff, (c1 >> 24) & 0xff, btwn) * opacity); |
| //System.out.println(j + " " + interp[j][0] + " " + interp[j][1] + " " + interp[j][2]); |
| } |
| prev = last; |
| } |
| |
| int index = 0; |
| for (int j = 0; j < h; j++) { |
| for (int i = 0; i < w; i++) { |
| //float distance = 0; //PApplet.dist(cx, cy, x + i, y + j); |
| //int which = PApplet.min((int) (distance * ACCURACY), interp.length-1); |
| float px = (x + i) - tx1; |
| float py = (y + j) - ty1; |
| // distance up the line is the dot product of the normalized |
| // vector of the gradient start/stop by the point being tested |
| int which = (int) ((px*nx + py*ny) * ACCURACY); |
| if (which < 0) which = 0; |
| if (which > interp.length-1) which = interp.length-1; |
| //if (which > 138) System.out.println("grabbing " + which); |
| |
| data[index++] = interp[which][0]; |
| data[index++] = interp[which][1]; |
| data[index++] = interp[which][2]; |
| data[index++] = interp[which][3]; |
| } |
| } |
| } |
| raster.setPixels(0, 0, w, h, data); |
| |
| return raster; |
| } |
| } |
| } |
| |
| |
| class RadialGradientPaint implements Paint { |
| float cx, cy, radius; |
| float[] offset; |
| int[] color; |
| int count; |
| float opacity; |
| |
| public RadialGradientPaint(float cx, float cy, float radius, |
| float[] offset, int[] color, int count, |
| float opacity) { |
| this.cx = cx; |
| this.cy = cy; |
| this.radius = radius; |
| this.offset = offset; |
| this.color = color; |
| this.count = count; |
| this.opacity = opacity; |
| } |
| |
| public PaintContext createContext(ColorModel cm, |
| Rectangle deviceBounds, Rectangle2D userBounds, |
| AffineTransform xform, RenderingHints hints) { |
| return new RadialGradientContext(); |
| } |
| |
| public int getTransparency() { |
| return TRANSLUCENT; |
| } |
| |
| public class RadialGradientContext implements PaintContext { |
| int ACCURACY = 5; |
| |
| public void dispose() {} |
| |
| public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } |
| |
| public Raster getRaster(int x, int y, int w, int h) { |
| WritableRaster raster = |
| getColorModel().createCompatibleWritableRaster(w, h); |
| |
| int span = (int) radius * ACCURACY; |
| int[][] interp = new int[span][4]; |
| int prev = 0; |
| for (int i = 1; i < count; i++) { |
| int c0 = color[i-1]; |
| int c1 = color[i]; |
| int last = (int) (offset[i] * (span - 1)); |
| for (int j = prev; j <= last; j++) { |
| float btwn = PApplet.norm(j, prev, last); |
| interp[j][0] = (int) PApplet.lerp((c0 >> 16) & 0xff, (c1 >> 16) & 0xff, btwn); |
| interp[j][1] = (int) PApplet.lerp((c0 >> 8) & 0xff, (c1 >> 8) & 0xff, btwn); |
| interp[j][2] = (int) PApplet.lerp(c0 & 0xff, c1 & 0xff, btwn); |
| interp[j][3] = (int) (PApplet.lerp((c0 >> 24) & 0xff, (c1 >> 24) & 0xff, btwn) * opacity); |
| } |
| prev = last; |
| } |
| |
| int[] data = new int[w * h * 4]; |
| int index = 0; |
| for (int j = 0; j < h; j++) { |
| for (int i = 0; i < w; i++) { |
| float distance = PApplet.dist(cx, cy, x + i, y + j); |
| int which = PApplet.min((int) (distance * ACCURACY), interp.length-1); |
| |
| data[index++] = interp[which][0]; |
| data[index++] = interp[which][1]; |
| data[index++] = interp[which][2]; |
| data[index++] = interp[which][3]; |
| } |
| } |
| raster.setPixels(0, 0, w, h, data); |
| |
| return raster; |
| } |
| } |
| } |
| |
| |
| protected Paint calcGradientPaint(Gradient gradient) { |
| if (gradient instanceof LinearGradient) { |
| LinearGradient grad = (LinearGradient) gradient; |
| return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2, |
| grad.offset, grad.color, grad.count, |
| opacity); |
| |
| } else if (gradient instanceof RadialGradient) { |
| RadialGradient grad = (RadialGradient) gradient; |
| return new RadialGradientPaint(grad.cx, grad.cy, grad.r, |
| grad.offset, grad.color, grad.count, |
| opacity); |
| } |
| return null; |
| } |
| |
| |
| // protected Paint calcGradientPaint(Gradient gradient, |
| // float x1, float y1, float x2, float y2) { |
| // if (gradient instanceof LinearGradient) { |
| // LinearGradient grad = (LinearGradient) gradient; |
| // return new LinearGradientPaint(x1, y1, x2, y2, |
| // grad.offset, grad.color, grad.count, |
| // opacity); |
| // } |
| // throw new RuntimeException("Not a linear gradient."); |
| // } |
| |
| |
| // protected Paint calcGradientPaint(Gradient gradient, |
| // float cx, float cy, float r) { |
| // if (gradient instanceof RadialGradient) { |
| // RadialGradient grad = (RadialGradient) gradient; |
| // return new RadialGradientPaint(cx, cy, r, |
| // grad.offset, grad.color, grad.count, |
| // opacity); |
| // } |
| // throw new RuntimeException("Not a radial gradient."); |
| // } |
| |
| |
| // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . |
| |
| |
| protected void styles(PGraphics g) { |
| super.styles(g); |
| |
| if (g instanceof PGraphicsJava2D) { |
| PGraphicsJava2D p2d = (PGraphicsJava2D) g; |
| |
| if (strokeGradient != null) { |
| p2d.strokeGradient = true; |
| p2d.strokeGradientObject = strokeGradientPaint; |
| } else { |
| // need to shut off, in case parent object has a gradient applied |
| //p2d.strokeGradient = false; |
| } |
| if (fillGradient != null) { |
| p2d.fillGradient = true; |
| p2d.fillGradientObject = fillGradientPaint; |
| } else { |
| // need to shut off, in case parent object has a gradient applied |
| //p2d.fillGradient = false; |
| } |
| } |
| } |
| |
| |
| // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . |
| |
| |
| //public void drawImpl(PGraphics g) { |
| // do nothing |
| //} |
| |
| |
| // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . |
| |
| |
| /** |
| * Get a particular element based on its SVG ID. When editing SVG by hand, |
| * this is the id="" tag on any SVG element. When editing from Illustrator, |
| * these IDs can be edited by expanding the layers palette. The names used |
| * in the layers palette, both for the layers or the shapes and groups |
| * beneath them can be used here. |
| * <PRE> |
| * // This code grabs "Layer 3" and the shapes beneath it. |
| * SVG layer3 = svg.getChild("Layer 3"); |
| * </PRE> |
| */ |
| public PShape getChild(String name) { |
| PShape found = super.getChild(name); |
| if (found == null) { |
| // Otherwise try with underscores instead of spaces |
| // (this is how Illustrator handles spaces in the layer names). |
| found = super.getChild(name.replace(' ', '_')); |
| } |
| // Set bounding box based on the parent bounding box |
| if (found != null) { |
| // found.x = this.x; |
| // found.y = this.y; |
| found.width = this.width; |
| found.height = this.height; |
| } |
| return found; |
| } |
| |
| |
| /** |
| * Prints out the SVG document. Useful for parsing. |
| */ |
| public void print() { |
| PApplet.println(element.toString()); |
| } |
| } |