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;