/* === 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
** Copyright (c) 2012-2013 Carlos López
**
** 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 <config.h>
#endif
#include "layer_shape.h"
#include <synfig/string.h>
#include <synfig/time.h>
#include <synfig/context.h>
#include <synfig/paramdesc.h>
#include <synfig/renddesc.h>
#include <synfig/surface.h>
#include <synfig/value.h>
#include <synfig/valuenode.h>
#include <float.h>
#include <synfig/blur.h>
#include <synfig/cairo_renddesc.h>
#include <synfig/curve_helper.h>
#include <vector>
#include <deque>
#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<Point> 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<Point>::const_iterator end = pointlist.end();
vector<Point>::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<Point>::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<Point> pointlist; //run length - p0, p1, p2, p3 = p10, p11, p12, p13 = p20 ...
vector<char> 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;
}
/* TODO: function not finished - no return value. To remove?
//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<Point>::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<MonoSegment> segs; //monotonically increasing
vector<CurveArray> 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<MonoSegment>::const_iterator s = segs.begin();
vector<CurveArray>::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(): y(), x(), cover(), area() {}
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<int> ContextRect;
class Layer_Shape::PolySpan
{
public:
typedef vector<PenMark> 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)
{
if (covers.size() == covers.capacity())
covers.reserve(covers.size() + 1024*1024);
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),
param_color (Color::black()),
param_origin (Vector(0,0)),
param_invert (bool(false)),
param_antialias (bool(true)),
param_blurtype (int(Blur::FASTGAUSSIAN)),
param_feather (Real(0.0)),
param_winding_style (int(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_VALUE_PLUS(param_color,
{
Color color=param_color.get(Color());
if (color.get_a() == 0)
{
if (converted_blend_)
{
set_blend_method(Color::BLEND_ALPHA_OVER);
color.set_a(1);
}
else
transparent_color_ = true;
}
param_color.set(color);
}
);
IMPORT_VALUE(param_origin);
IMPORT_VALUE(param_invert);
IMPORT_VALUE(param_antialias);
IMPORT_VALUE_PLUS(param_feather,
{
Real feather=param_feather.get(Real());
if(feather<0)
{
feather=0;
param_feather.set(feather);
}
}
);
IMPORT_VALUE(param_blurtype);
IMPORT_VALUE(param_winding_style);
if(param=="offset" && param_origin.get_type() == value.get_type())
{
param_origin=value;
return true;
}
return Layer_Composite::set_param(param,value);
}
ValueBase
Layer_Shape::get_param(const String ¶m)const
{
EXPORT_VALUE(param_color);
EXPORT_VALUE(param_origin);
EXPORT_VALUE(param_invert);
EXPORT_VALUE(param_antialias);
EXPORT_VALUE(param_feather);
EXPORT_VALUE(param_blurtype);
EXPORT_VALUE(param_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
{
Color color=param_color.get(Color());
Point origin=param_origin.get(Point());
bool invert =param_invert.get(bool(true));
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<Layer_Shape*>(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<Layer_Shape*>(this);
}
return context.hit_check(p);
}
Color
Layer_Shape::get_color(Context context, const Point &p)const
{
Color color=param_color.get(Color());
Point origin=param_origin.get(Point());
bool invert =param_invert.get(bool(true));
int blurtype=param_blurtype.get(int());
Real feather=param_feather.get(Real());
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::value_type amount,
Color::BlendMethod blend_method,
const Color &color,
bool invert,
bool antialias,
WindingStyle winding_style ) const
{
Surface::alpha_pen p(surface->begin(), amount, 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<float> *surface, PolySpan &polyspan) const
{
bool invert =param_invert.get(bool(true));
bool antialias =param_antialias.get(bool(true));
WindingStyle winding_style=(WindingStyle)param_winding_style.get(int());
etl::surface<float>::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
{
Color color=param_color.get(Color());
Point origin=param_origin.get(Point());
bool invert =param_invert.get(bool(true));
int blurtype=param_blurtype.get(int());
Real feather=param_feather.get(Real());
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<float> 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::accelerated_cairorender(Context context,cairo_t *cr, int quality, const RendDesc &renddesc, ProgressCallback *cb)const
{
Color color=param_color.get(Color());
Point origin=param_origin.get(Point());
bool invert =param_invert.get(bool(true));
bool antialias =param_antialias.get(bool(true));
int blurtype=param_blurtype.get(int());
Real feather=param_feather.get(Real());
WindingStyle winding_style=(WindingStyle)param_winding_style.get(int());
// Grab the rgba values
const float r(color.get_r());
const float g(color.get_g());
const float b(color.get_b());
const float a(color.get_a());
// First render the context
if(!is_solid_color())
if(!context.accelerated_cairorender(cr,quality,renddesc,cb))
{
if(cb)
cb->error(strprintf(__FILE__"%d: Accelerated Cairo Renderer Failure",__LINE__));
return false;
}
if(feather && quality != 10)
{
cairo_surface_t* subimage;
RendDesc workdesc(renddesc);
// Untransform the render desc
if(!cairo_renddesc_untransform(cr, workdesc))
return false;
int halfsizex(0), halfsizey(0);
int w=workdesc.get_w(), h=workdesc.get_h();
const double wpw=(workdesc.get_br()[0]-workdesc.get_tl()[0])/w;
const double wph=(workdesc.get_br()[1]-workdesc.get_tl()[1])/h;
//the expanded size = 1/2 the size in each direction rounded up
halfsizex = (int) (abs(feather*.5/wpw) + 3),
halfsizey = (int) (abs(feather*.5/wph) + 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;
#undef GAUSSIAN_ADJUSTMENT
}
}
// New expanded workdesc values
const int ww=workdesc.get_w();
const int wh=workdesc.get_h();
const double wtlx=workdesc.get_tl()[0];
const double wtly=workdesc.get_tl()[1];
subimage=cairo_surface_create_similar(cairo_get_target(cr), CAIRO_CONTENT_COLOR_ALPHA, ww, wh);
cairo_t* subcr=cairo_create(subimage);
cairo_scale(subcr, 1/wpw, 1/wph);
cairo_translate(subcr, -wtlx, -wtly);
cairo_translate(subcr, origin[0], origin[1]);
switch(winding_style)
{
case WINDING_NON_ZERO:
cairo_set_fill_rule(subcr, CAIRO_FILL_RULE_WINDING);
break;
default:
cairo_set_fill_rule(subcr, CAIRO_FILL_RULE_EVEN_ODD);
break;
}
cairo_set_source_rgba(subcr, r, g, b, a);
if(invert)
cairo_paint(subcr);
// Draw the shape
if(!antialias)
cairo_set_antialias(subcr, CAIRO_ANTIALIAS_NONE);
if(invert)
cairo_set_operator(subcr, CAIRO_OPERATOR_CLEAR);
else
cairo_set_operator(subcr, CAIRO_OPERATOR_OVER);
Layer_Shape::shape_to_cairo(subcr);
cairo_clip(subcr);
cairo_paint(subcr);
if(!feather_cairo_surface(subimage, workdesc, quality))
{
cairo_surface_destroy(subimage);
cairo_destroy(subcr);
return false;
}
cairo_destroy(subcr);
cairo_save(cr);
cairo_translate(cr, wtlx, wtly);
cairo_scale(cr, wpw, wph);
cairo_set_source_surface(cr, subimage, 0, 0);
cairo_paint_with_alpha_operator(cr, get_amount(), get_blend_method());
cairo_restore(cr);
cairo_surface_destroy(subimage);
return true;
}
cairo_save(cr);
cairo_translate(cr, origin[0], origin[1]);
cairo_set_source_rgba(cr, r, g, b, a);
switch(winding_style)
{
case WINDING_NON_ZERO:
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_WINDING);
break;
default:
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
break;
}
if(!antialias)
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
if(invert)
{
cairo_push_group(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_rgba(cr, r, g, b, a);
cairo_paint(cr);
Layer_Shape::shape_to_cairo(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_clip(cr);
cairo_paint(cr);
cairo_pop_group_to_source(cr);
cairo_paint_with_alpha_operator(cr, get_amount(), get_blend_method());
}
else
{
Layer_Shape::shape_to_cairo(cr);
cairo_clip(cr);
cairo_paint_with_alpha_operator(cr, get_amount(), get_blend_method());
}
cairo_restore(cr);
return true;
}
////
//
bool
Layer_Shape::shape_to_cairo(cairo_t *cr)const
{
int tmp(0);
//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)
{
cairo_close_path(cr);
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];
y = data[curnum][1];
if(curnum == 0)
{
cairo_move_to(cr, x, y);
}
else
{
cairo_line_to(cr, x, y);
}
curnum++; //only advance one point
break;
}
case Primitive::LINE_TO:
{
x = data[curnum][0];
y = data[curnum][1];
cairo_line_to(cr, x, y);
synfig::info("line to x,y = %f, %f", 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;
// }
} // switch
} // for
} // while
return true;
}
//
bool
Layer_Shape::feather_cairo_surface(cairo_surface_t* surface, RendDesc renddesc, int quality)const
{
Color color=param_color.get(Color());
int blurtype=param_blurtype.get(int());
Real feather=param_feather.get(Real());
if(feather && quality!=10)
{
etl::surface<float> shapesurface;
shapesurface.set_wh(renddesc.get_w(),renddesc.get_h());
shapesurface.clear();
CairoSurface cairosurface(surface);
if(!cairosurface.map_cairo_image())
{
synfig::info("map cairo image failed");
return false;
}
// Extract the alpha values:
int x, y;
int h(renddesc.get_h()), w(renddesc.get_w());
float div=1.0/((float)(CairoColor::ceil));
for(y=0; y<h; y++)
for(x=0;x<w;x++)
shapesurface[y][x]=cairosurface[y][x].get_a()*div;
// Blue the alpha values
Blur(feather,feather,blurtype)(shapesurface, renddesc.get_br()-renddesc.get_tl(), shapesurface);
// repaint the cairosurface with the result
Color ccolor(color);
for(y=0; y<h; y++)
for(x=0;x<w;x++)
{
float a=shapesurface[y][x];
ccolor.set_a(a);
ccolor.clamped();
cairosurface[y][x]=CairoColor(ccolor).premult_alpha();
}
cairosurface.unmap_cairo_image();
}
return true;
}
bool
Layer_Shape::render_shape(Surface *surface,bool useblend,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;
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]);
// if the pixels are zero sized then we're too zoomed out to see anything
if (pw == 0 || ph == 0)
return true;
Matrix matrix(
Matrix().set_translate(param_origin.get(Point()))
* renddesc.get_transformation_matrix()
* Matrix().set_translate(-renddesc.get_tl())
* Matrix().set_scale(pw, ph)
);
int tmp(0);
SuperCallback progress(cb,0,renddesc.get_h(),renddesc.get_h());
//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();
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)
{
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
tangent[0] = x - span.cur_x;
tangent[1] = y - span.cur_y;
span.line_to(x,y);
curnum++;
break;
}
case Primitive::CONIC_TO:
{
matrix.get_transformed(x, y, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum+2][0], data[curnum+2][1]);
matrix.get_transformed(x2, y2, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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();
Color::value_type amount = useblend ? get_amount() : 1.0;
Color::BlendMethod blend_method = useblend ? get_blend_method() : Color::BLEND_STRAIGHT;
Color color = param_color.get(Color());
bool invert = param_invert.get(bool(true));
bool antialias = param_antialias.get(bool(true));
WindingStyle winding_style = (WindingStyle)param_winding_style.get(int());
if (useblend && get_blend_method() == Color::BLEND_STRAIGHT_ONTO)
{
Surface s;
s.set_wh(surface->get_w(), surface->get_h());
s.clear();
bool result = render_polyspan(
&s,
span,
1.0,
Color::BLEND_STRAIGHT,
color,
invert,
antialias,
winding_style );
Surface::alpha_pen p(surface->begin(), amount, Color::BLEND_STRAIGHT_ONTO);
s.blit_to(p, 0, 0, surface->get_w(), surface->get_h());
return result;
}
return render_polyspan(
surface,
span,
amount,
blend_method,
color,
invert,
antialias,
winding_style );
}
bool
Layer_Shape::render_shape(etl::surface<float> *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;
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]);
// if the pixels are zero sized then we're too zoomed out to see anything
if (pw == 0 || ph == 0)
return true;
Matrix matrix(
Matrix().set_translate(param_origin.get(Point()))
* renddesc.get_transformation_matrix()
* Matrix().set_translate(-renddesc.get_tl())
* Matrix().set_scale(pw, ph)
);
//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();
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
tangent[0] = x - span.cur_x;
tangent[1] = y - span.cur_y;
span.line_to(x,y);
curnum++;
break;
}
case Primitive::CONIC_TO:
{
matrix.get_transformed(x, y, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum+2][0], data[curnum+2][1]);
matrix.get_transformed(x2, y2, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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:
{
matrix.get_transformed(x, y, data[curnum+1][0], data[curnum+1][1]);
matrix.get_transformed(x1, y1, data[curnum][0], data[curnum][1]);
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
{
Point origin=param_origin.get(Point());
bool invert =param_invert.get(bool(true));
Real feather=param_feather.get(Real());
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;
}