shithub: blie

Download patch

ref: 3b862725591bc312572a3e35aab3007712c8b9bf
parent: 37d050563f02063f183566ee75c767da1e4e70be
author: sirjofri <sirjofri@sirjofri.de>
date: Mon Aug 12 15:49:02 EDT 2024

bug fixes, native zoom and global menu

--- a/blie.c
+++ b/blie.c
@@ -41,6 +41,7 @@
 	.zoom = 1.0,
 	.offset = { 0, 0 },
 	.dirty = 3,
+	.maxquality = 1,
 };
 
 void
@@ -163,7 +164,7 @@
 	i = (foreachlayer(docomp, img) + 1) % 2;
 	nextlayerdirty = 0;
 
-	sampleview(panels.drawing, img[i]);
+	sampleview(panels.drawing, img[i], 1);
 	
 	if (estate.ed && estate.ed->overlay)
 		estate.ed->overlay(estate.l, panels.drawing);
@@ -277,6 +278,15 @@
 }
 
 static void
+condredraw(Redrawwin w)
+{
+	if (w & Rdrawing)
+		redrawdrawing();
+	if (w & Rtools)
+		redrawtools();
+}
+
+static void
 handlemouse(Event ev)
 {
 	Redrawwin n;
@@ -299,26 +309,15 @@
 		m->xy = subpt(m->xy, panels.tools->r.min);
 		if (estate.ed->toolinput)
 			n = estate.ed->toolinput(estate.l, Emouse, ev);
-		if (n & Rdrawing) {
-			setdrawingdirty(Dcontent);
-			redrawdrawing();
-		}
-		if (n & Rtools) {
-			redrawtools();
-		}
+		condredraw(n);
 		return;
 	}
 	if (estate.ed && ptinrect(m->xy, panels.drawing->r)) {
 		m->xy = subpt(m->xy, panels.drawing->r.min);
+		m->xy = scalepos(m->xy);
 		if (estate.ed->drawinput)
 			n = estate.ed->drawinput(estate.l, Emouse, ev);
-		if (n & Rdrawing) {
-			setdrawingdirty(Dcontent);
-			redrawdrawing();
-		}
-		if (n & Rtools) {
-			redrawtools();
-		}
+		condredraw(n);
 		esetcursor(dstate.cursor);
 		drawcursor(xy, 0);
 		return;
@@ -347,6 +346,8 @@
 		goto Dirtyzoom;
 	case ',':
 		vstate.zoom -= vdata.keyzoom;
+		if (vstate.zoom < 0.1)
+			vstate.zoom = 0.1;
 		goto Dirtyzoom;
 	}
 	return 0;
@@ -358,9 +359,49 @@
 	return 1;
 }
 
+static Redrawwin
+askcommand(Event ev)
+{
+	char cmd[256];
+	char *args[5];
+	int n;
+	
+	cmd[0] = 0;
+	if (!eenter("cmd", cmd, sizeof(cmd), &ev.mouse))
+		return Rnil;
+	
+	n = tokenize(cmd, args, 5);
+	
+	if (n == 2 && strcmp(args[0], "quality") == 0) {
+		if (strcmp(args[1], "0") == 0
+		 || strcmp(args[1], "nearest") == 0) {
+			vstate.maxquality = 0;
+			setdrawingdirty(Dzoom);
+			return Rdrawing;
+		}
+		if (strcmp(args[1], "1") == 0
+		 || strcmp(args[1], "bilinear") == 0) {
+			vstate.maxquality = 1;
+			setdrawingdirty(Dzoom);
+			return Rdrawing;
+		}
+		return Rnil;
+	}
+	if (strcmp(args[0], "w") == 0) {
+		// TODO: save all
+		return Rnil;
+	}
+	if (n == 2 && strcmp(args[0], "e") == 0) {
+		// TODO: export all to file args[1]
+		return Rnil;
+	}
+	return Rnil;
+}
+
 static void
 handlekeyboard(Event ev)
 {
+	Redrawwin w;
 	Mouse *m = &ev.mouse;
 	
 	/* global keys */
@@ -368,6 +409,10 @@
 	case 'q':
 	case Kdel:
 		exits(nil);
+	case '\t':
+		w = askcommand(ev);
+		condredraw(w);
+		return;
 	}
 	
 	if (ptinrect(m->xy, panels.layers->r)) {
@@ -383,8 +428,10 @@
 	}
 	if (estate.ed && ptinrect(m->xy, panels.tools->r)) {
 		m->xy = subpt(m->xy, panels.tools->r.min);
-		if (estate.ed->toolinput)
-			estate.ed->toolinput(estate.l, Ekeyboard, ev);
+		if (estate.ed->toolinput) {
+			w = estate.ed->toolinput(estate.l, Ekeyboard, ev);
+			condredraw(w);
+		}
 		return;
 	}
 	if (ptinrect(m->xy, panels.drawing->r)) {
@@ -392,7 +439,8 @@
 			return;
 		if (estate.ed && estate.ed->drawinput) {
 			m->xy = subpt(m->xy, panels.drawing->r.min);
-			estate.ed->drawinput(estate.l, Ekeyboard, ev);
+			w = estate.ed->drawinput(estate.l, Ekeyboard, ev);
+			condredraw(w);
 		}
 		return;
 	}
--- a/blie.h
+++ b/blie.h
@@ -48,12 +48,13 @@
 	Point offset;
 	float zoom;
 	int dirty;
+	int maxquality;
 };
 
 void setdrawingdirty(int);
 
 /* writes memimage to drawing image, considering pan and zoom using dirty flags */
-void sampleview(Image*, Memimage*);
+void sampleview(Image*, Memimage*, int quality);
 
 typedef enum {
 	Rnil = 0,
--- a/p9image.c
+++ b/p9image.c
@@ -231,7 +231,7 @@
 		return 0;
 	
 	setdrawingdirty(Dcontent);
-	sampleview(i, mi);
+	sampleview(i, mi, 0);
 	return 0;
 }
 
@@ -298,7 +298,6 @@
 	if (!tgt)
 		return Rnil;
 	
-	xy = scalepos(xy);
 	r = insetrect(tgt->r, -tstate.brushrad);
 	if (!ptinrect(xy, r))
 		return Rnil;
@@ -347,18 +346,21 @@
 		case 0:
 			tstate.mode = Composite;
 			tstate.drawtarget = DTimg;
-			return Rdrawing|Rtools;
+			goto Out;
 		case 1:
 			tstate.mode = Img;
 			tstate.drawtarget = DTimg;
-			return Rdrawing|Rtools;
+			goto Out;
 		case 2:
 			tstate.mode = Mask;
 			tstate.drawtarget = DTmask;
-			return Rdrawing|Rtools;
+			goto Out;
 		}
 	}
 	return Rnil;
+Out:
+	setdrawingdirty(Dcontent);
+	return Rdrawing|Rtools;
 }
 
 Editor p9image = {
--- a/sample.c
+++ b/sample.c
@@ -32,94 +32,175 @@
 	free(buf);
 }
 
-/*
- * uses external program for resampling in a pipe.
- * I can see several improvements for performance:
+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:
  *
- * 1. only scaling what's needed, which means calculating
- *    the rectangle we want, extracting that to a
- *    separate image for resampling.
- * 2. integrate resample functionality in this program
- * 3. only resample if zoom level or data changes.
- *    at the moment, it resamples when panning.
+ * 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)
+resample(Memimage *src, int nx, int ny, int quality)
 {
-	int pin[2];
-	int pout[2];
-	int re, ro;
-	char x[11], y[11];
 	Memimage *tm;
-	Dir *dir;
+	int x, y, bpp;
+	Vec2 uv, ndist, px;
+	uchar *f1, *f2, *f3, *f4, *to;
 	
-	if (nx == Dx(src->r) && ny == Dy(src->r))
-		return nil;
+	bpp = src->depth/8;
+	tm = allocmemimage(Rect(0, 0, nx, ny), src->chan);
+//	memfillcolor(tm, DBlack);
 	
-	snprint(x, sizeof(x), "%d", nx);
-	snprint(y, sizeof(y), "%d", ny);
+	px.x = (double)1 / src->r.max.x;
+	px.y = (double)1 / src->r.max.y;
 	
-	if (pipe(pout) < 0) {
-		close(pout[0]);
-		close(pout[1]);
-		goto Err;
-	}
-	if (pipe(pin) < 0) {
-		close(pout[0]);
-		close(pout[1]);
-		close(pin[0]);
-		close(pin[1]);
-		goto Err;
-	}
+	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
+		}
 	
-	dir = dirfstat(pin[0]);
-	if (!dir) {
-		free(dir);
-		goto Err;
-	}
-	dir->length = src->width * Dy(src->r) * sizeof(ulong) + 12 * 5;
-	if (!dirfwstat(pin[0], dir)) {
-		free(dir);
-		goto Err;
-	}
-	free(dir);
-	
-	switch (fork()) {
-	case -1: /* error */
-		goto Err;
-	case 0: /* child */
-		dup(pin[1], 0);
-		dup(pout[1], 1);
-		close(pin[0]);
-		close(pout[0]);
-		execl("/bin/resample", "resample", "-x", x, "-y", y, nil);
-		sysfatal("cannot exec: %r");
-	default: /* parent */
-		close(pout[1]);
-		close(pin[1]);
-		re = pout[0];
-		ro = pin[0];
-	}
-	
-	writeuncompressed(ro, src);
-	tm = readmemimage(re);
-	if (!tm) {
-		close(re);
-		close(ro);
-		goto Err;
-	}
-	close(re);
-	close(ro);
 	return tm;
-Err:
-	fprint(2, "resample: %r\n");
-	return nil;
 }
 
 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)
+sampleview(Image *img, Memimage *src, int quality)
 {
 	Memimage *tmi;
 	Rectangle r;
@@ -133,16 +214,19 @@
 	
 	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) {
-		lastsampled = resample(src, Dx(src->r)*vstate.zoom, Dy(src->r)*vstate.zoom);
-		if (lastsampled)
-			src = lastsampled;
+	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);
--- a/util.c
+++ b/util.c
@@ -18,7 +18,7 @@
 Memimage*
 gencanvas(Memimage *i)
 {
-	return allocmemimage(i->r, i->chan);
+	return allocmemimage(i->r, RGBA32);
 }
 
 Memimage*
@@ -53,7 +53,7 @@
 Point
 scalepos(Point xy)
 {
-	xy = subpt(xy, vstate.offset);
+	xy = addpt(xy, vstate.offset);
 	xy.x = xy.x / vstate.zoom;
 	xy.y = xy.y / vstate.zoom;
 	return xy;