shithub: femtolisp

ref: 1639dd6badc1c236034e3cac5b4da2f209b040a0
dir: /sixel.c/

View raw version
#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;
	int scalex;
	int scaley;
	uint8_t *buf;
	int bufsz;
};

static sixel_allocator_t *salloc;

static int
issixeloutput(value_t v)
{
	return iscvalue(v) && cv_class(ptr(v)) == FL(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(FL(ArgError), "invalid number of colors: %d", numcolors);
	value_t v = cvalue(FL(fsotype), sizeof(fso_t));
	fso_t *f = value2c(fso_t*, v);
	if(salloc == nil)
		sixel_allocator_new(&salloc, malloc, calloc, realloc, free);
	SIXELSTATUS r = sixel_output_new(&f->out, fso_write, f, salloc);
	if(SIXEL_FAILED(r))
		lerrorf(FL(IOError), "could not create sixel output");
	r = sixel_dither_new(&f->dither, numcolors, salloc);
	if(SIXEL_FAILED(r))
		lerrorf(FL(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);
	sixel_dither_set_transparent(f->dither, 0xff);
	f->numcolors = numcolors;
	f->scalex = f->scaley = 1;
	f->buf = nil;
	f->bufsz = 0;
	return v;
}

BUILTIN("sixel-set-scale", fsixel_set_scale)
{
	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]);
	int scalex = toulong(args[1]);
	int scaley = scalex;
	if(nargs > 2)
		scaley = toulong(args[2]);
	if(scalex < 1 || scalex > 32 || scaley < 1 || scaley > 32)
		lerrorf(FL(ArgError), "invalid scale factor: %dx%d", scalex, scaley);
	f->scalex = scalex;
	f->scaley = scaley;
	return FL(t);
}

// :: 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(FL(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(FL(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 == FL(uint8sym) || eltype->type == FL(bytesym))
			memcpy(out, cptr(args[1]), f->numcolors*3);
		else
			lerrorf(FL(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) / 360.0;
				float l = (float)conv_to_int32(pal+(i*3+1)*elsize, nt) / 100.0;
				int k = h*6;
				float f = h*6 - k;
				float p = l*(1-s);
				float q = l*(1-f*s);
				float t = l*(1-(1-f)*s);
				float r, g, b;
				switch(k % 6){
				case 0:  r=l; g=t; b=p; break;
				case 1:  r=q; g=l; b=p; break;
				case 2:  r=p; g=l; b=t; break;
				case 3:  r=p; g=q; b=l; break;
				case 4:  r=t; g=p; b=l; break;
				default: r=l; g=p; b=q; break;
				}
				out[i*3+0] = 255*r;
				out[i*3+1] = 255*g;
				out[i*3+2] = 255*b;
			}
		}
	}
	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]);
	uint8_t *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;
	if(f->scalex > 1 || f->scaley > 1){
		int ow = w * f->scalex, oh = h * f->scaley, osz = ow*oh;
		if(ow < 1 || oh < 1 || osz < ow || osz < oh)
			lerrorf(FL(ArgError), "scaling out of range");
		if(f->bufsz < osz){
			f->buf = LLT_REALLOC(f->buf, osz);
			f->bufsz = osz;
		}
		r = sixel_helper_scale_image(
			f->buf,
			pix,
			w, h,
			SIXEL_PIXELFORMAT_PAL8,
			ow, oh,
			SIXEL_RES_NEAREST,
			salloc
		);
		if(SIXEL_FAILED(r))
			lerrorf(FL(IOError), "could not scale image");
		w = ow;
		h = oh;
		pix = f->buf;
	}else if(f->buf != nil){
		LLT_FREE(f->buf);
		f->buf = nil;
		f->bufsz = 0;
	}
	r = sixel_encode(pix, w, h, 0, f->dither, f->out);
	if(SIXEL_FAILED(r))
		lerrorf(FL(IOError), "could not encode image");
	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, salloc);
	if(SIXEL_FAILED(r))
		lerrorf(FL(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);
	LLT_FREE(f->buf);
}

static cvtable_t fso_vtable = {
	print_sixeloutput,
	relocate_sixeloutput,
	free_sixeloutput,
	nil
};

void
fsixel_init(void)
{
	FL(fsosym) = symbol("sixel-output");
	FL(fsotype) = define_opaque_type(FL(fsosym), sizeof(fso_t), &fso_vtable, nil);
}