shithub: femtolisp

Download patch

ref: c164ffe64f6cff88a8115acf62dd0d9d64581982
parent: 07d6d11600b118e5737400f4190706bb196aa40b
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Thu Oct 31 20:08:12 EDT 2024

add basic libsixel-powered logic for drawing sixels

--- a/flisp.c
+++ b/flisp.c
@@ -17,6 +17,7 @@
 #include "hashing.h"
 #include "table.h"
 #include "iostream.h"
+#include "fsixel.h"
 
 typedef struct {
         char *name;
@@ -2190,6 +2191,7 @@
 
 	table_init();
 	iostream_init();
+	fsixel_init();
 }
 
 // top level ------------------------------------------------------------------
--- /dev/null
+++ b/fsixel.h
@@ -1,0 +1,1 @@
+void fsixel_init(void);
--- a/iostream.c
+++ b/iostream.c
@@ -65,7 +65,7 @@
 	return args[0] == FL_EOF ? FL_T : FL_F;
 }
 
-static ios_t *
+ios_t *
 toiostream(value_t v)
 {
 	if(!isiostream(v))
--- a/iostream.h
+++ b/iostream.h
@@ -1,4 +1,5 @@
 extern fltype_t *iostreamtype;
 
+ios_t * toiostream(value_t v);
 value_t stream_to_string(value_t *ps);
 void iostream_init(void);
--- a/meson.build
+++ b/meson.build
@@ -2,6 +2,7 @@
 	'femtolisp',
 	'c',
 	version: '0.999',
+	meson_version: '>=1.1.0',
 	default_options: [
 		'c_std=c2x',
 		'warning_level=3',
@@ -109,6 +110,21 @@
 	)
 endif
 
+sixel = get_option('sixel')
+
+if sixel.disabled()
+	libsixel_dep = []
+	src += ['sixel_disabled.c']
+else
+	libsixel = dependency('libsixel', required: sixel)
+	if libsixel.found()
+		src += ['sixel.c']
+		libsixel_dep = [libsixel]
+	elif
+		src += ['sixel_disabled.c']
+	endif
+endif
+
 math = cc.find_library('m', required: false)
 
 boot = custom_target(
@@ -148,7 +164,7 @@
 	],
 	dependencies: [
 		math,
-	],
+	] + libsixel_dep,
 	include_directories: include_directories(
 		'3rd',
 		'3rd/mp',
--- /dev/null
+++ b/meson.options
@@ -1,0 +1,1 @@
+option('sixel', type : 'feature', value : 'enabled')
--- a/mkfile
+++ b/mkfile
@@ -33,6 +33,7 @@
 	ptrhash.$O\
 	random.$O\
 	read.$O\
+	sixel_disabled.$O\
 	string.$O\
 	table.$O\
 	time_plan9.$O\
@@ -47,7 +48,7 @@
 	sed 's,\\,\\\\,g;s,",\\",g;s,^,",g;s,$,\\n",g' $prereq >$target
 
 plan9/builtin_fns.h:
-	sed -n 's/^BUILTIN[_]?(\(".*)/BUILTIN_FN\1/gp' *.c | sort >$target
+	sed -n 's/^BUILTIN[_]?(\(".*)/BUILTIN_FN\1/gp' `{echo $OFILES | sed 's/\.'$O'/.c/g'} | sort >$target
 
 main_plan9.$O: plan9/flisp.boot.h plan9/builtin_fns.h
 flisp.$O: maxstack.inc opcodes.h plan9/builtin_fns.h
--- /dev/null
+++ b/sixel.c
@@ -1,0 +1,206 @@
+#include <sixel.h>
+#include "llt.h"
+#include "flisp.h"
+#include "cvalues.h"
+#include "types.h"
+#include "iostream.h"
+#include "print.h"
+#include "operators.h"
+#include "fsixel.h"
+
+typedef struct fso_t fso_t;
+
+struct fso_t {
+	sixel_output_t *out;
+	sixel_dither_t *dither;
+	ios_t *io;
+	int numcolors;
+};
+
+static value_t fsosym;
+fltype_t *fsotype;
+
+static int
+issixeloutput(value_t v)
+{
+	return iscvalue(v) && cv_class(ptr(v)) == fsotype;
+}
+
+BUILTIN("sixel-ouput?", fsixel_outputp)
+{
+	argcount(nargs, 1);
+	return issixeloutput(args[0]) ? FL_T : FL_F;
+}
+
+static int
+fso_write(char *data, int size, void *priv)
+{
+	fso_t *f = priv;
+	return ios_write(f->io, data, size);
+}
+
+// :: num-colors -> ...
+BUILTIN("sixel-output", fsixel_output)
+{
+	int numcolors = 256;
+	if(nargs > 0){
+		argcount(nargs, 1);
+		numcolors = toulong(args[0]);
+	}
+	if(numcolors < 1 || numcolors > 256)
+		lerrorf(ArgError, "invalid number of colors: %d", numcolors);
+	value_t v = cvalue(fsotype, sizeof(fso_t));
+	fso_t *f = value2c(fso_t*, v);
+	SIXELSTATUS r = sixel_output_new(&f->out, fso_write, f, nil);
+	if(SIXEL_FAILED(r))
+		lerrorf(IOError, "could not create sixel output");
+	r = sixel_dither_new(&f->dither, numcolors, nil);
+	if(SIXEL_FAILED(r))
+		lerrorf(IOError, "could not create sixel dither");
+	sixel_output_set_palette_type(f->out, SIXEL_PALETTETYPE_RGB);
+	sixel_dither_set_pixelformat(f->dither, SIXEL_PIXELFORMAT_PAL8);
+	f->numcolors = numcolors;
+	return v;
+}
+
+static float
+h2rgb(float p, float q, float t)
+{
+	if(t < 0)
+		t++;
+	if(t > 1)
+		t--;
+	if(t >= 2.0/3.0)
+		return p;
+	if(t < 1.0/6.0)
+		return p+(q-p)*6.0*t;
+	if(t < 1.0/2.0)
+		return q;
+	return p+(q-p)*(2.0/3.0-t)*6.0;
+}
+
+// :: sixel-output -> palette -> [paltype ->] ...
+BUILTIN("sixel-set-palette", fsixel_set_palette)
+{
+	if(nargs < 2 || nargs > 3)
+		argcount(nargs, 2);
+	if(!issixeloutput(args[0]))
+		type_error("sixel-output", args[0]);
+	fso_t *f = value2c(fso_t*, args[0]);
+	bool isrgb = true;
+	size_t len;
+	if(nargs >= 3){
+		if(!fl_isstring(args[2]))
+			type_error("string", args[2]);
+		len = cv_len((cvalue_t*)ptr(args[2]));
+		char *s = cvalue_data(args[2]);
+		if(len == 3 && strncmp(s, "rgb", 3) == 0)
+			isrgb = true;
+		else if(len == 3 && strncmp(s, "hls", 3) == 0)
+			isrgb = false;
+		else
+			lerrorf(ArgError, "invalid palette type (must be either \"rgb\" or \"hls\")");
+	}
+
+	if(!isarray(args[1]))
+		type_error("array", args[1]);
+	len = cvalue_arraylen(args[1]);
+	if(f->numcolors*3 != (int)len)
+		lerrorf(ArgError, "invalid palette: expected %d colors, got %d", f->numcolors, (int)len);
+
+	fltype_t *type = cv_class(ptr(args[1]));
+	size_t elsize = type->elsz;
+	fltype_t *eltype = type->eltype;
+	numerictype_t nt = eltype->numtype;
+
+	uint8_t out[256*3] = {0};
+	if(isrgb){
+		if(eltype->type == uint8sym || eltype->type == bytesym)
+			memcpy(out, cptr(args[1]), f->numcolors*3);
+		else
+			lerrorf(ArgError, "invalid palette type: expected bytes");
+	}else{
+		uint8_t *pal = cptr(args[1]);
+		for(int i = 0; i < f->numcolors; i++){
+			float s = (float)conv_to_int32(pal+(i*3+2)*elsize, nt) / 100.0;
+			if(s == 0)
+				out[i*3+0] = out[i*3+1] = out[i*3+2] = 255*(int)conv_to_int32(pal+(i*3+1)*elsize, nt)/100;
+			else{
+				float h = (float)conv_to_int32(pal+(i*3+0)*elsize, nt) / 100.0;
+				float l = (float)conv_to_int32(pal+(i*3+1)*elsize, nt) / 100.0;
+				float q = l < 0.5 ? l*(s+1) : l+s-l*s;
+				float p = 2*l - q;
+				out[i*3+0] = 255*h2rgb(p, q, h+1.0/3.0);
+				out[i*3+1] = 255*h2rgb(p, q, h+0.0/3.0);
+				out[i*3+2] = 255*h2rgb(p, q, h-1.0/3.0);
+			}
+		}
+	}
+	sixel_dither_set_palette(f->dither, out);
+
+	return FL_T;
+}
+
+// :: sixel-output -> iostream -> pixels -> width -> height -> ...
+BUILTIN("sixel-encode", fsixel_encode)
+{
+	argcount(nargs, 5);
+	if(!issixeloutput(args[0]))
+		type_error("sixel-output", args[0]);
+	fso_t *f = value2c(fso_t*, args[0]);
+	f->io = toiostream(args[1]);
+	char *pix;
+	size_t sz;
+	to_sized_ptr(args[2], &pix, &sz);
+	size_t w = toulong(args[3]);
+	size_t h = toulong(args[4]);
+	if(w <= 0 || w > sz)
+		bounds_error(args[2], args[3]);
+	if(h <= 0 || w*h > sz)
+		bounds_error(args[2], args[4]);
+	SIXELSTATUS r = sixel_encode((uint8_t*)pix, w, h, 0, f->dither, f->out);
+	if(SIXEL_FAILED(r))
+		return FL_F;
+	return FL_T;
+}
+
+static void
+print_sixeloutput(value_t v, ios_t *s)
+{
+	USED(v);
+	fl_print_str("#<sixel output>", s);
+}
+
+static void
+relocate_sixeloutput(value_t oldv, value_t newv)
+{
+	fso_t *oldf = cv_data(ptr(oldv));
+	fso_t *f = cv_data(ptr(newv));
+	sixel_output_destroy(oldf->out);
+	SIXELSTATUS r = sixel_output_new(&f->out, fso_write, f, nil);
+	if(SIXEL_FAILED(r))
+		lerrorf(IOError, "could not recreate sixel output");
+	sixel_output_set_palette_type(f->out, SIXEL_PALETTETYPE_RGB);
+}
+
+static void
+free_sixeloutput(value_t self)
+{
+	fso_t *f = value2c(fso_t*, self);
+	sixel_dither_destroy(f->dither);
+	sixel_output_destroy(f->out);
+}
+
+static cvtable_t fso_vtable = {
+	print_sixeloutput,
+	relocate_sixeloutput,
+	free_sixeloutput,
+	nil
+};
+
+void
+fsixel_init(void)
+{
+	fsosym = symbol("sixel-output");
+	fsotype = define_opaque_type(fsosym, sizeof(fso_t), &fso_vtable, nil);
+}
--- /dev/null
+++ b/sixel_disabled.c
@@ -1,0 +1,6 @@
+#include "fsixel.h"
+
+void
+fsixel_init(void)
+{
+}