X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-core%2Ftags%2Fstable%2Fsrc%2Fsynfig%2Flayer_shape.cpp;fp=synfig-core%2Ftags%2Fstable%2Fsrc%2Fsynfig%2Flayer_shape.cpp;h=2116d2f4e1229113ba6a59d3be3b5872837df34c;hb=391f6a3be479646721d8cc78b5ee673db17bd540;hp=0000000000000000000000000000000000000000;hpb=ce9ddb9d0e877117ef8648f901f0c7db9eca3d79;p=synfig.git diff --git a/synfig-core/tags/stable/src/synfig/layer_shape.cpp b/synfig-core/tags/stable/src/synfig/layer_shape.cpp new file mode 100644 index 0000000..2116d2f --- /dev/null +++ b/synfig-core/tags/stable/src/synfig/layer_shape.cpp @@ -0,0 +1,3108 @@ +/* === S Y N F I G ========================================================= */ +/*! \file layer_shape.cpp +** \brief Implementation of the "Shape" layer +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2007, 2008 Chris Moore +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === H E A D E R S ======================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "layer_shape.h" +#include "string.h" +#include "time.h" +#include "context.h" +#include "paramdesc.h" +#include "renddesc.h" +#include "surface.h" +#include "value.h" +#include "valuenode.h" +#include "float.h" +#include "blur.h" + +#include "curve_helper.h" + +#include + +#include + +#endif + +/* === U S I N G =========================================================== */ + +using namespace synfig; +using namespace std; +using namespace etl; + +/* === G L O B A L S ======================================================= */ + +SYNFIG_LAYER_INIT(Layer_Shape); +SYNFIG_LAYER_SET_NAME(Layer_Shape,"shape"); +SYNFIG_LAYER_SET_LOCAL_NAME(Layer_Shape,N_("Shape")); +SYNFIG_LAYER_SET_CATEGORY(Layer_Shape,N_("Internal")); +SYNFIG_LAYER_SET_VERSION(Layer_Shape,"0.1"); +SYNFIG_LAYER_SET_CVS_ID(Layer_Shape,"$Id$"); + +#define EPSILON 1e-12 + +template < class T > +inline bool IsZero(const T &n) +{ + return (n < EPSILON) && (n > -EPSILON); +} + +/* === C L A S S E S ======================================================= */ + +//Assumes 64 byte aligned structures if at all +struct Primitive +{ + int operation; + int number; + + //Point data[0]; + + enum Operations + { + NONE = -1, + MOVE_TO = 0, //(x,y)+ after first point treated as line_to + CLOSE, // NOT RUNLENGTH enabled + LINE_TO, //(x,y)+ continuous func + CONIC_TO, //(x1,y1,x,y)+ " " + CONIC_TO_SMOOTH, //(x,y)+ " " + CUBIC_TO, //(x1,y1,x2,y2,x,y)+ " " + CUBIC_TO_SMOOTH, //(x2,y2,x,y)+ " " + END + }; +}; + +//******** CURVE FUNCTIONS ***************** +const int MAX_SUBDIVISION_SIZE = 64; +const int MIN_SUBDIVISION_DRAW_LEVELS = 4; + +static void Subd_Conic_Stack(Point *arc) +{ + /* + + b0 + * 0+1 a + b1 b * 1+2*1+2 a + * 1+2 b * + b2 * + * + + 0.1.2 -> 0.1 2 3.4 + + */ + + Real a,b; + + + arc[4][0] = arc[2][0]; + b = arc[1][0]; + + a = arc[1][0] = (arc[0][0] + b)/2; + b = arc[3][0] = (arc[4][0] + b)/2; + arc[2][0] = (a + b)/2; + + + arc[4][1] = arc[2][1]; + b = arc[1][1]; + + a = arc[1][1] = (arc[0][1] + b)/2; + b = arc[3][1] = (arc[4][1] + b)/2; + arc[2][1] = (a + b)/2; + + /* //USING SIMD + + arc[4] = arc[2]; + + arc[3] = (arc[2] + arc[1])/2; + arc[1] = (arc[0] + arc[1])/2; + + arc[2] = (arc[1] + arc[3])/2; + + */ + +} + +static void Subd_Cubic_Stack(Point *arc) +{ + Real a,b,c; + + /* + + b0 + * 0+1 a + b1 b * 1+2*1+2 a + * 1+2 b * 0+3*1+3*2+3 + b2 c * 1+2*2+2 b * + * 2+3 c * + b3 * + * + + 0.1 2.3 -> 0.1 2 3 4 5.6 + + */ + + arc[6][0] = arc[3][0]; + + b = arc[1][0]; + c = arc[2][0]; + + a = arc[1][0] = (arc[0][0] + b)/2; + b = (b + c)/2; + c = arc[5][0] = (arc[6][0] + c)/2; + + a = arc[2][0] = (a + b)/2; + b = arc[4][0] = (b + c)/2; + + arc[3][0] = (a + b)/2; + + + arc[6][1] = arc[3][1]; + + b = arc[1][1]; + c = arc[2][1]; + + a = arc[1][1] = (arc[0][1] + b)/2; + b = (b + c)/2; + c = arc[5][1] = (arc[6][1] + c)/2; + + a = arc[2][1] = (a + b)/2; + b = arc[4][1] = (b + c)/2; + + arc[3][1] = (a + b)/2; + + /* //USING SIMD + temp + + arc[6] = arc[3]; + + //backwards to avoid overwriting + arc[5] = (arc[2] + arc[3])/2; + temp = (arc[1] + arc[2])/2; + arc[1] = (arc[0] + arc[1])/2; + + arc[4] = (temp + arc[5])/2; + arc[2] = (arc[1] + temp)/2; + + arc[3] = (arc[2] + arc[4])/2; + + */ +} + +//************** PARAMETRIC RENDERER SUPPORT STRUCTURES **************** + +// super segment +struct MonoSegment +{ + Rect aabb; + int ydir; + vector pointlist; + + MonoSegment(int dir = 0, Real x0 = 0, Real x1 = 0, Real y0 = 0, Real y1 = 0) + { + aabb.minx = x0; + aabb.maxx = x1; + aabb.miny = y0; + aabb.maxy = y1; + + ydir = dir; + } + + int intersect(Real x,Real y) const + { + if((y < aabb.miny+EPSILON) || (y > aabb.maxy) || (x < aabb.minx)) return 0; + if(x > aabb.maxx) return ydir; + + //int i = 0; + //int size = pointlist.size(); + //vector::const_iterator end = pointlist.end(); + vector::const_iterator p = pointlist.begin(); + + //assumes that the rect culled away anything that would be beyond the edges + if(ydir > 0) + { + while(y > (*++p)[1]) + ; + } + else + { + while(y < (*++p)[1]) + ; + } + + //for the loop to break there must have been a slope (straight line would do nothing) + //vector::const_iterator p1 = p-1; + Real dy = p[-1][1] - p[0][1]; + Real dx = p[-1][0] - p[0][0]; + + assert(dy != 0); + + Real xi = p[0][0] + (y - p[0][1]) * dx / dy; + return (x > xi)*ydir; + } +}; + +struct CurveArray +{ + Rect aabb; //not necessarily as effective - can only reject values + vector pointlist; //run length - p0, p1, p2, p3 = p10, p11, p12, p13 = p20 ... + vector degrees; + + CurveArray(Real x0 = 0, Real x1 = 0, Real y0 = 0, Real y1 = 0) + { + aabb.set(x0,y0,x1,y1); + } + + void reset(Real x0 = 0, Real x1 = 0, Real y0 = 0, Real y1 = 0) + { + aabb.set(x0,y0,x1,y1); + pointlist.clear(); + degrees.clear(); + } + + int size () const + { + return degrees.size(); + } + + void Start(Point m) + { + reset(m[0],m[0],m[1],m[1]); + pointlist.push_back(m); + } + + void AddCubic(Point p1, Point p2, Point dest) + { + aabb.expand(p1[0],p1[1]); + aabb.expand(p2[0],p2[1]); + aabb.expand(dest[0],dest[1]); + + pointlist.push_back(p1); + pointlist.push_back(p2); + pointlist.push_back(dest); + + degrees.push_back(3); + } + + void AddConic(Point p1, Point dest) + { + aabb.expand(p1[0],p1[1]); + aabb.expand(dest[0],dest[1]); + + pointlist.push_back(p1); + pointlist.push_back(dest); + + degrees.push_back(2); + } + + static int intersect_conic(Real x, Real y, Point *p, int /*level*/ = 0) + { + Real ymin,ymax,xmin,xmax; + int intersects = 0; + + //sort the overall curve ys - degenerate detection + ymin = min(p[0][1],p[2][1]); + ymax = max(p[0][1],p[2][1]); + + xmin = min(min(p[0][0],p[1][0]),p[2][0]); + xmax = max(max(p[0][0],p[1][0]),p[2][0]); + + //to the left, to the right and out of range y, or completely out of range y + if( x < xmin ) return 0; + if( x > xmax && (y > ymax || y < ymin) ) return 0; + if( (y > ymax && y > p[1][1]) || (y < ymin && y < p[1][1]) ) return 0; + + //degenerate line max + if(ymin == ymax && ymax == p[1][1]) + return 0; + + //degenerate accept - to the right and crossing the base line + if(x > xmax) + { + return (y <= ymax && y >= ymin); + } + + //solve for curve = y + + //real roots: + //0 roots - 0 intersection + //1 root - get x, and figure out x + //2 roots (non-double root) - get 2 xs, and count xs to the left + + //for conic we can assume 1 intersection for monotonic curve + Real a = p[2][1] - 2*p[1][1] + p[0][1], + b = 2*p[1][1] - 2*p[0][1], + c = p[0][1] - y; + + Real t1 = -1, t2 = -1; + + if(a == 0) + { + //linear - easier :) + if(b == 0) return 0; //may not need this check + + t1 = - c / b; //bt + c = 0 solved + }else + { + //2 degree polynomial + Real b2_4ac = b*b - 4*a*c; + + //if there are double/no roots - no intersections (in real #s that is) + if(b2_4ac <= 0) + { + return 0; + } + + b2_4ac = sqrt(b2_4ac); + + t1 = (-b - b2_4ac) / 2*a, + t2 = (-b + b2_4ac) / 2*a; + } + + //calculate number of intersections + if(t1 >= 0 && t1 <= 1) + { + const Real t = t1; + const Real invt = 1 - t; + + //find x val and it counts if it's to the left of the point + const Real xi = invt*invt*p[0][0] + 2*t*invt*p[1][0] + t*t*p[2][0]; + const Real dy_t = 2*a*t + b; + + if(dy_t) + { + intersects += (x >= xi) * ( dy_t > 0 ? 1 : -1); + } + } + + if(t2 >= 0 && t2 <= 1) + { + const Real t = t2; + const Real invt = 1 - t; + + //find x val and it counts if it's to the left of the point + const Real xi = invt*invt*p[0][0] + 2*t*invt*p[1][0] + t*t*p[2][0]; + const Real dy_t = 2*a*t + b; + + if(dy_t) + { + intersects += (x >= xi) * ( dy_t > 0 ? 1 : -1); + } + } + + return intersects; + } + + static int quadratic_eqn(Real a, Real b, Real c, Real *t0, Real *t1) + { + const Real b2_4ac = b*b - 4*a*c; + + //degenerate reject (can't take sqrt) + if(b2_4ac < 0) + { + return 0; + } + + const Real sqrtb2_4ac = sqrt(b2_4ac); + const Real signb = b < 0 ? -1 : 1; + const Real q = - 0.5 * (b + signb * sqrtb2_4ac); + + *t0 = q/a; + *t1 = c/q; + + return sqrtb2_4ac == 0 ? 1 : 2; + } + + //Newton-Raphson root polishing (we don't care about bounds, assumes very near the desired root) + static Real polish_cubicroot(Real a, Real b, Real c, Real d, Real t, Real *dpdt) + { + const Real cn[4] = {a,b,c,d}; + Real p,dp,newt,oldpmag=FLT_MAX; + + //eval cubic eqn and its derivative + for(;;) + { + p = cn[0]*t + cn[1]; + dp = cn[0]; + + for(int i = 2; i < 4; i++) + { + dp = p + dp*t; + p = cn[i] + p*t; + } + + if(dp == 0) + { + synfig::warning("polish_cubicroot: Derivative should not vanish!!!"); + return t; + } + + newt = t - p/dp; + + if(newt == t || fabs(p) >= oldpmag) + { + *dpdt = dp; + return t; + } + + t = newt; + oldpmag = fabs(p); + } + } + + static int intersect_cubic(Real x, Real y, Point *p, int /*level*/ = 0) + { + const Real INVALIDROOT = -FLT_MAX; + Real ymin,ymax,xmin,xmax; + Real ymin2,ymax2,ymintot,ymaxtot; + int intersects = 0; + + //sort the overall curve ys and xs - degenerate detection + + //open span for the two end points + ymin = min(p[0][1],p[3][1]); + ymax = max(p[0][1],p[3][1]); + + //other points etc. + ymin2 = min(p[1][1],p[2][1]); + ymax2 = max(p[1][1],p[2][1]); + + ymintot = min(ymin,ymin2); + ymaxtot = max(ymax,ymax2); + + //the entire curve control polygon is in this x range + xmin = min(min(p[0][0],p[1][0]),min(p[2][0],p[3][0])); + xmax = max(max(p[0][0],p[1][0]),max(p[2][0],p[3][0])); + + //outside all y boundaries (no intersect) + if( (y > ymaxtot) || (y < ymintot) ) return 0; + + //left of curve (no intersect) + if(x < xmin) return 0; + + //right of curve (and outside base range) + if( x > xmax ) + { + if( (y > ymax) || (y < ymin) ) return 0; + + //degenerate accept - to the right and inside the [ymin,ymax] range (already rejected if out of range) + const Real n = p[3][1] - p[0][1]; + + //extract the sign from the value (we need valid data) + return n < 0 ? -1 : 1; + } + + //degenerate horizontal line max -- doesn't happen enough to check for + if( ymintot == ymaxtot ) return 0; + + //calculate roots: + // can have 0,1,2, or 3 real roots + // if any of them are double then reject the two... + + // y-coefficients for f_y(t) - y = 0 + Real a = p[3][1] - 3*p[2][1] + 3*p[1][1] - p[0][1], + b = 3*p[2][1] - 6*p[1][1] + 3*p[0][1], + c = 3*p[1][1] - 3*p[0][1], + d = p[0][1] - y; + + Real ax = p[3][0] - 3*p[2][0] + 3*p[1][0] - p[0][0], + bx = 3*p[2][0] - 6*p[1][0] + 3*p[0][0], + cx = 3*p[1][0] - 3*p[0][0], + dx = p[0][0]; + + Real t1 = INVALIDROOT, t2 = INVALIDROOT, t3 = INVALIDROOT, t, dydt; + + if(a == 0) + { + //only 2nd degree + if(b == 0) + { + //linear + if(c == 0) return 0; + + t1 = - d / c; //equation devolved into: ct + d = 0 - solve... + }else + { + //0 roots = 0 intersections, 1 root = 2 intersections at the same place (0 effective) + if(quadratic_eqn(a,b,c,&t1,&t2) != 2) return 0; + } + }else + { + //cubic - sigh.... + + //algorithm courtesy of Numerical Recipes in C (algorithm copied from pg. 184/185) + Real an = b / a, + bn = c / a, + cn = d / a; + + //if cn is 0 (or really really close), then we can simplify this... + if(IsZero(cn)) + { + t3 = 0; + + //0 roots = 0 intersections, 1 root = 2 intersections at the same place (0 effective) + if(quadratic_eqn(a,b,c,&t1,&t2) != 2) + { + t1 = t2 = INVALIDROOT; + } + } + else + { + //otherwise run the normal cubic root equation + Real Q = (an*an - 3.0*bn) / 9.0; + Real R = ((2.0*an*an - 9.0*bn)*an + 27.0*cn)/54.0; + + if(R*R < Q*Q*Q) + { + Real theta = acos(R / sqrt(Q*Q*Q)); + + t1 = -2.0*sqrt(Q)*cos(theta/3) - an/3.0; + t2 = -2.0*sqrt(Q)*cos((theta+2*PI)/3.0) - an/3.0; + t3 = -2.0*sqrt(Q)*cos((theta-2*PI)/3.0) - an/3.0; + + //don't need to reorder,l just need to eliminate double/triple roots + //if(t3 == t2 && t1 == t2) t2 = t3 = INVALIDROOT; + if(t3 == t2) t2 = t3 = INVALIDROOT; + if(t1 == t2) t1 = t2 = INVALIDROOT; + if(t1 == t3) t1 = t3 = INVALIDROOT; + }else + { + Real signR = R < 0 ? -1 : 1; + Real A = - signR * pow(signR*R + sqrt(R*R - Q*Q*Q),1/3.0); + + Real B; + if(A == 0) B = 0; + else B = Q / A; + + //single real root in this case + t1 = (A + B) - an/3.0; + } + } + } + + //if(t1 != INVALIDROOT) + { + t = t1;//polish_cubicroot(a,b,c,d,t1,&dydt); + if(t >= 0 && t < 1) + { + //const Real invt = 1 - t; + + //find x val and it counts if it's to the left of the point + const Real xi = ((ax*t + bx)*t + cx)*t + dx; + dydt = (3*a*t + 2*b)*t + c; + + if(dydt) + { + intersects += (x >= xi) * ( dydt > 0 ? 1 : -1); + } + } + } + + //if(t2 != INVALIDROOT) + { + t = t2;//polish_cubicroot(a,b,c,d,t2,&dydt); + if(t >= 0 && t < 1) + { + //const Real invt = 1 - t; + + //find x val and it counts if it's to the left of the point + const Real xi = ((ax*t + bx)*t + cx)*t + dx; + dydt = (3*a*t + 2*b)*t + c; + + if(dydt) + { + intersects += (x >= xi) * ( dydt > 0 ? 1 : -1); + } + } + } + + //if(t3 != INVALIDROOT) + { + t = t3;//polish_cubicroot(a,b,c,d,t3,&dydt); + if(t >= 0 && t < 1) + { + //const Real invt = 1 - t; + + //find x val and it counts if it's to the left of the point + const Real xi = ((ax*t + bx)*t + cx)*t + dx; + dydt = (3*a*t + 2*b)*t + c; + + if(dydt) + { + intersects += (x >= xi) * ( dydt > 0 ? 1 : -1); + } + } + } + + return intersects; + } + + int intersect(Real x,Real y, Point *table) const + { + if((y < aabb.miny) || (y > aabb.maxy) || (x < aabb.minx)) return 0; + + int i, curdeg, intersects = 0; + const int numcurves = degrees.size(); + + vector::const_iterator p = pointlist.begin(); + + for(i=0; i < numcurves; i++) + { + curdeg = degrees[i]; + + switch(curdeg) + { + case 2: + { + table[0] = *p++; + table[1] = *p++; + table[2] = *p; //we want to include the last point for the next curve + + intersects += intersect_conic(x,y,table); + + break; + } + + case 3: + { + table[0] = *p++; + table[1] = *p++; + table[2] = *p++; + table[3] = *p; //we want to include the last point for the next curve + + intersects += intersect_cubic(x,y,table); + + break; + } + + default: + { + warning("Invalid degree (%d) inserted into the list (index: %d)\n", curdeg, i); + return 0; + } + } + } + + return intersects; + } +}; + +struct Layer_Shape::Intersector +{ + Rect aabb; + + //! true iff aabb hasn't been initialized yet + bool initaabb; + + int flags; + + enum IntersectorFlags + { + NotClosed = 0x8000 + }; + + enum PrimitiveType + { + TYPE_NONE = 0, + TYPE_LINE, + TYPE_CURVE + }; + + Real cur_x,cur_y; + Real close_x,close_y; + + vector segs; //monotonically increasing + vector curves; //big array of consecutive curves + + int prim; + Vector tangent; + + Intersector() + { + clear(); + } + + bool notclosed() + { + return (flags & NotClosed) || (cur_x != close_x) || (cur_y != close_y); + } + + void move_to(Real x, Real y) + { + close(); + + close_x = cur_x = x; + close_y = cur_y = y; + + tangent[0] = tangent[1] = 0; + + if(initaabb) + { + aabb.set_point(x,y); + initaabb = false; + }else aabb.expand(x,y); + + prim = TYPE_NONE; + } + + void line_to(Real x, Real y) + { + int dir = (y > cur_y)*1 + (-1)*(y < cur_y); + + //check for context (if not line start a new segment) + //if we're not in line mode (covers 0 set case), or if directions are different (not valid for 0 direction) + if(prim != TYPE_LINE || (dir && segs.back().ydir != dir)) + { + MonoSegment seg(dir,x,x,y,y); + + seg.aabb.expand(cur_x,cur_y); + seg.pointlist.push_back(Point(cur_x,cur_y)); + seg.pointlist.push_back(Point(x,y)); + segs.push_back(seg); + } + //add to the last segment, because it works + else + { + segs.back().pointlist.push_back(Point(x,y)); + segs.back().aabb.expand(x,y); + } + + + + cur_x = x; + cur_y = y; + aabb.expand(x,y); //expand the entire thing's bounding box + + tangent[0] = x - cur_x; + tangent[1] = x - cur_y; + + flags |= NotClosed; + prim = TYPE_LINE; + } + + void conic_to_smooth(Real x, Real y) + { + const Real x1 = tangent[0]/2.0 + cur_x; + const Real y1 = tangent[1]/2.0 + cur_y; + + conic_to(x1,y1,x,y); + } + + void conic_to(Real x1, Real y1, Real x, Real y) + { + //if we're not already a curve start one + if(prim != TYPE_CURVE) + { + CurveArray c; + + c.Start(Point(cur_x,cur_y)); + c.AddConic(Point(x1,y1),Point(x,y)); + + curves.push_back(c); + }else + { + curves.back().AddConic(Point(x1,y1),Point(x,y)); + } + + cur_x = x; + cur_y = y; + + aabb.expand(x1,y1); + aabb.expand(x,y); + + tangent[0] = 2*(x - x1); + tangent[1] = 2*(y - y1); + + flags |= NotClosed; + prim = TYPE_CURVE; + } + + void curve_to_smooth(Real x2, Real y2, Real x, Real y) + { + Real x1 = tangent[0]/3.0 + cur_x; + Real y1 = tangent[1]/3.0 + cur_y; + + curve_to(x1,y1,x2,y2,x,y); + } + + void curve_to(Real x1, Real y1, Real x2, Real y2, Real x, Real y) + { + //if we're not already a curve start one + if(prim != TYPE_CURVE) + { + CurveArray c; + + c.Start(Point(cur_x,cur_y)); + c.AddCubic(Point(x1,y1),Point(x2,y2),Point(x,y)); + + curves.push_back(c); + }else + { + curves.back().AddCubic(Point(x1,y1),Point(x2,y2),Point(x,y)); + } + + cur_x = x; + cur_y = y; + + //expand bounding box around ALL of it + aabb.expand(x1,y1); + aabb.expand(x2,y2); + aabb.expand(x,y); + + tangent[0] = 3*(x - x2); + tangent[1] = 3*(y - y2); + + flags |= NotClosed; + prim = TYPE_CURVE; + } + + void close() + { + if(flags & NotClosed) + { + if(cur_x != close_x || cur_y != close_y) + { + line_to(close_x,close_y); + } + + flags &= ~NotClosed; + } + } + + //assumes the line to count the intersections with is (-1,0) + int intersect (Real x, Real y) const + { + int inter = 0; + unsigned int i; + vector::const_iterator s = segs.begin(); + vector::const_iterator c = curves.begin(); + + Point memory[3*MAX_SUBDIVISION_SIZE + 1]; + + for(i = 0; i < segs.size(); i++,s++) + { + inter += s->intersect(x,y); + } + + for(i=0; i < curves.size(); i++,c++) + inter += c->intersect(x,y,memory); + + return inter; + } + + //intersect an arbitrary line + //int intersect (Real x, Real y, Real vx, Real vy) {return 0;} + + void clear() + { + segs.clear(); + curves.clear(); + + flags = 0; + cur_x = cur_y = close_x = close_y = 0; + prim = TYPE_NONE; + tangent[0] = tangent[1] = 0; + initaabb = true; + } +}; + +//*********** SCANLINE RENDERER SUPPORT STRUCTURES *************** +struct PenMark +{ + int y,x; + Real cover,area; + + PenMark(){} + PenMark(int xin, int yin, Real c, Real a) + :y(yin),x(xin),cover(c),area(a) {} + + void set(int xin, int yin, Real c, Real a) { y = yin; x = xin; cover = c; area = a; } + + void setcoord(int xin, int yin) { y = yin; x = xin; } + + void setcover(Real c, Real a) { cover = c; area = a; } + void addcover(Real c, Real a) { cover += c; area += a; } + + bool operator<(const PenMark &rhs) const + { + return y == rhs.y ? x < rhs.x : y < rhs.y; + } +}; + +typedef rect ContextRect; + +class Layer_Shape::PolySpan +{ +public: + typedef deque cover_array; + + Point arc[3*MAX_SUBDIVISION_SIZE + 1]; + + cover_array covers; + PenMark current; + + int open_index; + + //ending position of last primitive + Real cur_x; + Real cur_y; + + //starting position of current primitive list + Real close_x; + Real close_y; + + //flags for the current segment + int flags; + + //the window that will be drawn (used for clipping) + ContextRect window; + + //for assignment to flags value + enum PolySpanFlags + { + NotSorted = 0x8000, + NotClosed = 0x4000 + }; + + //default constructor - 0 everything + PolySpan() :current(0,0,0,0),flags(NotSorted) + { + cur_x = cur_y = close_x = close_y = 0; + open_index = 0; + } + + bool notclosed() const + { + return (flags & NotClosed) || (cur_x != close_x) || (cur_y != close_y); + } + + //0 out all the variables involved in processing + void clear() + { + covers.clear(); + cur_x = cur_y = close_x = close_y = 0; + open_index = 0; + current.set(0,0,0,0); + flags = NotSorted; + } + + //add the current cell, but only if there is information to add + void addcurrent() + { + if(current.cover || current.area) + { + covers.push_back(current); + } + } + + //move to the next cell (cover values 0 initially), keeping the current if necessary + void move_pen(int x, int y) + { + if(y != current.y || x != current.x) + { + addcurrent(); + current.set(x,y,0,0); + } + } + + //close the primitives with a line (or rendering will not work as expected) + void close() + { + if(flags & NotClosed) + { + if(cur_x != close_x || cur_y != close_y) + { + line_to(close_x,close_y); + addcurrent(); + current.setcover(0,0); + } + flags &= ~NotClosed; + } + } + + // Not recommended - destroys any separation of spans currently held + void merge_all() + { + sort(covers.begin(),covers.end()); + open_index = 0; + } + + //will sort the marks if they are not sorted + void sort_marks() + { + if(flags & NotSorted) + { + //only sort the open index + addcurrent(); + current.setcover(0,0); + + sort(covers.begin() + open_index,covers.end()); + flags &= ~NotSorted; + } + } + + //encapsulate the current sublist of marks (used for drawing) + void encapsulate_current() + { + //sort the current list then reposition the open list section + sort_marks(); + open_index = covers.size(); + } + + //move to start a new primitive list (enclose the last primitive if need be) + void move_to(Real x, Real y) + { + close(); + if(isnan(x))x=0; + if(isnan(y))y=0; + move_pen((int)floor(x),(int)floor(y)); + close_y = cur_y = y; + close_x = cur_x = x; + } + + //primitive_to functions + void line_to(Real x, Real y); + void conic_to(Real x1, Real y1, Real x, Real y); + void cubic_to(Real x1, Real y1, Real x2, Real y2, Real x, Real y); + + void draw_scanline(int y, Real x1, Real y1, Real x2, Real y2); + void draw_line(Real x1, Real y1, Real x2, Real y2); + + Real ExtractAlpha(Real area, WindingStyle winding_style) + { + if (area < 0) + area = -area; + + if (winding_style == WINDING_NON_ZERO) + { + // non-zero winding style + if (area > 1) + return 1; + } + else // if (winding_style == WINDING_EVEN_ODD) + { + // even-odd winding style + while (area > 1) + area -= 2; + + // want pyramid like thing + if (area < 0) + area = -area; + } + + return area; + } +}; + +/* === M E T H O D S ======================================================= */ + +Layer_Shape::Layer_Shape(const Real &a, const Color::BlendMethod m): + Layer_Composite (a,m), + edge_table (new Intersector), + color (Color::black()), + origin (0,0), + invert (false), + antialias (true), + blurtype (Blur::FASTGAUSSIAN), + feather (0), + winding_style (WINDING_NON_ZERO), + bytestream (0), + lastbyteop (Primitive::NONE), + lastoppos (-1) +{ +} + +Layer_Shape::~Layer_Shape() +{ + delete edge_table; +} + +void +Layer_Shape::clear() +{ + edge_table->clear(); + bytestream.clear(); +} + +bool +Layer_Shape::set_param(const String & param, const ValueBase &value) +{ + IMPORT_PLUS(color, { if (color.get_a() == 0) { if (converted_blend_) { + set_blend_method(Color::BLEND_ALPHA_OVER); + color.set_a(1); } else transparent_color_ = true; } }); + IMPORT(origin); + IMPORT(invert); + IMPORT(antialias); + IMPORT_PLUS(feather, if(feather<0)feather=0;); + IMPORT(blurtype); + IMPORT(winding_style); + + IMPORT_AS(origin,"offset"); + + return Layer_Composite::set_param(param,value); +} + +ValueBase +Layer_Shape::get_param(const String ¶m)const +{ + EXPORT(color); + EXPORT(origin); + EXPORT(invert); + EXPORT(antialias); + EXPORT(feather); + EXPORT(blurtype); + EXPORT(winding_style); + + EXPORT_NAME(); + EXPORT_VERSION(); + + return Layer_Composite::get_param(param); +} + +Layer::Vocab +Layer_Shape::get_param_vocab()const +{ + Layer::Vocab ret(Layer_Composite::get_param_vocab()); + + ret.push_back(ParamDesc("color") + .set_local_name(_("Color")) + .set_description(_("Layer_Shape Color")) + ); + ret.push_back(ParamDesc("origin") + .set_local_name(_("Origin")) + ); + ret.push_back(ParamDesc("invert") + .set_local_name(_("Invert")) + ); + ret.push_back(ParamDesc("antialias") + .set_local_name(_("Antialiasing")) + ); + ret.push_back(ParamDesc("feather") + .set_local_name(_("Feather")) + .set_is_distance() + ); + ret.push_back(ParamDesc("blurtype") + .set_local_name(_("Type of Feather")) + .set_description(_("Type of feathering to use")) + .set_hint("enum") + .add_enum_value(Blur::BOX,"box",_("Box Blur")) + .add_enum_value(Blur::FASTGAUSSIAN,"fastgaussian",_("Fast Gaussian Blur")) + .add_enum_value(Blur::CROSS,"cross",_("Cross-Hatch Blur")) + .add_enum_value(Blur::GAUSSIAN,"gaussian",_("Gaussian Blur")) + .add_enum_value(Blur::DISC,"disc",_("Disc Blur")) + ); + ret.push_back(ParamDesc("winding_style") + .set_local_name(_("Winding Style")) + .set_description(_("Winding style to use")) + .set_hint("enum") + .add_enum_value(WINDING_NON_ZERO,"nonzero",_("Non Zero")) + .add_enum_value(WINDING_EVEN_ODD,"evenodd",_("Even/Odd")) + ); + + return ret; +} + +synfig::Layer::Handle +Layer_Shape::hit_check(synfig::Context context, const synfig::Point &p)const +{ + Point pos(p-origin); + + int intercepts = edge_table->intersect(pos[0],pos[1]); + + // If we have an odd number of intercepts, we are inside. + // If we have an even number of intercepts, we are outside. + bool intersect = ((!!intercepts) ^ invert); + + if(get_amount() == 0 || get_blend_method() == Color::BLEND_ALPHA_OVER) + { + intersect = false; + } + + if(intersect) + { + synfig::Layer::Handle tmp; + if(get_blend_method()==Color::BLEND_BEHIND && (tmp=context.hit_check(p))) + return tmp; + if(Color::is_onto(get_blend_method())) + { + //if there's something in the lower layer then we're set... + if(!context.hit_check(p).empty()) + return const_cast(this); + }else if(get_blend_method() == Color::BLEND_ALPHA_OVER) + { + synfig::info("layer_shape::hit_check - we've got alphaover"); + //if there's something in the lower layer then we're set... + if(color.get_a() < 0.1 && get_amount() > .9) + { + synfig::info("layer_shape::hit_check - can see through us... so nothing"); + return Handle(); + }else return context.hit_check(p); + }else + return const_cast(this); + } + + return context.hit_check(p); +} + +Color +Layer_Shape::get_color(Context context, const Point &p)const +{ + Point pp = p; + + if(feather) + pp = Blur(feather,feather,blurtype)(p); + + Point pos(pp-origin); + + int intercepts = edge_table->intersect(pos[0],pos[1]); + + // If we have an odd number of intercepts, we are inside. + // If we have an even number of intercepts, we are outside. + bool intersect = ((!!intercepts) ^ invert); + + if(!intersect) + return Color::blend(Color::alpha(),context.get_color(pp),get_amount(),get_blend_method()); + + //Ok, we're inside... bummmm ba bum buM... + if(get_blend_method() == Color::BLEND_STRAIGHT && get_amount() == 1) + return color; + else + return Color::blend(color,context.get_color(p),get_amount(),get_blend_method()); +} + +//************** SCANLINE RENDERING ********************* +void Layer_Shape::PolySpan::line_to(Real x, Real y) +{ + Real n[4] = {0,0,0,0}; + bool afterx = false; + + const Real xin(x), yin(y); + + Real dx = x - cur_x; + Real dy = y - cur_y; + + //CLIP IT!!!! + try { + //outside y - ignore entirely + if( (cur_y >= window.maxy && y >= window.maxy) + ||(cur_y < window.miny && y < window.miny) ) + { + cur_x = x; + cur_y = y; + } + else //not degenerate - more complicated + { + if(dy > 0) //be sure it's not tooooo small + { + // cur_y ... window.miny ... window.maxy ... y + + //initial degenerate - initial clip + if(cur_y < window.miny) + { + //new clipped start point (must also move pen) + n[2] = cur_x + (window.miny - cur_y) * dx / dy; + + cur_x = n[2]; + cur_y = window.miny; + move_pen((int)floor(cur_x),window.miny); + } + + //generate data for the ending clipped info + if(y > window.maxy) + { + //initial line to intersection (and degenerate) + n[2] = x + (window.maxy - y) * dx / dy; + + //intersect coords + x = n[2]; + y = window.maxy; + } + } + else + { + //initial degenerate - initial clip + if(cur_y > window.maxy) + { + //new clipped start point (must also move pen) + n[2] = cur_x + (window.maxy - cur_y) * dx / dy; + + cur_x = n[2]; + cur_y = window.maxy; + move_pen((int)floor(cur_x),window.maxy); + } + + //generate data for the ending clipped info + if(y < window.miny) + { + //initial line to intersection (and degenerate) + n[2] = x + (window.miny - y) * dx / dy; + + //intersect coords + x = n[2]; + y = window.miny; + } + } + + //all degenerate - but require bounded clipped values + if( (cur_x >= window.maxx && x >= window.maxx) + ||(cur_x < window.minx && x < window.minx) ) + { + //clip both vertices - but only needed in the x direction + cur_x = max(cur_x, (Real)window.minx); + cur_x = min(cur_x, (Real)window.maxx); + + //clip the dest values - y is already clipped + x = max(x,(Real)window.minx); + x = min(x,(Real)window.maxx); + + //must start at new point... + move_pen((int)floor(cur_x),(int)floor(cur_y)); + + draw_line(cur_x,cur_y,x,y); + + cur_x = xin; + cur_y = yin; + } + else + { + //clip x + if(dx > 0) + { + //initial degenerate - initial clip + if(cur_x < window.minx) + { + //need to draw an initial segment from clippedx,cur_y to clippedx,intersecty + n[2] = cur_y + (window.minx - cur_x) * dy / dx; + + move_pen(window.minx,(int)floor(cur_y)); + draw_line(window.minx,cur_y,window.minx,n[2]); + + cur_x = window.minx; + cur_y = n[2]; + } + + //generate data for the ending clipped info + if(x > window.maxx) + { + //initial line to intersection (and degenerate) + n[2] = y + (window.maxx - x) * dy / dx; + + n[0] = window.maxx; + n[1] = y; + + //intersect coords + x = window.maxx; + y = n[2]; + afterx = true; + } + }else + { + //initial degenerate - initial clip + if(cur_x > window.maxx) + { + //need to draw an initial segment from clippedx,cur_y to clippedx,intersecty + n[2] = cur_y + (window.maxx - cur_x) * dy / dx; + + move_pen(window.maxx,(int)floor(cur_y)); + draw_line(window.maxx,cur_y,window.maxx,n[2]); + + cur_x = window.maxx; + cur_y = n[2]; + } + + //generate data for the ending clipped info + if(x < window.minx) + { + //initial line to intersection (and degenerate) + n[2] = y + (window.minx - x) * dy / dx; + + n[0] = window.minx; + n[1] = y; + + //intersect coords + x = window.minx; + y = n[2]; + afterx = true; + } + } + + move_pen((int)floor(cur_x),(int)floor(cur_y)); + //draw the relevant line (clipped) + draw_line(cur_x,cur_y,x,y); + + if(afterx) + { + draw_line(x,y,n[0],n[1]); + } + + cur_x = xin; + cur_y = yin; + } + } + } catch(...) { synfig::error("line_to: cur_x=%f, cur_y=%f, x=%f, y=%f", cur_x, cur_y, x, y); throw; } + + flags |= NotClosed|NotSorted; +} + +static inline bool clip_conic(const Point *const p, const ContextRect &r) +{ + const Real minx = min(min(p[0][0],p[1][0]),p[2][0]); + const Real miny = min(min(p[0][1],p[1][1]),p[2][1]); + const Real maxx = max(max(p[0][0],p[1][0]),p[2][0]); + const Real maxy = max(max(p[0][1],p[1][1]),p[2][1]); + + return (minx > r.maxx) || + (maxx < r.minx) || + (miny > r.maxy) || + (maxy < r.miny); +} + +static inline bool clip_cubic(const Point *const p, const ContextRect &r) +{ + /*const Real minx = min(min(p[0][0],p[1][0]),min(p[2][0],p[3][0])); + const Real miny = min(min(p[0][1],p[1][1]),min(p[2][1],p[3][1])); + const Real maxx = max(max(p[0][0],p[1][0]),max(p[2][0],p[3][1])); + const Real maxy = max(max(p[0][1],p[1][1]),max(p[2][1],p[3][1])); + + return (minx > r.maxx) || + (maxx < r.minx) || + (miny > r.maxy) || + (maxy < r.miny);*/ + + return ((p[0][0] > r.maxx) && (p[1][0] > r.maxx) && (p[2][0] > r.maxx) && (p[3][0] > r.maxx)) || + ((p[0][0] < r.minx) && (p[1][0] < r.minx) && (p[2][0] < r.minx) && (p[3][0] < r.minx)) || + ((p[0][1] > r.maxy) && (p[1][1] > r.maxy) && (p[2][1] > r.maxy) && (p[3][1] > r.maxy)) || + ((p[0][1] < r.miny) && (p[1][1] < r.miny) && (p[2][1] < r.miny) && (p[3][1] < r.miny)); +} + +static inline Real max_edges_cubic(const Point *const p) +{ + const Real x1 = p[1][0] - p[0][0]; + const Real y1 = p[1][1] - p[0][1]; + + const Real x2 = p[2][0] - p[1][0]; + const Real y2 = p[2][1] - p[1][1]; + + const Real x3 = p[3][0] - p[2][0]; + const Real y3 = p[3][1] - p[2][1]; + + const Real d1 = x1*x1 + y1*y1; + const Real d2 = x2*x2 + y2*y2; + const Real d3 = x3*x3 + y3*y3; + + return max(max(d1,d2),d3); +} + +static inline Real max_edges_conic(const Point *const p) +{ + const Real x1 = p[1][0] - p[0][0]; + const Real y1 = p[1][1] - p[0][1]; + + const Real x2 = p[2][0] - p[1][0]; + const Real y2 = p[2][1] - p[1][1]; + + const Real d1 = x1*x1 + y1*y1; + const Real d2 = x2*x2 + y2*y2; + + return max(d1,d2); +} + +void Layer_Shape::PolySpan::conic_to(Real x1, Real y1, Real x, Real y) +{ + Point *current = arc; + int level = 0; + int num = 0; + bool onsecond = false; + + arc[0] = Point(x,y); + arc[1] = Point(x1,y1); + arc[2] = Point(cur_x,cur_y); + + //just draw the line if it's outside + if(clip_conic(arc,window)) + { + line_to(x,y); + return; + } + + //Ok so it's not super degenerate, subdivide and draw (run through minimum subdivision levels first) + while(current >= arc) + { + if(num >= MAX_SUBDIVISION_SIZE) + { + warning("Curve subdivision somehow ran out of space while tessellating!"); + + //do something... + assert(0); + return; + }else + //if the curve is clipping then draw degenerate + if(clip_conic(current,window)) + { + line_to(current[0][0],current[0][1]); //backwards so front is destination + current -= 2; + if(onsecond) level--; + onsecond = true; + num--; + continue; + }else + //if we are not at the level minimum + if(level < MIN_SUBDIVISION_DRAW_LEVELS) + { + Subd_Conic_Stack(current); + current += 2; //cursor on second curve + level ++; + num ++; + onsecond = false; + continue; + }else + //split it again, if it's too big + if(max_edges_conic(current) > 0.25) //distance of .5 (cover no more than half the pixel) + { + Subd_Conic_Stack(current); + current += 2; //cursor on second curve + level ++; + num ++; + onsecond = false; + } + else //NOT TOO BIG? RENDER!!! + { + //cur_x,cur_y = current[2], so we need to go 1,0 + line_to(current[1][0],current[1][1]); + line_to(current[0][0],current[0][1]); + + current -= 2; + if(onsecond) level--; + num--; + onsecond = true; + } + } +} + +void Layer_Shape::PolySpan::cubic_to(Real x1, Real y1, Real x2, Real y2, Real x, Real y) +{ + Point *current = arc; + int num = 0; + int level = 0; + bool onsecond = false; + + arc[0] = Point(x,y); + arc[1] = Point(x2,y2); + arc[2] = Point(x1,y1); + arc[3] = Point(cur_x,cur_y); + + //just draw the line if it's outside + if(clip_cubic(arc,window)) + { + line_to(x,y); + return; + } + + //Ok so it's not super degenerate, subdivide and draw (run through minimum subdivision levels first) + while(current >= arc) //once current goes below arc, there are no more curves left + { + if(num >= MAX_SUBDIVISION_SIZE) + { + warning("Curve subdivision somehow ran out of space while tessellating!"); + + //do something... + assert(0); + return; + }else + + //if we are not at the level minimum + if(level < MIN_SUBDIVISION_DRAW_LEVELS) + { + Subd_Cubic_Stack(current); + current += 3; //cursor on second curve + level ++; + num ++; + onsecond = false; + continue; + }else + //if the curve is clipping then draw degenerate + if(clip_cubic(current,window)) + { + line_to(current[0][0],current[0][1]); //backwards so front is destination + current -= 3; + if(onsecond) level--; + onsecond = true; + num --; + continue; + } + else + //split it again, if it's too big + if(max_edges_cubic(current) > 0.25) //could use max_edges<3> + { + Subd_Cubic_Stack(current); + current += 3; //cursor on second curve + level ++; + num ++; + onsecond = false; + } + else //NOT TOO BIG? RENDER!!! + { + //cur_x,cur_y = current[3], so we need to go 2,1,0 + line_to(current[2][0],current[2][1]); + line_to(current[1][0],current[1][1]); + line_to(current[0][0],current[0][1]); + + current -= 3; + if(onsecond) level--; + num --; + onsecond = true; + } + } +} + +//******************** LINE ALGORITHMS **************************** +// THESE CALCULATE THE AREA AND THE COVER FOR THE MARKS, TO THEN SCAN CONVERT +// - BROKEN UP INTO SCANLINES (draw_line - y intersections), +// THEN THE COVER AND AREA PER TOUCHED PIXEL IS CALCULATED (draw_scanline - x intersections) +void Layer_Shape::PolySpan::draw_scanline(int y, Real x1, Real fy1, Real x2, Real fy2) +{ + int ix1 = (int)floor(x1); + int ix2 = (int)floor(x2); + Real fx1 = x1 - ix1; + Real fx2 = x2 - ix2; + + Real dx,dy,dydx,mult; + + dx = x2 - x1; + dy = fy2 - fy1; + + //case horizontal line + if(fy1 == fy2) + { + move_pen(ix2,y); //pen needs to be at the last coord + return; + } + + //case all in same pixel + if(ix1 == ix2) //impossible for degenerate case (covered by the previous cases) + { + current.addcover(dy,(fx1 + fx2)*dy/2); //horizontal trapezoid area + return; + } + + if(dx > 0) + { + // ----> fx1...1 0...1 ... 0...1 0...fx2 + dydx = dy / dx; + + //set initial values + //Iterate through the covered pixels + mult = (1 - fx1)*dydx; //next y intersection diff value (at 1) + + //first pixel + current.addcover(mult,(1 + fx1)*mult/2); // fx1,fy1,1,fy@1 - starting trapezoidal area + + //move to the next pixel + fy1 += mult; + ix1++; + + move_pen(ix1,y); + + //set up for whole ones + while(ix1 != ix2) + { + //trapezoid(0,y1,1,y1+dydx); + current.addcover(dydx,dydx/2); //accumulated area 1/2 the cover + + //move to next pixel (+1) + ix1++; + fy1 += dydx; + move_pen(ix1,y); + } + + //last pixel + //final y-pos - last intersect pos + mult = fx2 * dydx; + current.addcover(mult,(0+fx2)*mult/2); + }else + { + // fx2...1 0...1 ... 0...1 0...fx1 <---- + //mult = (0 - fx1) * dy / dx; + //neg sign sucked into dydx + dydx = -dy / dx; + + //set initial values + //Iterate through the covered pixels + mult = fx1*dydx; //next y intersection diff value + + //first pixel + current.addcover(mult,fx1*mult/2); // fx1,fy1,0,fy@0 - starting trapezoidal area + + //move to next pixel + fy1 += mult; + ix1--; + + move_pen(ix1,y); + + //set up for whole ones + while(ix1 != ix2) + { + //trapezoid(0,y1,1,y1+dydx); + current.addcover(dydx,dydx/2); //accumulated area 1/2 the cover + + //move to next pixel (-1) + fy1 += dydx; + ix1--; + move_pen(ix1,y); + } + + //last pixel + mult = fy2 - fy1; //final y-pos - last intersect pos + + current.addcover(mult,(fx2+1)*mult/2); + } +} + +void Layer_Shape::PolySpan::draw_line(Real x1, Real y1, Real x2, Real y2) +{ + int iy1 = (int)floor(y1); + int iy2 = (int)floor(y2); + Real fy1 = y1 - iy1; + Real fy2 = y2 - iy2; + + assert(!isnan(fy1)); + assert(!isnan(fy2)); + + Real dx,dy,dxdy,mult,x_from,x_to; + + const Real SLOPE_EPSILON = 1e-10; + + //case all one scanline + if(iy1 == iy2) + { + draw_scanline(iy1,x1,y1,x2,y2); + return; + } + + //difference values + dy = y2 - y1; + dx = x2 - x1; + + //case vertical line + if(dx < SLOPE_EPSILON && dx > -SLOPE_EPSILON) + { + //calc area and cover on vertical line + if(dy > 0) + { + // ----> fx1...1 0...1 ... 0...1 0...fx2 + Real sub; + + int ix1 = (int)floor(x1); + Real fx1 = x1 - ix1; + + //current pixel + sub = 1 - fy1; + + current.addcover(sub,fx1*sub); + + //next pixel + iy1++; + + //move pen to next pixel + move_pen(ix1,iy1); + + while(iy1 != iy2) + { + //accumulate cover + current.addcover(1,fx1); + + //next pixel + iy1++; + move_pen(ix1,iy1); + } + + //last pixel + current.addcover(fy2,fy2*fx1); + }else + { + Real sub; + + int ix1 = (int)floor(x1); + Real fx1 = x1 - ix1; + + //current pixel + sub = 0 - fy1; + + current.addcover(sub,fx1*sub); + + //next pixel + iy1--; + + move_pen(ix1,iy1); + + while(iy1 != iy2) + { + //accumulate in current pixel + current.addcover(-1,-fx1); + + //move to next + iy1--; + move_pen(ix1,iy1); + } + + current.addcover(fy2-1,(fy2-1)*fx1); + } + return; + } + + //case normal line - guaranteed dx != 0 && dy != 0 + + //calculate the initial intersection with "next" scanline + if(dy > 0) + { + dxdy = dx / dy; + + mult = (1 - fy1) * dxdy; + + //x intersect scanline + x_from = x1 + mult; + draw_scanline(iy1,x1,fy1,x_from,1); + + //move to next line + iy1++; + + move_pen((int)floor(x_from),iy1); + + while(iy1 != iy2) + { + //keep up on the x axis, and render the current scanline + x_to = x_from + dxdy; + draw_scanline(iy1,x_from,0,x_to,1); + x_from = x_to; + + //move to next pixel + iy1++; + move_pen((int)floor(x_from),iy1); + } + + //draw the last one, fractional + draw_scanline(iy2,x_from,0,x2,fy2); + + }else + { + dxdy = -dx / dy; + + mult = fy1 * dxdy; + + //x intersect scanline + x_from = x1 + mult; + draw_scanline(iy1,x1,fy1,x_from,0); + + //each line after + iy1--; + + move_pen((int)floor(x_from),iy1); + + while(iy1 != iy2) + { + x_to = x_from + dxdy; + draw_scanline(iy1,x_from,1,x_to,0); + x_from = x_to; + + iy1--; + move_pen((int)floor(x_from),iy1); + } + //draw the last one, fractional + draw_scanline(iy2,x_from,1,x2,fy2); + } +} + +//****** LAYER PEN OPERATIONS (move_to, line_to, etc.) ****** +void Layer_Shape::move_to(Real x, Real y) +{ + //const int sizeblock = sizeof(Primitive)+sizeof(Point); + Primitive op; + Point p(x,y); + + op.operation = Primitive::MOVE_TO; + op.number = 1; //one point for now + + if(lastbyteop == Primitive::MOVE_TO) + { + char *ptr = &bytestream[lastoppos]; + memcpy(ptr,&op,sizeof(op)); + memcpy(ptr+sizeof(op),&p,sizeof(p)); + } + else //make a new op + { + lastbyteop = Primitive::MOVE_TO; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } + + edge_table->move_to(x,y); +} + +void Layer_Shape::close() +{ + Primitive op; + + op.operation = Primitive::CLOSE; + op.number = 0; + + if(lastbyteop == Primitive::CLOSE) + { + }else + { + lastbyteop = Primitive::CLOSE; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert header + } + + edge_table->close(); + //should not affect the bounding box since it would just be returning to old point... +} + +void Layer_Shape::endpath() +{ + Primitive op; + + op.operation = Primitive::END; + op.number = 0; + + if(lastbyteop == Primitive::END || lastbyteop == Primitive::NONE) + { + }else + { + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); + } + //should not affect the bounding box since it would just be returning to old point... if at all +} + +void Layer_Shape::line_to(Real x, Real y) +{ + assert(!isnan(x)); + assert(!isnan(y)); + + //const int sizeblock = sizeof(Primitive)+sizeof(Point); + Primitive op; + Point p(x,y); + + op.operation = Primitive::LINE_TO; + op.number = 1; //one point for now + + if(lastbyteop == Primitive::MOVE_TO || lastbyteop == Primitive::LINE_TO) + { + //only need to insert the point + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); + + Primitive * prim = (Primitive *)&bytestream[lastoppos]; + prim->number++; //increment number of points in the list + }else + { + lastbyteop = Primitive::LINE_TO; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } + + edge_table->line_to(x,y); +} + +void Layer_Shape::conic_to(Real x1, Real y1, Real x, Real y) +{ + //const int sizeblock = sizeof(Primitive)+sizeof(Point)*2; + Primitive op; + Point p(x,y); + Point p1(x1,y1); + + op.operation = Primitive::CONIC_TO; + op.number = 2; //2 points for now + + if(lastbyteop == Primitive::CONIC_TO) + { + //only need to insert the new points + bytestream.insert(bytestream.end(),(char*)&p1,(char*)(&p1+1)); + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); + + Primitive * prim = (Primitive *)&bytestream[lastoppos]; + prim->number += 2; //increment number of points in the list + }else + { + lastbyteop = Primitive::CONIC_TO; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p1,(char*)(&p1+1)); //insert the bytes for data + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } + + edge_table->conic_to(x1,y1,x,y); +} + +void Layer_Shape::conic_to_smooth(Real x, Real y) //x1,y1 derived from current tangent +{ + //const int sizeblock = sizeof(Primitive)+sizeof(Point); + Primitive op; + Point p(x,y); + + op.operation = Primitive::CONIC_TO_SMOOTH; + op.number = 1; //2 points for now + + if(lastbyteop == Primitive::CONIC_TO_SMOOTH) + { + //only need to insert the new point + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); + + Primitive * prim = (Primitive *)&bytestream[lastoppos]; + prim->number += 1; //increment number of points in the list + }else + { + lastbyteop = Primitive::CONIC_TO_SMOOTH; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } + + edge_table->conic_to_smooth(x,y); +} + +void Layer_Shape::curve_to(Real x1, Real y1, Real x2, Real y2, Real x, Real y) +{ + //const int sizeblock = sizeof(Primitive)+sizeof(Point)*3; + Primitive op; + Point p(x,y); + Point p1(x1,y1); + Point p2(x2,y2); + + op.operation = Primitive::CUBIC_TO; + op.number = 3; //3 points for now + + if(lastbyteop == Primitive::CUBIC_TO) + { + //only need to insert the new points + bytestream.insert(bytestream.end(),(char*)&p1,(char*)(&p1+1)); + bytestream.insert(bytestream.end(),(char*)&p2,(char*)(&p2+1)); + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); + + Primitive * prim = (Primitive *)&bytestream[lastoppos]; + prim->number += 3; //increment number of points in the list + }else + { + lastbyteop = Primitive::CUBIC_TO; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p1,(char*)(&p1+1)); //insert the bytes for data + bytestream.insert(bytestream.end(),(char*)&p2,(char*)(&p2+1)); //insert the bytes for data + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } + + edge_table->curve_to(x1,y1,x2,y2,x,y); +} + +void Layer_Shape::curve_to_smooth(Real x2, Real y2, Real x, Real y) //x1,y1 derived from current tangent +{ + //const int sizeblock = sizeof(Primitive)+sizeof(Point)*3; + Primitive op; + Point p(x,y); + Point p2(x2,y2); + + op.operation = Primitive::CUBIC_TO_SMOOTH; + op.number = 2; //3 points for now + + if(lastbyteop == Primitive::CUBIC_TO_SMOOTH) + { + //only need to insert the new points + bytestream.insert(bytestream.end(),(char*)&p2,(char*)(&p2+1)); + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); + + Primitive * prim = (Primitive *)&bytestream[lastoppos]; + prim->number += 2; //increment number of points in the list + }else + { + lastbyteop = Primitive::CUBIC_TO_SMOOTH; + lastoppos = bytestream.size(); + + bytestream.insert(bytestream.end(),(char*)&op,(char*)(&op+1)); //insert the bytes for the header + bytestream.insert(bytestream.end(),(char*)&p2,(char*)(&p2+1)); //insert the bytes for data + bytestream.insert(bytestream.end(),(char*)&p,(char*)(&p+1)); //insert the bytes for data + } +} + +// ACCELERATED RENDER FUNCTION - TRANSLATE BYTE CODE INTO FUNCTION CALLS + +bool Layer_Shape::render_polyspan(Surface *surface, PolySpan &polyspan, + Color::BlendMethod got_blend_method, Color::value_type got_amount) const +{ + Surface::alpha_pen p(surface->begin(),got_amount,_BlendFunc(got_blend_method)); + PolySpan::cover_array::iterator cur_mark = polyspan.covers.begin(); + PolySpan::cover_array::iterator end_mark = polyspan.covers.end(); + + Real cover,area,alpha; + + int y,x; + + p.set_value(color); + cover = 0; + + if(cur_mark == end_mark) + { + //no marks at all + if(invert) + { + p.move_to(polyspan.window.minx,polyspan.window.miny); + p.put_block(polyspan.window.maxy - polyspan.window.miny,polyspan.window.maxx - polyspan.window.minx); + } + return true; + } + + //fill initial rect / line + if(invert) + { + //fill all the area above the first vertex + p.move_to(polyspan.window.minx,polyspan.window.miny); + y = polyspan.window.miny; + int l = polyspan.window.maxx - polyspan.window.minx; + + p.put_block(cur_mark->y - polyspan.window.miny,l); + + //fill the area to the left of the first vertex on that line + l = cur_mark->x - polyspan.window.minx; + p.move_to(polyspan.window.minx,cur_mark->y); + if(l) p.put_hline(l); + } + + for(;;) + { + y = cur_mark->y; + x = cur_mark->x; + + p.move_to(x,y); + + area = cur_mark->area; + cover += cur_mark->cover; + + //accumulate for the current pixel + while(++cur_mark != polyspan.covers.end()) + { + if(y != cur_mark->y || x != cur_mark->x) + break; + + area += cur_mark->area; + cover += cur_mark->cover; + } + + //draw pixel - based on covered area + if(area) //if we're ok, draw the current pixel + { + alpha = polyspan.ExtractAlpha(cover - area, winding_style); + if(invert) alpha = 1 - alpha; + + if(!antialias) + { + if(alpha >= .5) p.put_value(); + } + else if(alpha) p.put_value_alpha(alpha); + + p.inc_x(); + x++; + } + + //if we're done, don't use iterator and exit + if(cur_mark == end_mark) break; + + //if there is no more live pixels on this line, goto next + if(y != cur_mark->y) + { + if(invert) + { + //fill the area at the end of the line + p.put_hline(polyspan.window.maxx - x); + + //fill area at the beginning of the next line + p.move_to(polyspan.window.minx,cur_mark->y); + p.put_hline(cur_mark->x - polyspan.window.minx); + } + + cover = 0; + + continue; + } + + //draw span to next pixel - based on total amount of pixel cover + if(x < cur_mark->x) + { + alpha = polyspan.ExtractAlpha(cover, winding_style); + if(invert) alpha = 1 - alpha; + + if(!antialias) + { + if(alpha >= .5) p.put_hline(cur_mark->x - x); + } + else if(alpha) p.put_hline(cur_mark->x - x,alpha); + } + } + + //fill the after stuff + if(invert) + { + //fill the area at the end of the line + p.put_hline(polyspan.window.maxx - x); + + //fill area at the beginning of the next line + p.move_to(polyspan.window.minx,y+1); + p.put_block(polyspan.window.maxy - y - 1,polyspan.window.maxx - polyspan.window.minx); + } + + return true; +} + +bool Layer_Shape::render_polyspan(etl::surface *surface, PolySpan &polyspan) const +{ + etl::surface::pen p(surface->begin()); + PolySpan::cover_array::iterator cur_mark = polyspan.covers.begin(); + PolySpan::cover_array::iterator end_mark = polyspan.covers.end(); + + Real cover,area,alpha; + + int y,x; + + cover = 0; + + //the pen always writes 1 (unless told to do otherwise) + p.set_value(1); + + if(cur_mark == end_mark) + { + //no marks at all + if(invert) + { + p.move_to(polyspan.window.minx,polyspan.window.miny); + p.put_block(polyspan.window.maxy - polyspan.window.miny,polyspan.window.maxx - polyspan.window.minx); + } + return true; + } + + //fill initial rect / line + if(invert) + { + //fill all the area above the first vertex + p.move_to(polyspan.window.minx,polyspan.window.miny); + y = polyspan.window.miny; + int l = polyspan.window.maxx - polyspan.window.minx; + + p.put_block(cur_mark->y - polyspan.window.miny,l); + + //fill the area to the left of the first vertex on that line + l = cur_mark->x - polyspan.window.minx; + p.move_to(polyspan.window.minx,cur_mark->y); + if(l) p.put_hline(l); + + for(;;) + { + y = cur_mark->y; + x = cur_mark->x; + + p.move_to(x,y); + + area = cur_mark->area; + cover += cur_mark->cover; + + //accumulate for the current pixel + while(++cur_mark != polyspan.covers.end()) + { + if(y != cur_mark->y || x != cur_mark->x) + break; + + area += cur_mark->area; + cover += cur_mark->cover; + } + + //draw pixel - based on covered area + if(area) //if we're ok, draw the current pixel + { + alpha = 1 - polyspan.ExtractAlpha(cover - area, winding_style); + if(!antialias) + { + if(alpha >= .5) p.put_value(); + } + else if(alpha) p.put_value(alpha); + + p.inc_x(); + x++; + } + + //if we're done, don't use iterator and exit + if(cur_mark == end_mark) break; + + //if there is no more live pixels on this line, goto next + if(y != cur_mark->y) + { + //fill the area at the end of the line + p.put_hline(polyspan.window.maxx - x); + + //fill area at the beginning of the next line + p.move_to(polyspan.window.minx,cur_mark->y); + p.put_hline(cur_mark->x - polyspan.window.minx); + + cover = 0; + + continue; + } + + //draw span to next pixel - based on total amount of pixel cover + if(x < cur_mark->x) + { + alpha = 1 - polyspan.ExtractAlpha(cover, winding_style); + if(!antialias) + { + if(alpha >= .5) p.put_hline(cur_mark->x - x); + } + else if(alpha) p.put_hline(cur_mark->x - x,alpha); + } + } + + //fill the area at the end of the line + p.put_hline(polyspan.window.maxx - x); + + //fill area at the beginning of the next line + p.move_to(polyspan.window.minx,y+1); + p.put_block(polyspan.window.maxy - y - 1,polyspan.window.maxx - polyspan.window.minx); + }else + { + for(;;) + { + y = cur_mark->y; + x = cur_mark->x; + + p.move_to(x,y); + + area = cur_mark->area; + cover += cur_mark->cover; + + //accumulate for the current pixel + while(++cur_mark != polyspan.covers.end()) + { + if(y != cur_mark->y || x != cur_mark->x) + break; + + area += cur_mark->area; + cover += cur_mark->cover; + } + + //draw pixel - based on covered area + if(area) //if we're ok, draw the current pixel + { + alpha = polyspan.ExtractAlpha(cover - area, winding_style); + if(!antialias) + { + if(alpha >= .5) p.put_value(); + } + else if(alpha) p.put_value(alpha); + + p.inc_x(); + x++; + } + + //if we're done, don't use iterator and exit + if(cur_mark == end_mark) break; + + //if there is no more live pixels on this line, goto next + if(y != cur_mark->y) + { + cover = 0; + + continue; + } + + //draw span to next pixel - based on total amount of pixel cover + if(x < cur_mark->x) + { + alpha = polyspan.ExtractAlpha(cover, winding_style); + if(!antialias) + { + if(alpha >= .5) p.put_hline(cur_mark->x - x); + } + else if(alpha) p.put_hline(cur_mark->x - x,alpha); + } + } + } + + return true; +} + +bool +Layer_Shape::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const +{ + const unsigned int w = renddesc.get_w(); + const unsigned int h = renddesc.get_h(); + + const Real pw = abs(renddesc.get_pw()); + const Real ph = abs(renddesc.get_ph()); + + //const Real OFFSET_EPSILON = 1e-8; + SuperCallback stageone(cb,1,10000,15001+renddesc.get_h()); + SuperCallback stagetwo(cb,10000,10001+renddesc.get_h(),15001+renddesc.get_h()); + SuperCallback stagethree(cb,10001+renddesc.get_h(),15001+renddesc.get_h(),15001+renddesc.get_h()); + + // Render what is behind us + + //clip if it satisfies the invert solid thing + if(is_solid_color() && invert) + { + Rect aabb = edge_table->aabb; + Point tl = renddesc.get_tl() - origin; + + Real pw = renddesc.get_pw(), + ph = renddesc.get_ph(); + + Rect nrect; + + Real pixelfeatherx = quality == 10 ? 0 : abs(feather/pw), + pixelfeathery = quality == 10 ? 0 : abs(feather/ph); + + nrect.set_point((aabb.minx - tl[0])/pw,(aabb.miny - tl[1])/ph); + nrect.expand((aabb.maxx - tl[0])/pw,(aabb.maxy - tl[1])/ph); + + RendDesc optdesc(renddesc); + + //make sure to expand so we gain subpixels rather than lose them + nrect.minx = floor(nrect.minx-pixelfeatherx); nrect.miny = floor(nrect.miny-pixelfeathery); + nrect.maxx = ceil(nrect.maxx+pixelfeatherx); nrect.maxy = ceil(nrect.maxy+pixelfeathery); + + //make sure the subwindow is clipped with our tile window (minimize useless drawing) + set_intersect(nrect,nrect,Rect(0,0,renddesc.get_w(),renddesc.get_h())); + + //must resize the surface first + surface->set_wh(renddesc.get_w(),renddesc.get_h()); + surface->clear(); + + //only render anything if it's visible from our current tile + if(nrect.valid()) + { + //set the subwindow to the viewable pixels and render it to the subsurface + optdesc.set_subwindow((int)nrect.minx, (int)nrect.miny, + (int)(nrect.maxx - nrect.minx), (int)(nrect.maxy - nrect.miny)); + + Surface optimizedbacksurf; + if(!context.accelerated_render(&optimizedbacksurf,quality,optdesc,&stageone)) + return false; + + //blit that onto the original surface so we can pretend that nothing ever happened + Surface::pen p = surface->get_pen((int)nrect.minx,(int)nrect.miny); + optimizedbacksurf.blit_to(p); + } + }else + { + if(!context.accelerated_render(surface,quality,renddesc,&stageone)) + return false; + } + + if(cb && !cb->amount_complete(10000,10001+renddesc.get_h())) return false; + + if(feather && quality != 10) + { + //we have to blur rather than be crappy + + //so make a separate surface + RendDesc workdesc(renddesc); + + etl::surface shapesurface; + + //the expanded size = 1/2 the size in each direction rounded up + int halfsizex = (int) (abs(feather*.5/pw) + 3), + halfsizey = (int) (abs(feather*.5/ph) + 3); + + //expand by 1/2 size in each direction on either side + switch(blurtype) + { + case Blur::DISC: + case Blur::BOX: + case Blur::CROSS: + { + workdesc.set_subwindow(-max(1,halfsizex),-max(1,halfsizey),w+2*max(1,halfsizex),h+2*max(1,halfsizey)); + break; + } + case Blur::FASTGAUSSIAN: + { + if(quality < 4) + { + halfsizex*=2; + halfsizey*=2; + } + workdesc.set_subwindow(-max(1,halfsizex),-max(1,halfsizey),w+2*max(1,halfsizex),h+2*max(1,halfsizey)); + break; + } + case Blur::GAUSSIAN: + { + #define GAUSSIAN_ADJUSTMENT (0.05) + Real pw = (Real)workdesc.get_w()/(workdesc.get_br()[0]-workdesc.get_tl()[0]); + Real ph = (Real)workdesc.get_h()/(workdesc.get_br()[1]-workdesc.get_tl()[1]); + + pw=pw*pw; + ph=ph*ph; + + halfsizex = (int)(abs(pw)*feather*GAUSSIAN_ADJUSTMENT+0.5); + halfsizey = (int)(abs(ph)*feather*GAUSSIAN_ADJUSTMENT+0.5); + + halfsizex = (halfsizex + 1)/2; + halfsizey = (halfsizey + 1)/2; + workdesc.set_subwindow( -halfsizex, -halfsizey, w+2*halfsizex, h+2*halfsizey ); + + break; + } + } + + shapesurface.set_wh(workdesc.get_w(),workdesc.get_h()); + shapesurface.clear(); + + //render the shape + if(!render_shape(&shapesurface,quality,workdesc,&stagetwo))return false; + + //blur the image + Blur(feather,feather,blurtype,&stagethree)(shapesurface,workdesc.get_br()-workdesc.get_tl(),shapesurface); + + //blend with stuff below it... + unsigned int u = halfsizex, v = halfsizey, x = 0, y = 0; + for(y = 0; y < h; y++,v++) + { + u = halfsizex; + for(x = 0; x < w; x++,u++) + { + float a = shapesurface[v][u]; + if(a) + { + //a = floor(a*255+0.5f)/255; + (*surface)[y][x]=Color::blend(color,(*surface)[y][x],a*get_amount(),get_blend_method()); + } + //else (*surface)[y][x] = worksurface[v][u]; + } + } + + //we are done + if(cb && !cb->amount_complete(100,100)) + { + synfig::warning("Layer_Shape: could not set amount complete"); + return false; + } + + return true; + }else + { + //might take out to reduce code size + return render_shape(surface,true,quality,renddesc,&stagetwo); + } + +} + +bool +Layer_Shape::render_shape(Surface *surface,bool useblend,int /*quality*/, + const RendDesc &renddesc, ProgressCallback *cb)const +{ + int tmp(0); + + SuperCallback progress(cb,0,renddesc.get_h(),renddesc.get_h()); + + // If our amount is set to zero, no need to render anything + if(!get_amount()) + return true; + + //test new polygon renderer + // Build edge table + // Width and Height of a pixel + const int w = renddesc.get_w(); + const int h = renddesc.get_h(); + const Real pw = renddesc.get_w()/(renddesc.get_br()[0]-renddesc.get_tl()[0]); + const Real ph = renddesc.get_h()/(renddesc.get_br()[1]-renddesc.get_tl()[1]); + + const Point tl = renddesc.get_tl(); + + Vector tangent (0,0); + + PolySpan span; + + // if the pixels are zero sized then we're too zoomed out to see anything + if (pw == 0 || ph == 0) + return true; + + //optimization for tessellating only inside tiles + span.window.minx = 0; + span.window.miny = 0; + span.window.maxx = w; + span.window.maxy = h; + + //pointers for processing the bytestream + const char *current = &bytestream[0]; + const char *end = &bytestream[bytestream.size()]; + + int operation = Primitive::NONE; + int number = 0; + int curnum; + + Primitive *curprim; + Point *data; + + Real x,y,x1,y1,x2,y2; + + + while(current < end) + { + tmp++; + + try { + + //get the op code safely + curprim = (Primitive *)current; + + //advance past indices + current += sizeof(Primitive); + if(current > end) + { + warning("Layer_Shape::accelerated_render - Error in the byte stream, not enough space for next declaration"); + return false; + } + + //get the relevant data + operation = curprim->operation; + number = curprim->number; + + if(operation == Primitive::END) + break; + + if(operation == Primitive::CLOSE) + { + if(span.notclosed()) + { + tangent[0] = span.close_x - span.cur_x; + tangent[1] = span.close_y - span.cur_y; + span.close(); + } + continue; + } + + data = (Point*)current; + current += sizeof(Point)*number; + + //check data positioning + if(current > end) + { + warning("Layer_Shape::accelerated_render - Error in the byte stream, in sufficient data space for declared number of points"); + return false; + } + + } catch(...) { synfig::error("Layer_Shape::render_shape()1: Caught an exception after %d loops, rethrowing...", tmp); throw; } + + //transfer all the data - RLE optimized + for(curnum=0; curnum < number;) + { + switch(operation) + { + case Primitive::MOVE_TO: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + if(curnum == 0) + { + span.move_to(x,y); + + tangent[0] = 0; + tangent[1] = 0; + } + else + { + tangent[0] = x - span.cur_x; + tangent[1] = y - span.cur_y; + + span.line_to(x,y); + } + + curnum++; //only advance one point + + break; + } + + case Primitive::LINE_TO: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + tangent[0] = x - span.cur_x; + tangent[1] = y - span.cur_y; + + span.line_to(x,y); + curnum++; + break; + } + + case Primitive::CONIC_TO: + { + x = data[curnum+1][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+1][1]; + y = (y - tl[1] + origin[1])*ph; + + x1 = data[curnum][0]; + x1 = (x1 - tl[0] + origin[0])*pw; + y1 = data[curnum][1]; + y1 = (y1 - tl[1] + origin[1])*ph; + + tangent[0] = 2*(x - x1); + tangent[1] = 2*(y - y1); + + span.conic_to(x1,y1,x,y); + curnum += 2; + break; + } + + case Primitive::CONIC_TO_SMOOTH: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + x1 = span.cur_x + tangent[0]/2; + y1 = span.cur_y + tangent[1]/2; + + tangent[0] = 2*(x - x1); + tangent[1] = 2*(y - y1); + + span.conic_to(x1,y1,x,y); + curnum ++; + + break; + } + + case Primitive::CUBIC_TO: + { + x = data[curnum+2][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+2][1]; + y = (y - tl[1] + origin[1])*ph; + + x2 = data[curnum+1][0]; + x2 = (x2 - tl[0] + origin[0])*pw; + y2 = data[curnum+1][1]; + y2 = (y2 - tl[1] + origin[1])*ph; + + x1 = data[curnum][0]; + x1 = (x1 - tl[0] + origin[0])*pw; + y1 = data[curnum][1]; + y1 = (y1 - tl[1] + origin[1])*ph; + + tangent[0] = 2*(x - x2); + tangent[1] = 2*(y - y2); + + span.cubic_to(x1,y1,x2,y2,x,y); + curnum += 3; + + break; + } + + case Primitive::CUBIC_TO_SMOOTH: + { + x = data[curnum+1][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+1][1]; + y = (y - tl[1] + origin[1])*ph; + + x2 = data[curnum][0]; + x2 = (x2 - tl[0] + origin[0])*pw; + y2 = data[curnum][1]; + y2 = (y2 - tl[1] + origin[1])*ph; + + x1 = span.cur_x + tangent[0]/3.0; + y1 = span.cur_y + tangent[1]/3.0; + + tangent[0] = 2*(x - x2); + tangent[1] = 2*(y - y2); + + span.cubic_to(x1,y1,x2,y2,x,y); + curnum += 2; + + break; + } + } + } + } + + //sort the bastards so we can render everything + span.sort_marks(); + + return render_polyspan(surface, span, + useblend?get_blend_method():Color::BLEND_STRAIGHT, + useblend?get_amount():1.0); +} + +bool +Layer_Shape::render_shape(etl::surface *surface,int /*quality*/, + const RendDesc &renddesc, ProgressCallback */*cb*/)const +{ + // If our amount is set to zero, no need to render anything + if(!get_amount()) + return true; + + //test new polygon renderer + // Build edge table + // Width and Height of a pixel + const int w = renddesc.get_w(); + const int h = renddesc.get_h(); + const Real pw = renddesc.get_w()/(renddesc.get_br()[0]-renddesc.get_tl()[0]); + const Real ph = renddesc.get_h()/(renddesc.get_br()[1]-renddesc.get_tl()[1]); + + const Point tl = renddesc.get_tl(); + + Vector tangent (0,0); + + PolySpan span; + + //optimization for tessellating only inside tiles + span.window.minx = 0; + span.window.miny = 0; + span.window.maxx = w; + span.window.maxy = h; + + //pointers for processing the bytestream + const char *current = &bytestream[0]; + const char *end = &bytestream[bytestream.size()]; + + int operation = Primitive::NONE; + int number = 0; + int curnum; + + Primitive *curprim; + Point *data; + + Real x,y,x1,y1,x2,y2; + + while(current < end) + { + //get the op code safely + curprim = (Primitive *)current; + + //advance past indices + current += sizeof(Primitive); + if(current > end) + { + warning("Layer_Shape::accelerated_render - Error in the byte stream, not enough space for next declaration"); + return false; + } + + //get the relevant data + operation = curprim->operation; + number = curprim->number; + + if(operation == Primitive::END) + break; + + if(operation == Primitive::CLOSE) + { + if(span.notclosed()) + { + tangent[0] = span.close_x - span.cur_x; + tangent[1] = span.close_y - span.cur_y; + span.close(); + } + continue; + } + + data = (Point*)current; + current += sizeof(Point)*number; + + //check data positioning + if(current > end) + { + warning("Layer_Shape::accelerated_render - Error in the byte stream, in sufficient data space for declared number of points"); + return false; + } + + //transfer all the data + for(curnum=0; curnum < number;) + { + switch(operation) + { + case Primitive::MOVE_TO: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + if(curnum == 0) + { + span.move_to(x,y); + + tangent[0] = 0; + tangent[1] = 0; + } + else + { + tangent[0] = x - span.cur_x; + tangent[1] = y - span.cur_y; + + span.line_to(x,y); + } + + curnum++; //only advance one point + + break; + } + + case Primitive::LINE_TO: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + tangent[0] = x - span.cur_x; + tangent[1] = y - span.cur_y; + + span.line_to(x,y); + curnum++; + break; + } + + case Primitive::CONIC_TO: + { + x = data[curnum+1][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+1][1]; + y = (y - tl[1] + origin[1])*ph; + + x1 = data[curnum][0]; + x1 = (x1 - tl[0] + origin[0])*pw; + y1 = data[curnum][1]; + y1 = (y1 - tl[1] + origin[1])*ph; + + tangent[0] = 2*(x - x1); + tangent[1] = 2*(y - y1); + + span.conic_to(x1,y1,x,y); + curnum += 2; + break; + } + + case Primitive::CONIC_TO_SMOOTH: + { + x = data[curnum][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum][1]; + y = (y - tl[1] + origin[1])*ph; + + x1 = span.cur_x + tangent[0]/2; + y1 = span.cur_y + tangent[1]/2; + + tangent[0] = 2*(x - x1); + tangent[1] = 2*(y - y1); + + span.conic_to(x1,y1,x,y); + curnum ++; + + break; + } + + case Primitive::CUBIC_TO: + { + x = data[curnum+2][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+2][1]; + y = (y - tl[1] + origin[1])*ph; + + x2 = data[curnum+1][0]; + x2 = (x2 - tl[0] + origin[0])*pw; + y2 = data[curnum+1][1]; + y2 = (y2 - tl[1] + origin[1])*ph; + + x1 = data[curnum][0]; + x1 = (x1 - tl[0] + origin[0])*pw; + y1 = data[curnum][1]; + y1 = (y1 - tl[1] + origin[1])*ph; + + tangent[0] = 2*(x - x2); + tangent[1] = 2*(y - y2); + + span.cubic_to(x1,y1,x2,y2,x,y); + curnum += 3; + + break; + } + + case Primitive::CUBIC_TO_SMOOTH: + { + x = data[curnum+1][0]; + x = (x - tl[0] + origin[0])*pw; + y = data[curnum+1][1]; + y = (y - tl[1] + origin[1])*ph; + + x2 = data[curnum][0]; + x2 = (x2 - tl[0] + origin[0])*pw; + y2 = data[curnum][1]; + y2 = (y2 - tl[1] + origin[1])*ph; + + x1 = span.cur_x + tangent[0]/3.0; + y1 = span.cur_y + tangent[1]/3.0; + + tangent[0] = 2*(x - x2); + tangent[1] = 2*(y - y2); + + span.cubic_to(x1,y1,x2,y2,x,y); + curnum += 2; + + break; + } + } + } + } + + //sort the bastards so we can render everything + span.sort_marks(); + + return render_polyspan(surface, span); +} + +Rect +Layer_Shape::get_bounding_rect()const +{ + if(invert) + return Rect::full_plane(); + + if (edge_table->initaabb) + return Rect::zero(); + + Rect bounds(edge_table->aabb+origin); + bounds.expand(max((bounds.get_min() - bounds.get_max()).mag()*0.01, + feather)); + + return bounds; +}