git: 9front

Download patch

ref: 0dd9f39cf5c2ffc4ea83abcc703b1944e7d19093
parent: f67e66ebd0e98f0ff7487b56375d048af133cf09
author: rodri <rgl@antares-labs.eu>
date: Sun Sep 14 07:35:18 EDT 2025

add image(1): a new set of tools for image processing

--- /dev/null
+++ b/lib/image/filter/box4
@@ -1,0 +1,2 @@
+1 1
+1 1
--- /dev/null
+++ b/lib/image/filter/box9
@@ -1,0 +1,3 @@
+1 1 1
+1 1 1
+1 1 1
--- /dev/null
+++ b/lib/image/filter/edge1
@@ -1,0 +1,3 @@
+0 -1 0
+-1 4 -1
+0 -1 0
--- /dev/null
+++ b/lib/image/filter/edge2
@@ -1,0 +1,3 @@
+-1 -1 -1
+-1 8 -1
+-1 -1 -1
--- /dev/null
+++ b/lib/image/filter/gauss9
@@ -1,0 +1,3 @@
+1 2 1
+2 4 2
+1 2 1
--- /dev/null
+++ b/lib/image/filter/sharpen1
@@ -1,0 +1,3 @@
+0 -1 0
+-1 5 -1
+0 -1 0
--- /dev/null
+++ b/lib/image/filter/sharpen2
@@ -1,0 +1,5 @@
+0 0 1 0 0
+0 1 1 1 0
+1 1 -9 1 1
+0 1 1 1 0
+0 0 1 0 0
--- a/sys/lib/rootstub
+++ b/sys/lib/rootstub
@@ -7,6 +7,7 @@
 mkdir -p 386/bin/disk
 mkdir -p 386/bin/fs
 mkdir -p 386/bin/games
+mkdir -p 386/bin/image
 mkdir -p 386/bin/ip/httpd
 mkdir -p 386/bin/ndb
 mkdir -p 386/bin/nusb
@@ -23,6 +24,7 @@
 mkdir -p 68000/bin/disk
 mkdir -p 68000/bin/fs
 mkdir -p 68000/bin/games
+mkdir -p 68000/bin/image
 mkdir -p 68000/bin/ip/httpd
 mkdir -p 68000/bin/ndb
 mkdir -p 68000/bin/nusb
@@ -39,6 +41,7 @@
 mkdir -p 68020/bin/disk
 mkdir -p 68020/bin/fs
 mkdir -p 68020/bin/games
+mkdir -p 68020/bin/image
 mkdir -p 68020/bin/ip/httpd
 mkdir -p 68020/bin/ndb
 mkdir -p 68020/bin/nusb
@@ -66,6 +69,7 @@
 mkdir -p amd64/bin/disk
 mkdir -p amd64/bin/fs
 mkdir -p amd64/bin/games
+mkdir -p amd64/bin/image
 mkdir -p amd64/bin/ip/httpd
 mkdir -p amd64/bin/ndb
 mkdir -p amd64/bin/nusb
@@ -82,6 +86,7 @@
 mkdir -p arm/bin/disk
 mkdir -p arm/bin/fs
 mkdir -p arm/bin/games
+mkdir -p arm/bin/image
 mkdir -p arm/bin/ip/httpd
 mkdir -p arm/bin/ndb
 mkdir -p arm/bin/nusb
@@ -98,6 +103,7 @@
 mkdir -p arm64/bin/disk
 mkdir -p arm64/bin/fs
 mkdir -p arm64/bin/games
+mkdir -p arm64/bin/image
 mkdir -p arm64/bin/ip/httpd
 mkdir -p arm64/bin/ndb
 mkdir -p arm64/bin/nusb
@@ -129,6 +135,7 @@
 mkdir -p mips/bin/disk
 mkdir -p mips/bin/fs
 mkdir -p mips/bin/games
+mkdir -p mips/bin/image
 mkdir -p mips/bin/ip/httpd
 mkdir -p mips/bin/ndb
 mkdir -p mips/bin/nusb
@@ -145,6 +152,7 @@
 mkdir -p spim/bin/disk
 mkdir -p spim/bin/fs
 mkdir -p spim/bin/games
+mkdir -p spim/bin/image
 mkdir -p spim/bin/ip/httpd
 mkdir -p spim/bin/ndb
 mkdir -p spim/bin/nusb
@@ -161,6 +169,7 @@
 mkdir -p power/bin/disk
 mkdir -p power/bin/fs
 mkdir -p power/bin/games
+mkdir -p power/bin/image
 mkdir -p power/bin/ip/httpd
 mkdir -p power/bin/ndb
 mkdir -p power/bin/nusb
@@ -177,6 +186,7 @@
 mkdir -p power64/bin/disk
 mkdir -p power64/bin/fs
 mkdir -p power64/bin/games
+mkdir -p power64/bin/image
 mkdir -p power64/bin/ip/httpd
 mkdir -p power64/bin/ndb
 mkdir -p power64/bin/nusb
@@ -196,6 +206,7 @@
 mkdir -p sparc/bin/disk
 mkdir -p sparc/bin/fs
 mkdir -p sparc/bin/games
+mkdir -p sparc/bin/image
 mkdir -p sparc/bin/ip/httpd
 mkdir -p sparc/bin/ndb
 mkdir -p sparc/bin/nusb
@@ -212,6 +223,7 @@
 mkdir -p sparc64/bin/disk
 mkdir -p sparc64/bin/fs
 mkdir -p sparc64/bin/games
+mkdir -p sparc64/bin/image
 mkdir -p sparc64/bin/ip/httpd
 mkdir -p sparc64/bin/ndb
 mkdir -p sparc64/bin/nusb
--- /dev/null
+++ b/sys/man/1/image
@@ -1,0 +1,112 @@
+.TH IMAGE 1
+.SH NAME
+affinewarp, correlate - image processing tools
+.SH SYNOPSIS
+.B image/affinewarp
+[
+.B -Rqp
+] [[
+.B -s
+.I x y
+] [
+.B -r
+.I θ
+] [
+.B -t
+.I x y
+] [
+.B -S
+.I x y
+]
+.I ...
+]
+.br
+.B image/correlate
+[
+.B -cRp
+]
+.I kernel
+[
+.I denom
+]
+.SH DESCRIPTION
+Image processing tools for
+.IR image (6)
+files.
+.PP
+.I Affinewarp
+applies a sequence of affine image warping transformations to an image
+read from stdin and writes the result to stdout.  The transformations
+happen in a left-handed coordinate system where the origin is the
+upper-left corner, and are applied in the order of the arguments.  Its
+options:
+.TP
+.B -R
+Replicate the source image over the entire destination span.  Without
+this option pixels that map outside the bounds of the image will be
+set to black; or transparent for images with an alpha channel.
+.TP
+.B -q
+Apply a bilinear filter when sampling the source for a higher-quality
+(albeit blurrier) result.
+.TP
+.B -p
+Enable parallelism.
+.TP
+.B -s
+Scale the picture
+.I x
+times along the x-axis and
+.I y
+times along the y-axis.
+.TP
+.B -r
+Rotate the image
+.I θ
+degrees.
+.TP
+.B -t
+Translate the picture
+.I x
+pixels along the x-axis and
+.I y
+pixels along the y-axis.
+.TP
+.B -S
+Shear the image
+.I x
+times along the x-axis and
+.I y
+times along the y-axis.
+.PP
+.I Correlate
+applies a correlation operation between an image read from stdin and a
+filtering
+.I kernel
+specified in a file of the same name, writing the result to stdout.
+It will look for the file in the current directory first, and if it
+doesn't find it, it will try in
+.BR /lib/image/filter .
+A
+.I denom
+parameter controls the normalizing coefficient applied to the kernel
+before doing the operation, and it can be any real number; by default
+or when set to zero, it will use the sum of coefficients in the
+kernel.  Its options:
+.TP
+.B -c
+Apply a convolution instead of a correlation.
+.TP
+.B -R
+Replicate the source image.  This changes the behavior of the sampler
+when accessing pixels outside of the image boundaries, warping the
+probe point so that it falls back into the image (a toroidal map).
+.TP
+.B -p
+Enable parallelism.
+.SH SOURCE
+.B /sys/src/cmd/image
+.SH SEE ALSO
+.IR crop (1),
+.IR resample (1),
+.IR image (6)
--- /dev/null
+++ b/sys/src/cmd/image/affinewarp.c
@@ -1,0 +1,236 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include <geometry.h>
+#include "fns.h"
+
+typedef struct Mstk Mstk;
+struct Mstk
+{
+	Matrix *items;
+	ulong size;
+};
+
+void
+pushmat(Mstk *stk, Matrix m)
+{
+	if(stk->size % 4 == 0)
+		stk->items = erealloc(stk->items, (stk->size + 4)*sizeof(Matrix));
+	memmove(stk->items[stk->size++], m, sizeof(Matrix));
+}
+
+void
+popmat(Mstk *stk, Matrix m)
+{
+	memmove(m, stk->items[--stk->size], sizeof(Matrix));
+	if(stk->size == 0){
+		free(stk->items);
+		stk->items = nil;
+	}
+}
+
+void
+mkrotation(Matrix m, double θ)
+{
+	double c, s;
+
+	c = cos(θ);
+	s = sin(θ);
+	Matrix R = {
+		c, -s, 0,
+		s,  c, 0,
+		0,  0, 1,
+	};
+	memmove(m, R, sizeof(Matrix));
+}
+
+void
+mkscale(Matrix m, double sx, double sy)
+{
+	Matrix S = {
+		sx, 0, 0,
+		0, sy, 0,
+		0, 0, 1,
+	};
+	memmove(m, S, sizeof(Matrix));
+}
+
+void
+mktranslation(Matrix m, double tx, double ty)
+{
+	Matrix T = {
+		1, 0, tx,
+		0, 1, ty,
+		0, 0, 1,
+	};
+	memmove(m, T, sizeof(Matrix));
+}
+
+void
+mkshear(Matrix m, double shx, double shy)
+{
+	Matrix Sxy = {
+		1, shx, 0,
+		shy, 1, 0,
+		0,   0, 1,
+	};
+	memmove(m, Sxy, sizeof(Matrix));
+}
+
+void
+mkxform(Matrix m, Mstk *stk)
+{
+	Matrix t;
+
+	identity(m);
+	while(stk->size > 0){
+		popmat(stk, t);
+		mulm(m, t);
+	}
+}
+
+Point2
+Ptpt2(Point p)
+{
+	return (Point2){p.x, p.y, 1};
+}
+
+#define min(a,b)	((a)<(b)?(a):(b))
+#define max(a,b)	((a)>(b)?(a):(b))
+Rectangle
+getbbox(Rectangle *sr, Matrix m, Point2 *dp0)
+{
+	Point2 p0, p1, p2, p3;
+
+	p0 = Pt2(sr->min.x + 0.5, sr->min.y + 0.5, 1);
+	p0 = *dp0 = xform(p0, m);
+	p1 = Pt2(sr->max.x + 0.5, sr->min.y + 0.5, 1);
+	p1 = xform(p1, m);
+	p2 = Pt2(sr->min.x + 0.5, sr->max.y + 0.5, 1);
+	p2 = xform(p2, m);
+	p3 = Pt2(sr->max.x + 0.5, sr->max.y + 0.5, 1);
+	p3 = xform(p3, m);
+
+	p0.x = min(min(min(p0.x, p1.x), p2.x), p3.x);
+	p0.y = min(min(min(p0.y, p1.y), p2.y), p3.y);
+	p1.x = max(max(max(p1.x, p1.x), p2.x), p3.x);
+	p1.y = max(max(max(p1.y, p1.y), p2.y), p3.y);
+	return Rect(p0.x, p0.y, p1.x, p1.y);
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-Rqp] [[-s x y] [-r θ] [-t x y] [-S x y]]...\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Memimage *dst, *src;
+	Matrix m;
+	Mstk stk;
+	Point2 Δp;
+	Warp w;
+	Rectangle dr, *wr;
+	double x, y, θ;
+	int smooth, dorepl, parallel, nproc, i;
+	char *nprocs;
+
+	dorepl = 0;
+	smooth = 0;
+	parallel = 0;
+	ARGBEGIN{
+	case 's':
+		x = strtod(EARGF(usage()), nil);
+		y = strtod(EARGF(usage()), nil);
+		mkscale(m, x, y);
+		pushmat(&stk, m);
+		break;
+	case 'r':
+		θ = strtod(EARGF(usage()), nil)*DEG;
+		mkrotation(m, θ);
+		pushmat(&stk, m);
+		break;
+	case 't':
+		x = strtod(EARGF(usage()), nil);
+		y = strtod(EARGF(usage()), nil);
+		mktranslation(m, x, y);
+		pushmat(&stk, m);
+		break;
+	case 'S':
+		x = strtod(EARGF(usage()), nil);
+		y = strtod(EARGF(usage()), nil);
+		mkshear(m, x, y);
+		pushmat(&stk, m);
+		break;
+	case 'R':
+		dorepl++;
+		break;
+	case 'q':
+		smooth++;
+		break;
+	case 'p':
+		parallel++;
+		break;
+	default:
+		usage();
+	}ARGEND;
+	if(argc != 0)
+		usage();
+
+	if(memimageinit() != 0)
+		sysfatal("memimageinit: %r");
+
+	src = ereadmemimage(0);
+	if(dorepl)
+		src->flags |= Frepl;
+
+	mkxform(m, &stk);
+	dr = getbbox(&src->r, m, &Δp);
+	Δp = subpt2(Δp, Ptpt2(dr.min));
+	Matrix T = {
+		1, 0, Δp.x,
+		0, 1, Δp.y,
+		0, 0, 1,
+	};
+	mulm(T, m);
+	mkwarp(w, T);
+
+	dr = rectaddpt(dr, subpt(src->r.min, dr.min));
+	dst = eallocmemimage(dr, src->chan);
+	memfillcolor(dst, DTransparent);
+
+	if(parallel){
+		nprocs = getenv("NPROC");
+		if(nprocs == nil || (nproc = strtoul(nprocs, nil, 10)) < 2)
+			nproc = 1;
+		free(nprocs);
+
+		wr = emalloc(nproc*sizeof(Rectangle));
+		initworkrects(wr, nproc, &dr);
+
+		for(i = 0; i < nproc; i++){
+			switch(rfork(RFPROC|RFMEM)){
+			case -1:
+				sysfatal("rfork: %r");
+			case 0:
+				if(memaffinewarp(dst, wr[i], src, src->r.min, w, smooth) < 0)
+					fprint(2, "[%d] memaffinewarp: %r\n", getpid());
+				exits(nil);
+			}
+		}
+		while(waitpid() != -1)
+			;
+
+		free(wr);
+	}else
+		if(memaffinewarp(dst, dr, src, src->r.min, w, smooth) < 0)
+			sysfatal("memaffinewarp: %r");
+
+	ewritememimage(1, dst);
+
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/image/correlate.c
@@ -1,0 +1,177 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "fns.h"
+
+char kerndir[] = "/lib/image/filter";
+
+void
+reversek(double *k, int len)
+{
+	double *e, t;
+
+	e = k + len;
+	while(k < e){
+		t = *k;
+		*k++ = *--e;
+		*e = t;
+	}
+}
+
+#define isspace(c)	((c) == ' ' || (c) == '\t')
+Memimage *
+readimagekernel(int fd, double denom, int reverse)
+{
+	Memimage *ik;
+	Biobuf *bin;
+	double *k, d;
+	int nk, dx0, dx, dy;
+	char *line;
+
+	k = nil;
+	nk = 0;
+	dx0 = dx = dy = 0;
+
+	bin = Bfdopen(fd, OREAD);
+	if(bin == nil)
+		sysfatal("Bfdopen: %r");
+
+	while((line = Brdstr(bin, '\n', 1)) != nil){
+		while(*line){
+			d = strtod(line, &line);
+			if(*line && !isspace(*line)){
+				free(k);
+				Bterm(bin);
+				werrstr("bad image kernel file");
+				return nil;
+			}
+			k = erealloc(k, (nk + 1)*sizeof(double));
+			k[nk++] = d;
+			dx++;
+		}
+		if(dy > 0 && dx != dx0){
+			free(k);
+			Bterm(bin);
+			werrstr("image kernel file has inconsistent dx");
+			return nil;
+		}
+		if(dy++ == 0)
+			dx0 = dx;
+		dx = 0;
+	}
+	Bterm(bin);
+
+	if(reverse)
+		reversek(k, dx0*dy);
+
+	ik = allocmemimagekernel(k, dx0, dy, denom);
+	free(k);
+	if(ik == nil){
+		werrstr("allocmemimagekernel: %r");
+		return nil;
+	}
+	return ik;
+}
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s [-cRp] kernel [denom]\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	Memimage *dst, *src;
+	Memimage *ikern;
+	Rectangle dr, *wr;
+	int convolve, dorepl, parallel, fd, nproc, i;
+	char *nprocs, *p, kernf[128];
+	double denom;
+
+	denom = 0;
+	convolve = 0;
+	dorepl = 0;
+	parallel = 0;
+	ARGBEGIN{
+	case 'c':
+		convolve++;
+		break;
+	case 'R':
+		dorepl++;
+		break;
+	case 'p':
+		parallel++;
+		break;
+	default:
+		usage();
+	}ARGEND;
+	switch(argc){
+	case 2:
+		denom = strtod(argv[1], nil);
+		/* fallthrough */
+	case 1:
+		break;
+	default:
+		usage();
+	}
+
+	if(memimageinit() != 0)
+		sysfatal("memimageinit: %r");
+
+	fd = open(argv[0], OREAD);
+	if(fd < 0){
+		p = strrchr(argv[0], '/');
+		p = p? p+1: argv[0];
+		snprint(kernf, sizeof kernf, "%s/%s", kerndir, p);
+		fd = open(kernf, OREAD);
+		if(fd < 0)
+			sysfatal("could not find filter: %r");
+	}
+	ikern = readimagekernel(fd, denom, convolve);
+	if(ikern == nil)
+		sysfatal("readimagekernel: %r");
+	close(fd);
+
+	src = ereadmemimage(0);
+	if(dorepl)
+		src->flags |= Frepl;
+
+	dr = src->r;
+	dst = eallocmemimage(dr, src->chan);
+	memfillcolor(dst, DTransparent);
+
+	if(parallel){
+		nprocs = getenv("NPROC");
+		if(nprocs == nil || (nproc = strtoul(nprocs, nil, 10)) < 2)
+			nproc = 1;
+		free(nprocs);
+
+		wr = emalloc(nproc*sizeof(Rectangle));
+		initworkrects(wr, nproc, &dr);
+
+		for(i = 0; i < nproc; i++){
+			switch(rfork(RFPROC|RFMEM)){
+			case -1:
+				sysfatal("rfork: %r");
+			case 0:
+				if(memimagecorrelate(dst, wr[i], src, src->r.min, ikern) < 0)
+					fprint(2, "[%d] memimagecorrelate: %r\n", getpid());
+				exits(nil);
+			}
+		}
+		while(waitpid() != -1)
+			;
+
+		free(wr);
+	}else
+		if(memimagecorrelate(dst, dr, src, src->r.min, ikern) < 0)
+			sysfatal("memimagecorrelate: %r");
+
+	ewritememimage(1, dst);
+
+	exits(nil);
+}
--- /dev/null
+++ b/sys/src/cmd/image/fns.h
@@ -1,0 +1,6 @@
+void initworkrects(Rectangle*, int, Rectangle*);
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+Memimage *eallocmemimage(Rectangle, ulong);
+Memimage *ereadmemimage(int);
+int ewritememimage(int, Memimage*);
--- /dev/null
+++ b/sys/src/cmd/image/mkfile
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin/image
+TARG=\
+	correlate\
+	affinewarp\
+
+OFILES=\
+	util.$O\
+
+HFILES=\
+	fns.h\
+
+</sys/src/cmd/mkmany
--- /dev/null
+++ b/sys/src/cmd/image/util.c
@@ -1,0 +1,81 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+#include "fns.h"
+
+void
+initworkrects(Rectangle *wr, int nwr, Rectangle *fbr)
+{
+	int i, Δy;
+
+	wr[0] = *fbr;
+	Δy = Dy(wr[0])/nwr;
+	wr[0].max.y = wr[0].min.y + Δy;
+	for(i = 1; i < nwr; i++)
+		wr[i] = rectaddpt(wr[i-1], Pt(0,Δy));
+	if(wr[nwr-1].max.y < fbr->max.y)
+		wr[nwr-1].max.y = fbr->max.y;
+}
+
+void *
+emalloc(ulong n)
+{
+	void *v;
+
+	v = malloc(n);
+	if(v == nil)
+		sysfatal("realloc: %r");
+	setmalloctag(v, getcallerpc(&n));
+	return v;
+}
+
+void *
+erealloc(void *v, ulong n)
+{
+	void *nv;
+
+	nv = realloc(v, n);
+	if(nv == nil)
+		sysfatal("realloc: %r");
+	if(v == nil)
+		setmalloctag(nv, getcallerpc(&v));
+	else
+		setrealloctag(nv, getcallerpc(&v));
+	return nv;
+}
+
+Memimage *
+eallocmemimage(Rectangle r, ulong chan)
+{
+	Memimage *i;
+
+	i = allocmemimage(r, chan);
+	if(i == nil)
+		sysfatal("allocmemimage: %r");
+	memfillcolor(i, DTransparent);
+	setmalloctag(i, getcallerpc(&r));
+	return i;
+}
+
+Memimage *
+ereadmemimage(int fd)
+{
+	Memimage *i;
+
+	i = readmemimage(fd);
+	if(i == nil)
+		sysfatal("readmemimage: %r");
+	return i;
+}
+
+int
+ewritememimage(int fd, Memimage *i)
+{
+	int rc;
+
+	rc = writememimage(fd, i);
+	if(rc < 0)
+		sysfatal("writememimage: %r");
+	return rc;
+}
--