shithub: fnt

ref: f9bb63633ce47c39a98d929a98b8392c98b07e87
dir: /rast.c/

View raw version
/*
 * 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 ε 1.0e-10

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;

#define ₀(x) (x > -ε && x < ε)
	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;
	}
#undef ₀

	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(fabs(z.p0.y-1) < ε && fabs(z.p2.y-1) < ε)
			continue;
		z.p0.x = e(a, x);
		z.p2.x = e(b, x);
		if(fabs(z.p0.x-1) < ε && fabs(z.p2.x-1) < ε)
			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 cQ(s) (det(s.p0, s.p1) + det(s.p1, s.p2) + det(s.p0, s.p2)/2.0)/3.0
		s₀ -= cQ(seg[i]);
#undef det
#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;
}

u8int *
otfdrawglyf(Glyf *g, int h, int *wo)
{
	SegQ *s₀, *s;
	Sval *fp;
	Point *p₀, *p, *prev;
	Spt *pts₀, *pts, *e;
	int w, i, r, k, npts, npx, ns;
	double scale;

	/* FIXME those epsilons and ceil is a rather dumb hack for now */
	scale = (double)h / (g->yMax - g->yMin) + ε;
	w = *wo = ceil((g->xMax - g->xMin) * scale + ε);

	npx = w * h;
	fp = calloc(1, npx*sizeof(*fp));
	ns = 0;
	s₀ = nil;

	for(k = 0; k < g->numberOfContours; k++){
		npts = g->simple->endPtsOfContours[k]+1;
		if(k > 0)
			npts -= g->simple->endPtsOfContours[k-1]+1;

		pts₀ = e = malloc((npts+2)*3*sizeof(*e));
		p₀ = p = g->simple->points + (k > 0 ? g->simple->endPtsOfContours[k-1]+1 : 0);
		prev = p₀+npts-1;
		if(!p->onCurve){
			/* fun stuff */
			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(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++;
			}
			*e = (Spt){p->x, p->y};
		}
		if(e[-1].x != pts₀->x || e[-1].y != pts₀->y){
			if(p[-1].onCurve) /* close with a straight line */
				*e++ = *pts₀;
			*e++ = *pts₀;
		}

		s₀ = realloc(s₀, (ns + (e-pts₀)/2)*sizeof(*s₀));
		s = s₀ + ns;
		pts = e;
		for(e = pts₀; e <= pts-3; e += 2, s++, ns++){
			s->v0[0] = (e[0].x - g->xMin) * scale;
			s->v0[1] = (e[0].y - g->yMin) * scale;
			s->v1[0] = (e[1].x - g->xMin) * scale;
			s->v1[1] = (e[1].y - g->yMin) * scale;
			s->v2[0] = (e[2].x - g->xMin) * scale;
			s->v2[1] = (e[2].y - g->yMin) * scale;
		}
		free(pts₀);
	}

	r = qbzr(s₀, ns, w, h, fp);
	free(s₀);

	if(r != 0)
		return nil;

	u8int *b = malloc(npx);
	for(i = 0; i < npx; i++){
		b[i] = fp[i] <= 0 ? 0 : (fp[i] >= QBZR_PIX_SCALE ? 255 : fp[i]/QBZR_PIX_SCALE*255);
		b[i] = 255 - b[i];
	}
	free(fp);
	return b;
}