shithub: blie

ref: f8d65f9f1c74b101cccfdc08250e31c5e554b899
dir: /sample.c/

View raw version
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <memdraw.h>
#include <event.h>
#include "blie.h"

/* direct copy from /sys/src/cmd/iconv.c:/^writeuncompressed */
static void
writeuncompressed(int fd, Memimage *m)
{
	char chanstr[32];
	int bpl, y, j;
	uchar *buf;

	if(chantostr(chanstr, m->chan) == nil)
		sysfatal("can't convert channel descriptor: %r");
	fprint(fd, "%11s %11d %11d %11d %11d ",
		chanstr, m->r.min.x, m->r.min.y, m->r.max.x, m->r.max.y);

	bpl = bytesperline(m->r, m->depth);
	buf = malloc(bpl);
	if(buf == nil)
		sysfatal("malloc failed: %r");
	for(y=m->r.min.y; y<m->r.max.y; y++){
		j = unloadmemimage(m, Rect(m->r.min.x, y, m->r.max.x, y+1), buf, bpl);
		if(j != bpl)
			sysfatal("image unload failed: %r");
		if(write(fd, buf, bpl) != bpl)
			sysfatal("write failed: %r");
	}
	free(buf);
}

typedef struct Vec2 Vec2;
struct Vec2 {
	double x;
	double y;
};

/* add components to vector */
static Vec2
addvec(Vec2 a, double x, double y)
{
	a.x += x;
	a.y += y;
	return a;
}

/* get UV for p in [0;np] */
static Vec2
getuv(Point p, Point np)
{
	Vec2 r;
	if (p.x > np.x)
		p.x = np.x;
	else if (p.x < 0)
		p.x = 0;
	if (p.y > np.y)
		p.y = np.y;
	else if (p.y < 0)
		p.y = 0;
	
	r.x = (double)p.x / np.x;
	r.y = (double)p.y / np.y;
	return r;
}

/* clamp(v, 0., 1.) */
static void
saturate(Vec2 *uv)
{
	if (uv->x < 0.)
		uv->x = 0.;
	else if (uv->x > 1.)
		uv->x = 1.;
	if (uv->y < 0.)
		uv->y = 0.;
	else if (uv->y > 1.)
		uv->y = 1.;
}

/* sample reference point from uv (nearest neighbor, top-left pixel) */
static uchar*
samplevalue(Memimage *i, Vec2 uv)
{
	Point p;
	saturate(&uv);
	p.x = uv.x * i->r.max.x;
	p.y = uv.y * i->r.max.y;
	return byteaddr(i, p);
}

/* distance of uv from reference point (barycentric coordinates) */
static Vec2
ptdist(Memimage *i, Vec2 uv, Vec2 px)
{
	Point p;
	Vec2 a, b, r;
	p.x = uv.x * i->r.max.x;
	p.y = uv.y * i->r.max.y;
	a.x = (double)p.x / i->r.max.x;
	a.y = (double)p.y / i->r.max.y;
	b.x = a.x + px.x;
	b.y = a.y + px.y;
	r.x = (uv.x - p.x) * px.x * (-1);
	r.y = (uv.y - p.y) * px.y * (-1);
	return r;
}

static double
lerp(double A, double B, double a)
{
	return A * a + B * (1.-a);
}

/* bilinear interpolation */
static uchar
pxavg(uchar v1, uchar v2, uchar v3, uchar v4, Vec2 n)
{
	double a1, a2, a3, a4;
	a1 = (double)v1/256;
	a2 = (double)v2/256;
	a3 = (double)v3/256;
	a4 = (double)v4/256;
	return lerp(
		lerp(a1, a2, n.x),
		lerp(a3, a4, n.x),
		n.y) * 256;
}

/* performance can be improved:
 *
 * only scaling what's needed, which means calculating
 * the rectangle we want, extracting that to a
 * separate image for resampling.
 *
 * However, this would mean that we have to resample when
 * panning.
 */
static Memimage*
resample(Memimage *src, int nx, int ny, int quality)
{
	Memimage *tm;
	int x, y, bpp;
	Vec2 uv, ndist, px;
	uchar *f1, *f2, *f3, *f4, *to;
	
	bpp = src->depth/8;
	tm = allocmemimage(Rect(0, 0, nx, ny), src->chan);
//	memfillcolor(tm, DBlack);
	
	px.x = (double)1 / src->r.max.x;
	px.y = (double)1 / src->r.max.y;
	
	for (y = 0; y < ny; y++)
		for (x = 0; x < nx; x++) {
			to = byteaddr(tm, Pt(x, y));
			uv = getuv(Pt(x, y), tm->r.max);
			f1 = samplevalue(src, uv);
			if (!quality) {
				switch (bpp) {
				case 4:
					to[3] = f1[3];
				case 3:
					to[2] = f1[2];
					to[1] = f1[1];
				case 1:
					to[0] = f1[0];
				}
				continue;
			}
			ndist = ptdist(src, uv, px);
			f2 = samplevalue(src, addvec(uv, px.x, 0.));
			f3 = samplevalue(src, addvec(uv, 0., px.y));
			f4 = samplevalue(src, addvec(uv, px.x, px.y));
			
#define AVG(n) (pxavg(f1[n], f2[n], f3[n], f4[n], ndist))
			switch (bpp) {
			case 4:
				to[3] = AVG(3);
			case 3:
				to[2] = AVG(2);
				to[1] = AVG(1);
			case 1:
				to[0] = AVG(0);
			}
#undef AVG
		}
	
	return tm;
}

Memimage *lastsampled = nil;

static int
needssample(Memimage *i, double zoom)
{
	return !(Dx(i->r)*zoom == Dx(i->r) && Dy(i->r)*zoom == Dy(i->r));
}

void
sampleview(Image *img, Memimage *src, int quality)
{
	Memimage *tmi;
	Rectangle r;
	int nw;
	
	if (!vstate.dirty)
		return;
	
	if (!src)
		return;
	
	r.min = ZP;
	r.max = Pt(Dx(img->r), Dy(img->r));
	if (quality > vstate.maxquality)
		quality = vstate.maxquality;
	
	if (vstate.dirty & Dzoom) {
		freememimage(lastsampled);
		lastsampled = nil;
	}
	if (!lastsampled && needssample(src, vstate.zoom)) {
		lastsampled = resample(src,
			Dx(src->r)*vstate.zoom, Dy(src->r)*vstate.zoom, quality);
	}
	if (lastsampled)
		src = lastsampled;
	
	tmi = allocmemimage(r, img->chan);
	memfillcolor(tmi, DBlack);
	memimagedraw(tmi, tmi->r, src, addpt(tmi->r.min, vstate.offset), nil, ZP, S);
	
	nw = tmi->width * tmi->r.max.y * sizeof(ulong); // tmi->r.max.y == Dy(tmi->r)
	draw(img, img->r, display->black, nil, ZP);
	loadimage(img, img->r, tmi->data->bdata, nw);
	
	freememimage(tmi);
	vstate.dirty = 0;
}