// © Michiel Kamermans 2010 - CC 3.0 license (AT-NC) /** * Basic vector instruction. * There are four instructions: * * - move to (M) * - line to (L) * - curve to (C) * - join to start (Z) */ class VectorInstruction { int fx, fy, tx, ty; VectorInstruction(int fromx, int fromy, int tox, int toy) { fx=fromx; fy=fromy; tx=tox; ty=toy; } int getFromX() { return fx; } int getFromY() { return fy; } int getToX() { return tx; } int getToY() { return ty; } String toSVG() { return ""; } float[] getBoundingBox() { float[] bbox = {min(fx,tx), min(fy,ty), max(fx,tx), max(fy,ty)}; return bbox; } } /** * Absolute coordinate "move to" */ class MoveTo extends VectorInstruction { MoveTo(int fx, int fy, int tx, int ty) { super(fx, fy, tx, ty); } String toSVG() { return "M "+tx+" "+ty+" "; } } /** * Absolute coordinate "line to" */ class LineTo extends VectorInstruction { LineTo(int fx, int fy, int tx, int ty) { super(fx, fy, tx, ty); } String toSVG() { return "L "+tx+" "+ty+" "; } } /** * Absolute coordinate cubic bezier "curve to" */ class CurveTo extends VectorInstruction { int px, py, cx, cy; CurveTo(int fx, int fy, int px, int py, int cx, int cy, int tx, int ty) { super(fx, fy, tx, ty); this.px=px; this.py=py; this.cx=cx; this.cy=cy; } int getPX() { return px; } int getPY() { return py; } int getCX() { return cx; } int getCY() { return cy; } String toSVG() { return "C "+px+" "+py+" "+cx+" "+cy+" "+tx+" "+ty+" "; } float[] getBoundingBox() { return calculate_bbox(fx, fy, px, py, cx, cy, tx, ty); } float calculate_bezier(float t, float p0, float p1, float p2, float p3) { return (1-t) * (1-t) * (1-t) * p0 + 3 * (1-t) * (1-t) * t * p1 + 3 * (1-t) * t * t * p2 + t * t * t * p3; } float[] calculate_bbox(float p0x, float p0y, float p1x, float p1y, float p2x, float p2y, float p3x, float p3y) { // compute the linear bounding box float[] bounds = {min(p0x,p3x), min(p0y,p3y), max(p0x,p3x), max(p0y,p3y)}; // // The next bit is technical. See the comment on this topic on // http://newsgroups.derkeiler.com/Archive/Comp/comp.graphics.algorithms/2005-07/msg00334.html // and the mathematics at http://pomax.nihongoresources.com/downloads/bezierbounds.html // for an explanation of why the following code is being used, and (not unimportantly) why it works. // float dcx0 = p1x-p0x; float dcy0 = p1y-p0y; float dcx1 = p2x - p1x; float dcy1 = p2y - p1y; float dcx2 = p3x - p2x; float dcy2 = p3y - p2y; // Compute the min/max bound values for the x dimension, but only if the control // points lie outside the linear bounding box if(p1xbounds[2] || p2xbounds[2]) { // easier on the eyes float a = dcx0; float b = dcx1; float c = dcx2; // Do we have a problematic discriminator if we use these values? // If we do, because we're computing at sub-pixel level anyway, simply salt 'b' a tiny bit. if(a+c != 2*b) { b+=0.01; } float numerator = 2*(a - b); float denominator = 2*(a - 2*b + c); float doubleroot = (2*b-2*a)*(2*b-2*a) - 2*a*denominator; float root = sqrt(doubleroot); // there are two possible values for t that yield inflection points float t1 = (numerator + root) / denominator; float t2 = (numerator - root) / denominator; // so, which of these is the useful point? (t must lie in [0,1]) if(0<=t1 && t1<=1) { float inflectionpoint = calculate_bezier(t1, p0x, p1x, p2x, p3x); if(bounds[0] > inflectionpoint) { bounds[0] = inflectionpoint; } else if(bounds[2] < inflectionpoint) { bounds[2] = inflectionpoint; }} if(0<=t2 && t2<=1) { float inflectionpoint = calculate_bezier(t2, p0x, p1x, p2x, p3x); if(bounds[0] > inflectionpoint) { bounds[0] = inflectionpoint; } else if(bounds[2] < inflectionpoint) { bounds[2] = inflectionpoint; }} } // no comments in the code this time, because it's identical (save the dimension) to the x case if(p1ybounds[3] || p2ybounds[3]) { float a = dcy0; float b = dcy1; float c = dcy2; if(a+c != 2*b) { b+=0.01; } float numerator = 2*(a - b); float denominator = 2*(a - 2*b + c); float doubleroot = (2*b-2*a)*(2*b-2*a) - 2*a*denominator; float root = sqrt(doubleroot); float t1 = (numerator + root) / denominator; float t2 = (numerator - root) / denominator; if(0<=t1 && t1<=1) { float inflectionpoint = calculate_bezier(t1, p0y, p1y, p2y, p3y); if(bounds[1] > inflectionpoint) { bounds[1] = inflectionpoint; } else if(bounds[3] < inflectionpoint) { bounds[3] = inflectionpoint; }} if(0<=t2 && t2<=1) { float inflectionpoint = calculate_bezier(t2, p0y, p1y, p2y, p3y); if(bounds[1] > inflectionpoint) { bounds[1] = inflectionpoint; } else if(bounds[3] < inflectionpoint) { bounds[3] = inflectionpoint; }} } return bounds; } } /** * "join to start" instruction */ class ClosePath extends VectorInstruction { ClosePath() { super(0,0,0,0); } String toSVG() { return "Z"; } }