shithub: subpixelize

Download patch

ref: 02dddea65e3534fc7cbb101b62136e419718a2ab
author: sirjofri <sirjofri@sirjofri.de>
date: Sun Feb 15 08:00:36 EST 2026

adds subpixelize program

--- /dev/null
+++ b/Readme
@@ -1,0 +1,30 @@
+subpixelize image filter
+
+USAGE
+
+  subpixelize < input.bit > output.bit
+
+
+This program converts a grayscale image to a colored image that makes use of subpixels to add fine details.
+
+The program defines three levels of gray:
+
+- Black: <  64
+- Low:   ≥  64 and < 128
+- High:  ≥ 128 and < 192
+- White: ≥ 192
+
+Depending on the surrounding two pixels (left and right), Low and High values will be replaced by colored pixels.
+
+
+BUGS
+
+Right now, the input image needs to have 3 or 4 channels (alpha is ignored/will be passed through). Single-channel images must be converted before use, e. g. using iconv.
+
+Only images with (mostly) pure black and (mostly) pure white areas are supported.
+
+Subpixelize supports only horizontal subpixels (RGB in a row).
+
+Some people don't like subpixels.
+
+The quality of the results isn't visually perfect and might need manual fixup.
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,10 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=subpixelize
+OFILES=subpixelize.$O
+
+</sys/src/cmd/mkone
+
+t:V: $O.out
+	$O.out < test.bit > testout.bit
--- /dev/null
+++ b/subpixelize.c
@@ -1,0 +1,191 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <memdraw.h>
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s\n", argv0);
+	exits("usage");
+}
+
+#define LOWVAL   (255/4)
+#define HIGHVAL  (255/4*3)
+#define LOW(A)   ((A) >= LOWVAL && (A) < 128)
+#define HIGH(A)  ((A) < HIGHVAL && (A) >= 128)
+#define BLACK(A)  ((A) < LOWVAL)
+#define WHITE(A) ((A) >= HIGHVAL)
+
+typedef enum {
+	Equal,
+	Low,
+	High,
+	Rise,
+	Fall,
+} Type;
+
+int
+getbyteval(uchar *val, int nchan)
+{
+	int v, i;
+	
+	v = 0;
+	for (i = 0; i < nchan-1; i++)
+		v += val[i];
+	
+	return v/i;
+}
+
+Type
+checkrow(Memimage *src, Point p2, uchar *ival)
+{
+	Point p1, p3;
+	int v1, v2, v3;
+	
+	p1 = p2;
+	p3 = p2;
+	p1.x--;
+	p3.x++;
+	
+	if (!ptinrect(p1, src->r))
+		return Equal;
+	if (!ptinrect(p2, src->r))
+		return Equal;
+	if (!ptinrect(p3, src->r))
+		return Equal;
+	
+	v1 = getbyteval(byteaddr(src, p1), src->nchan);
+	v2 = getbyteval(byteaddr(src, p2), src->nchan);
+	v3 = getbyteval(byteaddr(src, p3), src->nchan);
+	
+	*ival = v2;
+	
+	if (BLACK(v2) || WHITE(v2))
+		return Equal;
+	
+	if (BLACK(v1) && BLACK(v3))
+		return Low;
+	if (WHITE(v1) && WHITE(v3))
+		return High;
+	if (BLACK(v1) && WHITE(v3))
+		return Rise;
+	if (WHITE(v1) && BLACK(v3))
+		return Fall;
+	if (BLACK(v1) &&   LOW(v3))
+		return Rise;
+	if (BLACK(v1) &&  HIGH(v3))
+		return Rise;
+	if ( HIGH(v1) && BLACK(v3))
+		return Fall;
+	if (  LOW(v1) && BLACK(v3))
+		return Fall;
+	
+	fprint(2, "unhandled case! %3d %3d %3d\n", v1, v2, v3);
+	return Equal;
+}
+
+void
+handlepixel(Memimage *tgt, Memimage *src, Point p)
+{
+	uchar *t, *s;
+	int i;
+	Type tp;
+	uchar ival;
+	uchar col[4]; /* bgra */
+	
+	t = byteaddr(tgt, p);
+	s = byteaddr(src, p);
+	
+	for (i = 0; i < tgt->nchan; i++) {
+		t[i] = s[i];
+	}
+	
+	tp = checkrow(src, p, &ival);
+	
+	if (tp == Equal)
+		return;
+	
+	if (BLACK(ival))
+		return;
+	if (WHITE(ival)) /* considered white */
+		return;
+	
+	/* green value */
+	col[1] = LOW(ival) ? 0 : 255;
+	
+	col[3] = 255;
+	switch (tp) {
+	case Rise:
+		//fprint(2, "case r √\n");
+		col[0] = 255;
+		col[2] =   0;
+		goto Fill;
+	case Fall:
+		//fprint(2, "case f √\n");
+		col[0] =   0;
+		col[2] = 255;
+		goto Fill;
+	case High:
+		//fprint(2, "case h √\n");
+		col[0] = 255;
+		col[1] = LOW(ival) ? 0 : 128;
+		col[2] = 255;
+		goto Fill;
+	case Low:
+		//fprint(2, "case l √\n");
+		col[0] =   0;
+		col[1] = LOW(ival) ? 128 : 255;
+		col[2] =   0;
+		goto Fill;
+	}
+	fprint(2, "should never happen!\n");
+	return;
+Fill:
+	for (i = 0; i < tgt->nchan; i++) {
+		t[i] = col[i];
+	}
+}
+
+void
+subpixelize(Memimage *tgt, Memimage *src)
+{
+	long x, y;
+	
+	for (y = tgt->r.min.y; y < tgt->r.max.y; y++)
+		for (x = tgt->r.min.x; x < tgt->r.max.x; x++)
+			handlepixel(tgt, src, Pt(x, y));
+}
+
+void
+main(int argc, char **argv)
+{
+	Memimage *src;
+	Memimage *tgt;
+	
+	ARGBEGIN{
+	case 'h':
+		usage();
+		break;
+	}ARGEND;
+	
+	if (memimageinit() < 0)
+		sysfatal("%r");
+	
+	src = readmemimage(0);
+	if (!src)
+		sysfatal("readmemimage: %r");
+	
+	if (!(src->nchan == 3 || src->nchan == 4))
+		sysfatal("unsupported chan");
+	
+	tgt = allocmemimage(src->r, src->chan);
+	if (!tgt)
+		sysfatal("allocmemimage: %r");
+	memfillcolor(tgt, DTransparent);
+	
+	subpixelize(tgt, src);
+	if (writememimage(1, tgt) < 0)
+		sysfatal("writememimage");
+	exits(nil);
+}
\ No newline at end of file
binary files /dev/null b/test.bit differ
--