diff --git a/candy/src/processing/candy/SVG.java b/candy/src/processing/candy/SVG.java index d1bbe284b..cb41dd127 100755 --- a/candy/src/processing/candy/SVG.java +++ b/candy/src/processing/candy/SVG.java @@ -1,91 +1,134 @@ -package processing.svg.reader; - -//import java.awt.Color; -//import java.awt.GradientPaint; -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.Hashtable; - -import processing.core.PApplet; -import processing.core.PConstants; -import processing.core.PGraphicsJava2D; -import processing.xml.XMLElement; - -//CANDY -//Processing SVG import for the common man -//author: Flux -//with contributions by: Christian Riekoff -//email: flux.blackcat@gmail.com -//http://www.ghost-hack.com -//Built for Processing 118 - -//Using Christian Riekoff's handy proXML parser -//http://www.texone.org/proxml -//Hawt. - -//Last 100% working for Processing 0118 -//Please send bugs and annoyances to above email -//Refer to readme.txt that was part of this package for legal jargon - /* -TODO -X actual linear gradients working properly -X add a table for all objects with their names, so they can be grabbed individually -_ add accessor to get items from the table -_ see if items can be named in illusfarter using the svg palette -X compound shapes (fonts) won't wind properly, so fill will be messed up -X added hack to allow for broken shapes -X rename draw() and its buddy -X a moveto *inside* a shape will be treated as a lineto -X had to fix this -X implement polyline -_ try enabling blending modes -_ test what happens when transparency is used with gradient fill -_ some means of centering the entire drawing (is this included already?) -_ or setting to one of the corners -_ does the svg spec just do this? -_ look into transformation issues... guessing this is probably wrong -_ this may be what's throwing off the radial radius transform -_ implement A and a -_ check for any other pieces of missing path api -_ multiple sets of coordinates after a command not supported -_ i.e. M with several coords means moveto followed by many linetos -_ also curveto with multiple sets of points is ignored + Candy 2 - SVG Importer for Processing - http://processing.org + + Copyright (c) 2006 Michael Chang (Flux) + http://www.ghost-hack.com/ + + Revised and expanded by Ben Fry for inclusion as a core library + Copyright (c) 2006- Ben Fry and Casey Reas + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. */ +package processing.candy; +import java.awt.*; +import java.awt.geom.*; +import java.awt.image.*; +import java.util.Hashtable; + +import processing.core.*; +import processing.xml.*; + + +/** + * Candy is a minimal SVG import library for Processing. + * Candy was written by Michael Chang, and later revised and + * expanded for use as a Processing core library by Ben Fry. + *

+ * SVG stands for Scalar Vector Graphics, a portable graphics + * format. It is a vector format so it allows for infinite resolution + * and relatively minute file sizes. Most modern media software + * can view SVG files, including Firefox, Adobe products, etc. + * You can use something like Illustrator to edit SVG files. + *

+ * This library was specifically tested under SVG files created from + * Adobe Illustrator. I can't guarantee that it'll work for any + * SVG's created from anything else. In the future I will also + * test with open source graphics editing software so we'll reach + * maximal compatibility. In the mean time, you're on your own. + *

+ * An SVG created under Illustrator must be created in the + * following manner:
+ * File → Save for Web (or control-alt-shift-s on a PC) + * Under settings, make sure the CSS properties is set to + * "Presentation Attributes". With Illustrator CS2, it is also + * possible to use "Save As" with "SVG" as the file setting, + * but the CSS properties should also be set similarly. + *

+ * Saving it any other way will most likely break Candy. + * + *


+ * + * A minimal example program using Candy: + * (assuming a working moo.svg is in your data folder) + * + * + * import processing.candy.*; + * import processing.xml.*; + * + * SVG moo; + * void setup(){ + * size(400,400); + * moo = new SVG("moo.svg",this); + * } + * void draw(){ + * moo.draw(); + * } + * + * + * Note that processing.xml is imported as well. This is not needed + * when running the app directly from Processing, as Candy will know + * where it is. However when you export as an applet you will + * also need to export processing.xml along with it to have working Candy. + * + *


+ * + * Revisions for "Candy 2" November 2006 by fry + *

+ * + * Revision 10/31/06 by flux + * + * + * Some SVG objects and features may not yet be supported. + * Here is a partial list of non-included features + * + * + * If you experience any other wierdness or bugs, please file them to + * flux.blackcat@gmail.com with subject: Delicious Candy + */ public class SVG { - public String filename; - - //protected static PApplet parent; - //private static XMLInOut xmlio; protected PApplet parent; public float width; - public float height; - //private float svgWidth = 0; - //private float svgHeight = 0; + public float height; - //private Vector draw = new Vector(); + protected Hashtable table = new Hashtable(); + protected XMLElement svg; + protected BaseObject root; - private Hashtable idTable = new Hashtable(); - private XMLElement svg; - private Group root; - - //For some reason Processing was using -1 for #FFFFFFFF - //thus we will use our own null value - //private static int transValue = -255; - - private boolean styleOverride = false; + protected boolean styleOverride = false; + /** * Initializes a new SVG Object with the given filename. @@ -94,7 +137,7 @@ public class SVG { */ public SVG(String filename, PApplet parent){ this.parent = parent; - this.filename = filename; + //this.filename = filename; // this will grab the root document, starting // the xml version and initial comments are ignored @@ -119,8 +162,8 @@ public class SVG { throw new RuntimeException("root isn't svg, it's " + svg.getName()); } - width = svg.getFloatAttribute("width"); - height = svg.getFloatAttribute("height"); + width = parseUnitSize(svg.getStringAttribute("width")); + height = parseUnitSize(svg.getStringAttribute("height")); /* PApplet.println("document has " + document.getChildCount() + " children"); @@ -159,11 +202,77 @@ public class SVG { //svg.printElementTree(" ."); */ } + + + public SVG(PApplet parent, float width, float height, Hashtable table, + BaseObject obj, boolean styleOverride) { + this.parent = parent; + this.width = width; + this.height = height; + this.table = table; + this.root = obj; + this.svg = obj.element; + this.styleOverride = styleOverride; + } + /** + * Parse a size that may have a suffix for its units. + * Ignoring cases where this could also be a percentage. + * + * http://www.w3.org/TR/SVG/coords.html#Units + * "1pt" equals "1.25px" (and therefore 1.25 user units) + * "1pc" equals "15px" (and therefore 15 user units) + * "1mm" would be "3.543307px" (3.543307 user units) + * "1cm" equals "35.43307px" (and therefore 35.43307 user units) + * "1in" equals "90px" (and therefore 90 user units) + * + */ + public 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)); + } else { + return PApplet.parseFloat(text); + } + } + + + /** + * 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. + * + * // This code grabs "Layer 3" and the shapes beneath it. + * SVG layer3 = svg.getElement("Layer 3"); + * + */ + public SVG getElement(String name) { + BaseObject obj = (BaseObject) table.get(name); + if (obj == null) { + // try with underscores instead of spaces + obj = (BaseObject) table.get(name.replace(' ', '_')); + } + if (obj != null) { + return new SVG(parent, width, height, table, obj, styleOverride); + } + return null; + } + + /** * Draws the SVG document. - * */ public void draw(){ //Maybe it would be smart to save all changes that have @@ -203,31 +312,25 @@ public class SVG { /** - * Prints out the SVG document usefull for parsing + * Prints out the SVG document useful for parsing */ - public void print(){ + public void print() { PApplet.println(svg.toString()); } - /* - //Converts a string to a float - private float valueOf(String s){ - return Float.valueOf(s).floatValue(); - } - */ - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . protected abstract class BaseObject { String id; + XMLElement element; public BaseObject(XMLElement properties) { + element = properties; id = properties.getStringAttribute("id"); if (id != null) { - idTable.put(id, this); + table.put(id, this); //System.out.println("now parsing " + id); } } @@ -335,10 +438,10 @@ public class SVG { (Integer.parseInt(strokeText.substring(1), 16)) & 0xFFFFFF; } else if (strokeText.startsWith("url(#")) { strokeName = strokeText.substring(5, strokeText.length() - 1); - Object strokeObject = idTable.get(strokeName); + Object strokeObject = table.get(strokeName); if (strokeObject instanceof Gradient) { strokeGradient = (Gradient) strokeObject; - strokeGradientPaint = calcGradientPaint(strokeGradient); + strokeGradientPaint = calcGradientPaint(strokeGradient); //, opacity); } else { System.err.println("url " + strokeName + " refers to unexpected data"); } @@ -357,12 +460,12 @@ public class SVG { } else if (fillText.startsWith("url(#")) { fillName = fillText.substring(5, fillText.length() - 1); //PApplet.println("looking for " + fillName); - Object fillObject = idTable.get(fillName); + Object fillObject = table.get(fillName); //PApplet.println("found " + fillObject); if (fillObject instanceof Gradient) { fill = true; fillGradient = (Gradient) fillObject; - fillGradientPaint = calcGradientPaint(fillGradient); + fillGradientPaint = calcGradientPaint(fillGradient); //, opacity); //PApplet.println("got filla " + fillObject); } else { System.err.println("url " + fillName + " refers to unexpected data"); @@ -376,7 +479,7 @@ public class SVG { } - protected Paint calcGradientPaint(Gradient gradient) { + protected Paint calcGradientPaint(Gradient gradient) { //, float opacity) { if (gradient instanceof LinearGradient) { LinearGradient grad = (LinearGradient) gradient; @@ -387,7 +490,8 @@ public class SVG { grad.x2, grad.y2, c2); */ return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2, - grad.offset, grad.color, grad.count); + grad.offset, grad.color, grad.count, + opacity); } else if (gradient instanceof RadialGradient) { @@ -396,7 +500,8 @@ public class SVG { //Color c1 = new Color(0xFF000000 | grad.color[0]); //Color c2 = new Color(0xFF000000 | grad.color[grad.count-1]); return new RadialGradientPaint(grad.cx, grad.cy, grad.r, - grad.offset, grad.color, grad.count); + grad.offset, grad.color, grad.count, + opacity); } return null; } @@ -484,15 +589,18 @@ public class SVG { float[] offset; int[] color; int count; + float opacity; public RadialGradientPaint(float cx, float cy, float radius, - float[] offset, int[] color, int count) { + 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, @@ -506,7 +614,7 @@ public class SVG { return new RadialGradientContext((float) transformedPoint.getX(), (float) transformedPoint.getY(), radius, //(float) transformedRadius.distance(0, 0), - offset, color, count); + offset, color, count, opacity); } public int getTransparency() { @@ -515,7 +623,8 @@ public class SVG { int a2 = mBackgroundColor.getAlpha(); return (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT); */ - return OPAQUE; + //return (opacity == 1) ? OPAQUE : TRANSLUCENT; + return TRANSLUCENT; // why not.. rather than checking each color } } @@ -525,15 +634,18 @@ public class SVG { float[] offset; int[] color; int count; + float opacity; public RadialGradientContext(float cx, float cy, float radius, - float[] offset, int[] color, int count) { + 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 void dispose() {} @@ -547,8 +659,9 @@ public class SVG { getColorModel().createCompatibleWritableRaster(w, h); //System.out.println("radius here is " + radius); + //System.out.println("count is " + count); int span = (int) radius * ACCURACY; - int[][] interp = new int[span][3]; + int[][] interp = new int[span][4]; int prev = 0; for (int i = 1; i < count; i++) { int c0 = color[i-1]; @@ -559,6 +672,8 @@ public class SVG { 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(interp[j][3]); } prev = last; } @@ -573,7 +688,7 @@ public class SVG { data[index++] = interp[which][0]; data[index++] = interp[which][1]; data[index++] = interp[which][2]; - data[index++] = 255; + data[index++] = interp[which][3]; } } raster.setPixels(0, 0, w, h, data); @@ -588,9 +703,11 @@ public class SVG { 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[] offset, int[] color, int count, + float opacity) { this.x1 = x1; this.y1 = y1; this.x2 = x2; @@ -598,6 +715,7 @@ public class SVG { this.offset = offset; this.color = color; this.count = count; + this.opacity = opacity; } public PaintContext createContext(ColorModel cm, @@ -607,7 +725,7 @@ public class SVG { 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(), - offset, color, count); + offset, color, count, opacity); } public int getTransparency() { @@ -616,7 +734,8 @@ public class SVG { int a2 = mBackgroundColor.getAlpha(); return (((a1 & a2) == 0xff) ? OPAQUE : TRANSLUCENT); */ - return OPAQUE; + //return OPAQUE; + return TRANSLUCENT; // why not.. rather than checking each color } } @@ -626,9 +745,11 @@ public class SVG { float[] offset; int[] color; int count; + float opacity; public LinearGradientContext(float x1, float y1, float x2, float y2, - float[] offset, int[] color, int count) { + float[] offset, int[] color, int count, + float opacity) { this.x1 = x1; this.y1 = y1; this.x2 = x2; @@ -636,13 +757,14 @@ public class SVG { this.offset = offset; this.color = color; this.count = count; + this.opacity = opacity; } public void dispose() { } public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } - int ACCURACY = 1; + int ACCURACY = 2; public Raster getRaster(int x, int y, int w, int h) { WritableRaster raster = @@ -658,7 +780,7 @@ public class SVG { } int span = (int) PApplet.dist(x1, y1, x2, y2) * ACCURACY; - int[][] interp = new int[span][3]; + int[][] interp = new int[span][4]; int prev = 0; for (int i = 1; i < count; i++) { int c0 = color[i-1]; @@ -670,6 +792,7 @@ public class SVG { 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; @@ -693,7 +816,7 @@ public class SVG { data[index++] = interp[which][0]; data[index++] = interp[which][1]; data[index++] = interp[which][2]; - data[index++] = 255; + data[index++] = interp[which][3]; } } raster.setPixels(0, 0, w, h, data); @@ -719,37 +842,42 @@ public class SVG { for (int i = 0; i < elements.length; i++){ String name = elements[i].getName(); //getElement(); - XMLElement element = elements[i]; + XMLElement elem = elements[i]; if (name.equals("g")) { - objects[objectCount++] = new Group(element); + objects[objectCount++] = new Group(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 + objects[objectCount++] = new Group(elem); } else if (name.equals("line")) { - objects[objectCount++] = new Line(element); + objects[objectCount++] = new Line(elem); } else if (name.equals("circle")) { - objects[objectCount++] = new Circle(element); + objects[objectCount++] = new Circle(elem); } else if (name.equals("ellipse")) { - objects[objectCount++] = new Ellipse(element); + objects[objectCount++] = new Ellipse(elem); } else if (name.equals("rect")) { - objects[objectCount++] = new Rect(element); + objects[objectCount++] = new Rect(elem); } else if (name.equals("polygon")) { - objects[objectCount++] = new Poly(element, true); + objects[objectCount++] = new Poly(elem, true); } else if (name.equals("polyline")) { - objects[objectCount++] = new Poly(element, false); + objects[objectCount++] = new Poly(elem, false); } else if (name.equals("path")) { - objects[objectCount++] = new Path(element); + objects[objectCount++] = new Path(elem); } else if (name.equals("radialGradient")) { - objects[objectCount++] = new RadialGradient(element); + objects[objectCount++] = new RadialGradient(elem); } else if (name.equals("linearGradient")) { - objects[objectCount++] = new LinearGradient(element); + objects[objectCount++] = new LinearGradient(elem); } else { PApplet.println("not handled " + name); @@ -783,11 +911,23 @@ public class SVG { // for (int i = 0; i < elements.length; i++){ - XMLElement element = elements[i]; - String name = element.getName(); + XMLElement elem = elements[i]; + String name = elem.getName(); if (name.equals("stop")) { - offset[count] = element.getFloatAttribute("offset"); - String farbe = element.getStringAttribute("style"); + offset[count] = elem.getFloatAttribute("offset"); + String style = elem.getStringAttribute("style"); + Hashtable styles = parseStyleAttributes(style); + + String colorStr = (String) styles.get("stop-color"); + if (colorStr == null) colorStr = "#000000"; + String opacityStr = (String) 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++; + //System.out.println("this color is " + PApplet.hex(color[count])); + /* int idx = farbe.indexOf("#"); if (idx != -1) { color[count] = Integer.parseInt(farbe.substring(idx+1), 16); @@ -795,6 +935,7 @@ public class SVG { } else { System.err.println("problem with gradient stop " + properties); } + */ } } } @@ -802,6 +943,16 @@ public class SVG { abstract protected void drawShape(); } + static protected Hashtable parseStyleAttributes(String style) { + Hashtable table = new Hashtable(); + 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; + } + private class LinearGradient extends Gradient { float x1, y1, x2, y2;