ref: 83519f11fd26cd605dba45d9ce1fc0f39d467d40
dir: /rast.c/
/*
* Recommended reading:
*
* Wavelet Rasterization
* (2011) J. Manson, S.Schaefer
*
* A Simple and Fast Line-Clipping Method as a Scratch Extension for Computer Graphics Education
* (2019) Dimitrios Matthes, Vasileios Drakopoulos
*
* Clipping of Bézier Curves
* (1985) Fuhua Cheng, Chi-Cheng Lin
*
* On the numerical condition of polynomials in Bernstein form
* (1987) R. T. Farouki, V. T. Rajan
*
* Numerical Condition of Polynomials in Different Forms
* (2001) Hong Zhang
*
* Computer Aided Geometric Design
* (2017) Thomas W. Sederberg
*/
#include "otfsys.h"
#include "otf.h"
typedef struct SegQ SegQ;
typedef struct SegC SegC;
typedef struct Spt Spt;
typedef double Sval;
struct Spt {
Sval x;
Sval y;
};
struct SegQ {
union {
struct {
union {
Spt p0;
Sval v0[2];
};
union {
Spt p1;
Sval v1[2];
};
union {
Spt p2;
Sval v2[2];
};
};
Sval v[3*2];
};
};
/* the divisions when calculating coefficients are reduced,
* thus pixel value range is [0.0-QBZR_PIX_SCALE] instead of [0.0-1.0]
*/
#define QBZR_PIX_SCALE 4.0
#define MAXCOMPONENTS 16
#define ε 1.0e-9
#define ₀(v) ((v) > -ε && (v) < ε)
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
static int
closest²(int v)
{
v--;
v |= v>>1;
v |= v>>2;
v |= v>>4;
v |= v>>8;
v |= v>>16;
return v + 1;
}
static int
qslv(Sval *qs, Sval a, Sval b, Sval c)
{
Sval p, q, d;
int n;
if(₀(a)){
if(₀(b))
return 0;
qs[0] = -c/b;
return qs[0] > 0 && qs[0] < 1;
}
p = b/(2.0*a);
q = c/a;
d = p*p - q;
if(₀(d)){
qs[0] = -p;
return qs[0] > 0 && qs[0] < 1;
}
if(d < 0.0)
return 0;
d = sqrt(d);
n = 0;
if((q = -d-p) > 0 && q < 1)
qs[n++] = q;
if((q = d-p) > 0 && q < 1)
qs[n++] = q;
return n;
}
static int
Svalcmp(void *a_, void *b_)
{
Sval a = *(Sval*)a_, b = *(Sval*)b_;
return (a > b) - (a < b);
}
static int
qKL(SegQ *s₀, int jj, Sval px, Sval py, Sval *K, Sval *L)
{
Sval qs[1+4*2+1];
Sval a, b, c, w, v;
SegQ s, z;
int i, j, n, r;
/* transform */
for(i = 0; i < nelem(s.v); i += 2){
s.v[i+0] = s₀->v[i+0]*jj - px;
s.v[i+1] = s₀->v[i+1]*jj - py;
}
/* FIXME would it make things faster to do proper convex hull test here? */
if(s.p0.x <= 0 && s.p1.x <= 0 && s.p2.x <= 0 ||
s.p0.x >= 1 && s.p1.x >= 1 && s.p2.x >= 1 ||
s.p0.y <= 0 && s.p1.y <= 0 && s.p2.y <= 0 ||
s.p0.y >= 1 && s.p1.y >= 1 && s.p2.y >= 1)
return 0;
#define e(t,a) (s.p0.a*(1-t)*(1-t) + 2*s.p1.a*(1-t)*t + s.p2.a*t*t)
#define within(v) ((w = e(v, x)) >= -ε && w <= 1+ε && (w = e(v, y)) >= -ε && w <= 1+ε)
/* clip to quadtree cell */
n = 0;
if(s.p0.x >= 0 && s.p0.x <= 1 && s.p0.y >= 0 && s.p0.y <= 1)
qs[n++] = 0;
for(i = 0; i < 2; i++){
c = s.v0[i];
a = c - 2*s.v1[i] + s.v2[i];
b = 2*(s.v1[i] - c);
n += qslv(qs+n, a, b, c);
n += qslv(qs+n, a, b, c-1);
}
qsort(qs, n, sizeof(Sval), Svalcmp);
if(s.p2.x >= 0 && s.p2.x <= 1 && s.p2.y >= 0 && s.p2.y <= 1)
qs[n++] = 1;
j = 0;
for(i = 0; i < n; i++){
v = qs[i];
if(within(v))
qs[j++] = v;
}
#undef within
/* remove duplicates */
n = 1;
for(i = 1; i < j; i++){
v = qs[i];
if(v != qs[i-1])
qs[n++] = v;
}
/* calculate K & L on subsections */
r = 0;
for(i = 0; i < n-1; i++){
a = qs[i];
b = qs[i+1];
z.p0.y = e(a, y);
z.p2.y = e(b, y);
if(₀(z.p0.y) && ₀(z.p2.y))
continue;
z.p0.x = e(a, x);
z.p2.x = e(b, x);
if(₀(z.p0.x) && ₀(z.p2.x))
continue;
#undef e
c = 1 - a;
v = (b - a)/c;
z.p1.x = (1-v)*z.p0.x + v*(c*s.p1.x + a*s.p2.x);
z.p1.y = (1-v)*z.p0.y + v*(c*s.p1.y + a*s.p2.y);
K[0] += z.p2.y - z.p0.y;
K[1] += z.p0.x - z.p2.x;
L[0] += 3*z.p2.x*z.p2.y
+ 2*z.p2.y*z.p1.x
- 2*z.p2.x*z.p1.y
+ z.p2.y*z.p0.x
+ 2*z.p1.y*z.p0.x
- z.p0.y*( z.p2.x + 2*z.p1.x + 3*z.p0.x);
L[1] += 2*z.p1.y*z.p0.x
+ z.p2.y*(2*z.p1.x + z.p0.x)
- 2*z.p1.x*z.p0.y
+ 3*z.p0.x*z.p0.y
- z.p2.x*(3*z.p2.y + 2*z.p1.y + z.p0.y);
r = 1;
}
if(r){
L[0] /= 6.0;
L[1] /= 6.0;
}
return r;
}
static int
lKL(SegQ *s₀, int jj, Sval px, Sval py, Sval *K, Sval *L)
{
Sval x₁, x₂, y₁, y₂, α₁, α₂, β₁, β₂;
/* transform */
α₁ = x₁ = s₀->p0.x*jj - px;
β₁ = y₁ = s₀->p0.y*jj - py;
α₂ = x₂ = s₀->p1.x*jj - px;
β₂ = y₂ = s₀->p1.y*jj - py;
if(α₁ < 0){
if(α₂ < 0) return 0;
α₁ = 0;
β₁ = (y₂-y₁)/(x₂-x₁)*(0-x₁)+y₁;
}else if(α₁ >= 1){
if(α₂ >= 1) return 0;
α₁ = 1;
β₁ = (y₂-y₁)/(x₂-x₁)*(1-x₁)+y₁;
}
if(α₂ < 0){
α₂ = 0;
β₂ = (y₂-y₁)/(x₂-x₁)*(0-x₁)+y₁;
}else if(α₂ > 1){
α₂ = 1;
β₂ = (y₂-y₁)/(x₂-x₁)*(1-x₁)+y₁;
}
if(β₁ < 0){
if(β₂ < 0) return 0;
β₁ = 0;
α₁ = (x₂-x₁)/(y₂-y₁)*(0-y₁)+x₁;
}else if(β₁ >= 1){
if(β₂ >= 1) return 0;
β₁ = 1;
α₁ = (x₂-x₁)/(y₂-y₁)*(1-y₁)+x₁;
}
if(β₂ < 0){
β₂ = 0;
α₂ = (x₂-x₁)/(y₂-y₁)*(0-y₁)+x₁;
}else if(β₂ > 1){
β₂ = 1;
α₂ = (x₂-x₁)/(y₂-y₁)*(1-y₁)+x₁;
}
K[0] += β₂ - β₁;
K[1] += α₁ - α₂;
L[0] += (β₂ - β₁)*(α₁ + α₂)/2;
L[1] += (α₁ - α₂)*(β₁ + β₂)/2;
return 1;
}
static u64int
qCxy(SegQ *s, int ns, int jj, int px, int py, Sval *c, u64int *m, u64int tm)
{
int (*f)(SegQ*, int, Sval, Sval, Sval*, Sval*);
u64int tx₀, tx₁, z, all;
Sval K[4][2], L[4][2];
int i;
jj *= 2;
px *= 2;
py *= 2;
if(jj > 8){
tx₀ = 0;
tx₁ = 0;
}else{
tx₀ = 1ULL<<(px*jj + py);
tx₁ = 1ULL<<((px+1)*jj + py);
}
all = 0;
for(i = 0; i < ns; i++, s++){
if((m[i] & tm) == 0)
continue;
K[0][0] = K[0][1] = 0;
K[1][0] = K[1][1] = 0;
K[2][0] = K[2][1] = 0;
K[3][0] = K[3][1] = 0;
L[0][0] = L[0][1] = 0;
L[1][0] = L[1][1] = 0;
L[2][0] = L[2][1] = 0;
L[3][0] = L[3][1] = 0;
if(s->p1.x == s->p2.x && s->p1.y == s->p2.y)
f = lKL;
else
f = qKL;
z = 0;
if(tx₀ == 0){
z |= f(s, jj, px+0, py+0, K[0], L[0]);
z |= f(s, jj, px+0, py+1, K[1], L[1]);
z |= f(s, jj, px+1, py+0, K[2], L[2]);
z |= f(s, jj, px+1, py+1, K[3], L[3]);
}else{
z = 0;
if(f(s, jj, px+0, py+0, K[0], L[0]))
z |= tx₀;
if(f(s, jj, px+0, py+1, K[1], L[1]))
z |= tx₀<<1;
if(f(s, jj, px+1, py+0, K[2], L[2]))
z |= tx₁;
if(f(s, jj, px+1, py+1, K[3], L[3]))
z |= tx₁<<1;
m[ns+i] |= z;
all |= z;
}
if(z != 0){
c[0] += L[0][1] + L[2][1] + K[1][1] - L[1][1] + K[3][1] - L[3][1];
c[1] += L[0][0] + L[1][0] + K[2][0] - L[2][0] + K[3][0] - L[3][0];
c[2] += L[0][0] - L[1][0] + K[2][0] - L[2][0] - K[3][0] + L[3][0];
}
}
return all;
}
static int
qbzr(SegQ *seg, int ns, int dw, int dh, Sval *p)
{
int ₂ju, ₂j, w, h, i, j, ju, px, py;
Sval r₂ju, s₀, s, *c, *c₀x, *c₀y, *cs, zx, zy;
u64int *ma, *mb, all, nall;
₂ju = closest²(dh);
if(₂ju < (j = closest²(dw)))
₂ju = j;
r₂ju = 1.0 / ₂ju;
for(ju = 1; (1<<ju) < ₂ju; ju++);
/* scale */
for(i = 0; i < ns; i++){
for(j = 0; j < nelem(seg->v); j++)
seg[i].v[j] *= r₂ju;
}
/* calculate area */
s₀ = 0;
for(i = 0; i < ns; i++){
#define det(a,b) (a.x*b.y - a.y*b.x)
#define cL(s) det(s.p0, s.p2)/2.0
#define cQ(s) (2*det(s.p0, s.p1) + 2*det(s.p1, s.p2) + det(s.p0, s.p2))/6.0
if(seg[i].p1.x == seg[i].p2.x && seg[i].p1.y == seg[i].p2.y)
s₀ -= cL(seg[i]);
else
s₀ -= cQ(seg[i]);
#undef det
#undef cL
#undef cQ
}
s₀ *= QBZR_PIX_SCALE;
/* working space:
* quick is-segment-in-the-cell tests (two 64-bit nums per segment)
* wavelet coefficients (count by geometric series, 3 per "pixel" of every zoom level)
*/
j = 2*ns*sizeof(*ma) + (1-(4<<2*(ju-1)))/(1-4)*3*sizeof(*c);
/* current zoom level bitmasks for quick tests */
if((ma = calloc(1, j)) == nil){
werrstr("no memory");
return -1;
}
/* next zoom level bitmasks */
mb = ma + ns;
/* first (1x1) zoom level - all segments are there */
for(i = 0; i < ns; i++)
ma[i] = 1;
/* coefficients */
c = cs = (Sval*)(ma + 2*ns);
/* bitmasks used per-segment are united (bitwise OR) to skip entire empty cells */
all = 1;
nall = 0;
/* first few loops build the test bitmasks: 1x1 -> 4x4 -> 8x8 */
for(j = 0, ₂j = 1; j < ju && j <= 3; j++, ₂j <<= 1){
for(px = 0; px < ₂j; px++){
for(py = 0; py < ₂j; py++, c += 3){
u64int tm = 1ULL<<(px*₂j + py);
if(all & tm)
nall |= qCxy(seg, ns, ₂j, px, py, c, ma, tm);
}
}
if(j != 3){
for(i = 0; i < ns; i++){
ma[i] = mb[i];
mb[i] = 0;
}
all = nall;
nall = 0;
}
}
/* no more bitmasks, just calculate coefficients */
for(; j < ju; j++, ₂j <<= 1){
for(px = 0; px < ₂j; px++){
/* bitmasks are expanded only up to 8x8 (64 bits)
* so use that for more coarse testing
*/
u64int tm₀ = 1ULL<<((px>>(j-3))*8);
for(py = 0; py < ₂j; py++, c += 3){
u64int tm = tm₀ << (py>>(j-3));
if(all & tm)
qCxy(seg, ns, ₂j, px, py, c, ma, tm);
}
}
}
/* reverse Y axis (opentype coord vs plan 9) */
for(h = dh-1; h >= 0; h--){
for(w = 0; w < dw; w++){
c = cs;
s = s₀;
for(₂j = 1; ₂j < ₂ju; ₂j *= 2){
zx = r₂ju * ₂j * w;
c₀x = c;
px = (int)zx;
zx -= px;
c += px*3*₂j;
for(; zx >= 0; px++, zx--, c = c₀y + 3*₂j){
c₀y = c;
zy = r₂ju * ₂j * h;
py = (int)zy;
zy -= py;
c += 3*py;
for(; zy >= 0; py++, zy--, c += 3){
switch((zy < 0.5)<<1 | (zx < 0.5)){
case 0: s += +c[0] + c[1] - c[2]; break;
case 1: s += +c[0] - c[1] + c[2]; break;
case 2: s += -c[0] + c[1] + c[2]; break;
default: s += -c[0] - c[1] - c[2]; break;
}
}
}
c = c₀x + 3*₂j*₂j;
}
*p++ = s;
}
}
free(ma);
return 0;
}
static SegQ *
addpoints(SegQ *s, Spt *pts₀, int max, Glyf *g)
{
Point *p₀, *p, *prev;
Spt *e, *pts;
int k, npts;
for(k = 0; k < g->numberOfContours; k++){
npts = g->simple->endPtsOfContours[k]+1;
if(k > 0)
npts -= g->simple->endPtsOfContours[k-1]+1;
if(npts < 2)
continue;
e = pts = pts₀;
p₀ = p = g->simple->points + (k > 0 ? g->simple->endPtsOfContours[k-1]+1 : 0);
prev = p₀+npts-1;
if(!p->onCurve){
/* fun stuff */
if(max-- < 1)
goto noplace;
if(prev->onCurve)
*e = (Spt){prev->x, prev->y};
else
*e = (Spt){(Sval)(p->x + prev->x)/2, (Sval)(p->y + prev->y)/2};
e++;
}
for(prev = nil; p < p₀+npts; prev = p, p++, e++){
if(prev != nil && p->onCurve == prev->onCurve){
/* more fun stuff */
if(max-- < 1)
goto noplace;
if(p->onCurve) /* straight line */
*e = (Spt){p->x, p->y};
else /* a point in the middle */
*e = (Spt){(Sval)(p->x + prev->x)/2, (Sval)(p->y + prev->y)/2};
e++;
}
if(max-- < 1)
goto noplace;
*e = (Spt){p->x, p->y};
}
if(p[-1].onCurve){ /* close with a straight line */
if(max-- < 1)
goto noplace;
*e++ = *pts;
}
if(max-- < 1)
goto noplace;
*e++ = *pts;
for(; pts <= e-3; pts += 2, s++){
s->v0[0] = pts[0].x;
s->v0[1] = pts[0].y;
s->v1[0] = pts[1].x;
s->v1[1] = pts[1].y;
s->v2[0] = pts[2].x;
s->v2[1] = pts[2].y;
}
}
return s;
noplace:
werrstr("points don't fit");
return nil;
}
static int
glyfmaxnpts(Glyf *g)
{
int k, npts, d;
npts = 0;
for(k = 0; k < g->numberOfContours; k++){
d = g->simple->endPtsOfContours[k]+1;
if(k > 0)
d -= g->simple->endPtsOfContours[k-1]+1;
if(d > 1)
npts += 1+2*d+2; /* two extra to close off the contour */
}
return npts < 2 ? 0 : npts;
}
int
otfdrawglyf(Otf *o, Glyf *g, double ppem, int gap, GlyfImage *im)
{
int i, j, maxptstotal, maxpts, ngs, w, h, r, npx, baseline;
Glyf *gs[MAXCOMPONENTS];
ComponentGlyph *cg;
Sval scale, offY;
SegQ *s₀, *s, *p;
Spt *pts;
Sval *fp;
u8int *b;
r = -1;
ngs = 0;
pts = nil;
fp = nil;
if(g->simple != nil){
gs[ngs++] = g;
}else{
for(cg = g->component; cg != nil && ngs < nelem(gs); cg = cg->next, ngs++){
if((gs[ngs] = otfglyf(o, cg->glyphIndex)) == nil)
goto done;
}
}
for(maxptstotal = maxpts = i = 0; i < ngs; i++){
j = glyfmaxnpts(gs[i]);
maxptstotal += j;
if(maxpts < j)
maxpts = j;
}
/*
FIXME metrics (lsb+rsb)
for(cg = g->component, i = 0; cg != nil && i < ngs; cg = cg->next, i++){
if(cg->flags & CGLYPH_FL_METRICS){
...
break;
}
}
*/
Sval xMin = 99999, yMin = 99999, xMax = -99999, yMax = -99999;
scale = ppem / otfupem(o);
pts = calloc(1, maxpts*sizeof(*pts) + maxptstotal/2*sizeof(*s));
p = s = s₀ = (SegQ*)(pts + maxpts);
cg = g->component;
for(i = 0; i < ngs; i++){
Sval dx = 0, dy = 0, gscaleX = scale, gscaleY = scale;
if(cg != nil){
if(cg->flags & CGLYPH_FL_SIGNED_XY){
dx = cg->dx;
dy = cg->dy;
}else{
dx = 0;
dy = 0;
}
if(cg->flags & CGLYPH_FL_SCALE_XY){
gscaleX *= cg->scaleX;
gscaleY *= cg->scaleY;
}
if(cg->flags & CGLYPH_FL_SCALED_COMPONENT_OFFSET){
dx *= gscaleX;
dy *= gscaleY;
}else{
dx *= scale;
dy *= scale;
}
/* FIXME rounding
if(cg->flags & CGLYPH_FL_ROUND_TO_GRID_XY){
...
}
*/
cg = cg->next;
}
if((s = addpoints(s, pts, maxpts, gs[i])) == nil)
goto done;
for(; p < s; p++){
p->p0.x = p->p0.x*gscaleX + dx;
xMin = min(xMin, p->p0.x);
xMax = max(xMax, p->p0.x);
p->p0.y = p->p0.y*gscaleY + dy;
yMin = min(yMin, p->p0.y);
yMax = max(yMax, p->p0.y);
p->p1.x = p->p1.x*gscaleX + dx;
xMin = min(xMin, p->p1.x);
xMax = max(xMax, p->p1.x);
p->p1.y = p->p1.y*gscaleY + dy;
yMin = min(yMin, p->p1.y);
yMax = max(yMax, p->p1.y);
p->p2.x = p->p2.x*gscaleX + dx;
xMin = min(xMin, p->p2.x);
xMax = max(xMax, p->p2.x);
p->p2.y = p->p2.y*gscaleY + dy;
yMin = min(yMin, p->p2.y);
yMax = max(yMax, p->p2.y);
}
}
if(s == s₀){
w = h = 1;
baseline = 0;
}else{
xMin -= gap;
yMin -= gap;
xMax += gap;
yMax += gap;
/* height+baseline is where it is in the image */
baseline = floor(yMin);
offY = -baseline;
yMax += offY;
w = ceil(xMax - xMin);
h = ceil(yMax);
for(p = s₀; p != s; p++){
p->p0.x -= xMin;
p->p1.x -= xMin;
p->p2.x -= xMin;
p->p0.y += offY;
p->p1.y += offY;
p->p2.y += offY;
}
}
npx = w*h;
fp = malloc(npx*sizeof(*fp));
if(qbzr(s₀, s-s₀, w, h, fp) == 0){
b = (u8int*)fp;
for(i = 0; i < npx; i++)
b[i] = 255 - (fp[i] < QBZR_PIX_SCALE/255 ? 0 : (fp[i] >= QBZR_PIX_SCALE ? 255 : fp[i]/QBZR_PIX_SCALE*255));
if((b = realloc(fp, npx)) != nil){
im->b = b;
im->w = w;
im->h = h;
im->baseline = baseline;
fp = nil;
r = 0;
}
}
done:
for(i = 0; i < ngs; i++){
if(gs[i] != g)
free(gs[i]);
}
free(pts);
free(fp);
return r;
}