shithub: blie

ref: 72ea71f4d629f81f7c5eb34d31cf4b873162d06b
dir: /p9image.c/

View raw version
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <draw.h>
#include <memdraw.h>
#include <event.h>
#include <cursor.h>
#include "blie.h"
#include "db.h"

#define DEBUG
static int consfd = -1;
static void
clog(char *fmt, ...)
{
#ifdef DEBUG
	va_list args;
	
	if (consfd < 0) {
		consfd = open("#c/cons", OWRITE|OCEXEC);
		if (consfd < 0)
			return;
	}
	fprint(consfd, "blie-p9image: ");
	va_start(args, fmt);
	vfprint(consfd, fmt, args);
	va_end(args);
	fprint(consfd, "\n");
#endif
}

Cursor ccircle = {
	{-7, -7},
	{0xFF, 0xFF, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07,
	 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07,
	 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07, 0xe0, 0x07,
	 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xFF, 0xFF},
	{0x00, 0x00, 0x7f, 0xfe, 0x40, 0x02, 0x40, 0x02,
	 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02,
	 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02,
	 0x40, 0x02, 0x40, 0x02, 0x7f, 0xfe, 0x00, 0x00}
};

Point toolcell;
#define NUMCELLS (5)

int lwinoffset = 10;

Image *tmpcol;

#define SLIST(DO) \
	DO(value, INT, int, nil)

typedef struct Maskdata Maskdata;
struct Maskdata {
	SLIST(STRUCT)
};
Sdata smaskdata[] = {
	SLIST(SDATA)
	ENDSDATA
};

typedef struct Data Data;
struct Data {
	Memimage *img;
	Memimage *mask;
	Memimage *imask;
	
	/* serializable values */
	Db *db;
	Maskdata *maskdata;
};

typedef enum {
	Composite,
	Img,
	Mask,
} Mode;

enum {
	DTimg = 0,
	DTmask,
};

typedef struct Brush Brush;
struct Brush {
	Memimage *i;
	int r;
};

typedef struct Tstate Tstate;
struct Tstate {
	Mode mode;
	int drawtarget;
	Image *circle;
	Memimage *circlebrush;
	Memimage *colorbrush;
	int brushrad;
	
	int cbrad;
	double cbexp;
	double cbmult;
	
	int curbrush;
	int curcolor;
	
	Brush brushes[NUMCELLS];
	ulong colordata[NUMCELLS];
};
Tstate tstate;

static Brush*
getcurrentbrush(void)
{
	if (tstate.curbrush > NUMCELLS || tstate.curbrush < 0)
		return nil;
	return &tstate.brushes[tstate.curbrush];
}

static void
setcolorbrush(ulong color)
{
	if (tstate.colorbrush)
		freememimage(tstate.colorbrush);
	tstate.colorbrush = allocmemimage(Rect(0, 0, 1, 1), RGB24);
	tstate.colorbrush->flags |= Frepl|Fsimple|Fbytes;
	memfillcolor(tstate.colorbrush, color);
}

static ulong
getcurrentcolorval(void)
{
	if (tstate.curcolor < 0 || tstate.curcolor >= NUMCELLS)
		return DTransparent;
	return tstate.colordata[tstate.curcolor];
}

static Memimage*
getcurrentcolor(void)
{
	if (tstate.curcolor < 0 || tstate.curcolor >= NUMCELLS)
		return nil;
	setcolorbrush(tstate.colordata[tstate.curcolor]);
	return tstate.colorbrush;
}

static double
distance(Point p, Point q)
{
	double n;
	p = subpt(q, p);
	p.x *= p.x;
	p.y *= p.y;
	n = sqrt((double)p.x + (double)p.y);
	return n;
}

static int
isaturate(int a)
{
	return a < 0 ? 0 : (a > 255 ? 255 : a);
}

static void
setcirclebrush(int r, double exp, double mult)
{
	int x, y, d, n;
	double dist;
	
	tstate.cbrad = r;
	tstate.cbexp = exp;
	tstate.cbmult = mult;
	
	d = r*2 + 1;
	if (tstate.circlebrush)
		freememimage(tstate.circlebrush);
	tstate.brushrad = r;
	tstate.circlebrush = allocmemimage(Rect(0, 0, d, d), GREY8);
	tstate.circlebrush->flags |= Fbytes|Falpha;
	for (y = 0; y < d; y++)
		for (x = 0; x < d; x++) {
			dist = distance(Pt(x, y), Pt(r, r))/r;
			if (dist > 1.) {
				*byteaddr(tstate.circlebrush, Pt(x, y)) = 0x0;
				continue;
			}
			dist = pow(1. - dist, exp) * mult;
			
			n = (int)(dist * 256.);
			*byteaddr(tstate.circlebrush, Pt(x, y)) = isaturate(n);
		}
	
	tstate.brushes[0].i = tstate.circlebrush;
	tstate.brushes[0].r = tstate.brushrad;
}

static void
readcolors(void)
{
	int i;
	Db *db;
	ulong val;
	Dpack *dv;
	char *tv;
	
	db = opendb("p9image/colors");
	if (!db)
		sysfatal("cannot read colors: %r");
	
	for (dv = db->dpack; dv; dv = dv->next) {
		tv = getdval(dv, "color", nil);
		if (!tv)
			continue;
		i = atoi(dv->id);
		val = strtoul(tv, nil, 16);
		tstate.colordata[i] = val;
	}
	freedb(db);
}

static int
writecolors(void)
{
	int i;
	ulong c;
	Dpack *dv;
	Db *db;
	char buf[9];
	
	if (access("p9image", AEXIST)) {
		i = create("p9image", OREAD, DMDIR|0555);
		if (i < 0) {
			werrstr("p9image: %r");
			return 0;
		}
		close(i);
	}
	
	db = opendb(nil);
	
	for (i = 0; i < NUMCELLS; i++) {
		c = tstate.colordata[i];
		snprint(buf, sizeof(buf), "%d", i);
		dv = getdpack(db, buf);
		snprint(buf, sizeof(buf), "%ulx", c&0xff ? c : 0);
		setdval(dv, "color", buf);
	}
	
	i = writedb(db, "p9image/colors");
	freedb(db);
	return i;
}

static void
p9initialize()
{
	tstate.mode = Composite;
	tstate.drawtarget = DTimg;
	
	if (headless)
		return;
	
	toolcell = Pt(15, vdata.fontheight + 4);
	
	tstate.circle = allocimage(display, Rect(0, 0, 41, 41), RGBA32, 0, DTransparent);
	ellipse(tstate.circle, Pt(20, 20), 19, 19, 0, display->white, ZP);
	ellipse(tstate.circle, Pt(20, 20), 20, 20, 0, display->black, ZP);
	
	setcirclebrush(20, 1., 1.);
	setcolorbrush(DRed);
	tstate.curbrush = 0;
	tstate.curcolor = -1;
	
	/* load tools */
	readcolors();
}

static int
writelayer(Layer *l)
{
	Data *d;
	int fd;
	char *s;
	
	if (!l->data) {
		werrstr("p9image: layer not initialized: %s", l->name);
		return 0;
	}
	d = (Data*)l->data;
	
	/* image file */
	if (!d->img) {
		werrstr("p9image: no image");
		return 0;
	}
	
	s = smprint("l/%s/img", l->name);
	fd = open(s, OWRITE|OTRUNC);
	if (fd < 0)
		fd = create(s, OWRITE|OTRUNC, 0666);
	if (fd < 0) {
		werrstr("p9image: %r");
		free(s);
		return 0;
	}
	free(s);
	if (writememimage(fd, d->img)) {
		close(fd);
		werrstr("p9image: %r");
		return 0;
	}
	close(fd);
	
	/* mask file */
	if (!d->mask)
		return 1;
	
	s = smprint("l/%s/mask", l->name);
	fd = open(s, OWRITE|OTRUNC);
	if (fd < 0)
		fd = create(s, OWRITE|OTRUNC, 0666);
	if (fd < 0) {
		werrstr("p9image: %r");
		free(s);
		return 0;
	}
	free(s);
	if (writememimage(fd, d->mask)) {
		close(fd);
		werrstr("p9image: %r");
		return 0;
	}
	close(fd);
	return 1;
}

static int
p9writedata(Layer*, Data *d)
{
	Db *db;
	Dpack *dv;
	
	char buf[128];
	db = d->db;
	
	dv = getdpack(db, "mask");
	if (dv) {
		snprint(buf, sizeof buf, "%d", d->maskdata->value);
		setdval(dv, "value", buf);
	}
	
	return writedb(db, nil);
}

static void
updateimask(Data *d)
{
	int x, y, dx, dy;
	double m;
	uchar *p;
	
	if (d->imask) {
		freememimage(d->imask);
		d->imask = nil;
	}
	
	if (d->maskdata->value == 255) {
		d->imask = allocmemimage(d->img->r, GREY8);
		memfillcolor(d->imask, DBlack);
		memimagedraw(d->imask, d->imask->r, memwhite, ZP, d->img, ZP, SoverD);
		return;
	}
	
	m = (double)d->maskdata->value / 256;
	dx = Dx(d->img->r);
	dy = Dy(d->img->r);
	d->imask = allocmemimage(d->img->r, GREY8);
	memfillcolor(d->imask, DBlack);
	memimagedraw(d->imask, d->imask->r, memwhite, ZP, d->img, ZP, SoverD);
	
	for (y = 0; y < dy; y++) {
		for (x = 0; x < dx; x++) {
			p = byteaddr(d->imask, Pt(x, y));
			*p = *p * m;
		}
	}
}

static void
p9readdata(Layer *l, Data *d)
{
	Db *db;
	Dpack *dv;
	char *s;
	
	clog("readdata: %s", l->name);
	
	s = smprint("l/%s/data", l->name);
	db = opendb(s);
	free(s);
	if (!db)
		return;
	d->db = db;
	
	dv = getdpack(db, "mask");
	deserialize(dv, d->maskdata, smaskdata);
}

static void
p9init(Layer *l)
{
	int fd;
	char *s;
	Data *d;
	
	if (l->data)
		return;
	d = mallocz(sizeof(Data), 1);
	l->data = d;
	d->maskdata = mallocz(sizeof(Maskdata), 1);
	
	p9readdata(l, d);
	
	/* image file */
	s = smprint("l/%s/img", l->name);
	fd = open(s, OREAD);
	if (fd < 0) {
		free(s);
		return;
	}
	free(s);
	
	seek(fd, 0, 0);
	d->img = creadmemimage(fd);
	if (!d->img) {
		seek(fd, 0, 0);
		d->img = readmemimage(fd);
	}
	close(fd);
	
	/* mask file */
	s = smprint("l/%s/mask", l->name);
	fd = open(s, OREAD);
	if (fd < 0) {
		free(s);
		updateimask(d);
		return;
	}
	free(s);
	
	seek(fd, 0, 0);
	d->mask = creadmemimage(fd);
	if (!d->mask) {
		seek(fd, 0, 0);
		d->mask = readmemimage(fd);
	}
	close(fd);
	updateimask(d);
}

/* just use ecompose, which uses raw() and mask() */
static Memimage*
p9composite(Layer *l, Memimage *img)
{
	Data *d;
	d = (Data*)l->data;
	
	if (!img) {
		fprint(2, "%s: return input image: %p\n", l->name, d->img);
		return dupmemimage(d->img);
	}
	
	fprint(2, "%s: return composite image: %p %p %p\n", l->name,
		img, d->img, d->mask);
	return gencomposite(img, d->img, d->mask, l->op);
}

static Memimage*
p9raw(Layer *l)
{
	Data *d;
	d = (Data*)l->data;
	return d->img;
}

static Memimage*
p9mask(Layer *l)
{
	Data *d;
	d = (Data*)l->data;
	if (d->imask)
		return d->imask;
	return d->mask;
}

static int
p9overlay(Layer *l, Image *i)
{
	Data *data;
	Memimage *mi;
	
	data = (Data*)l->data;
	
	if (!i)
		return tstate.mode != Composite;
	
	switch (tstate.mode) {
	case Composite:
		break;
	case Img:
		mi = data->img;
		goto Mout;
	case Mask:
		if (data->imask) {
			mi = data->imask;
			goto Mout;
		}
		mi = nil;
		goto Mout;
	}
	changecursor(nil, nil, ZP);
	return 0;
Mout:
	changecursor(&ccircle, tstate.circle, Pt(-20, -20));
	if (!mi)
		return 0;
	
	setdrawingdirty(Dcontent);
	sampleview(i, mi, 0);
	return 0;
}

static Rectangle
p9toolrect(Layer*)
{
	return Rect(0, 0, 200, 50);
}

static void
drcells(Image *i, Point p, char *s, int hl)
{
	Rectangle r;
	r.min = p;
	r.max = addpt(p, toolcell);
	draw(i, r, display->white, nil, ZP);
	border(i, r, 1, vdata.gray, ZP);
	if (hl) {
		r = insetrect(r, 2);
		draw(i, r, vdata.gray, nil, ZP);
	}
	string(i, addpt(p, Pt(2, 2)), display->black, ZP, font, s);
}

static void
drcols(Image *i, Point p, int n, int hl)
{
	Rectangle r;
	r.min = p;
	r.max = addpt(p, toolcell);
	tmpcol = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, tstate.colordata[n]);
	draw(i, r, tmpcol, nil, ZP);
	freeimage(tmpcol);
	border(i, r, 1, hl ? display->black : vdata.gray, ZP);
}

static void
drbrush(Image *i, Point p, int, int hl)
{
	Rectangle r;
	r.min = p;
	r.max = addpt(p, toolcell);
	border(i, r, 1, hl ? display->black : vdata.gray, ZP);
}

static void
p9drawtools(Layer*, Image *i)
{
	int n;
	Point p;
	p = i->r.min;
	draw(i, i->r, display->white, nil, ZP);
	
	for (n = 0; n < NUMCELLS; n++) {
		drbrush(i, p, n, tstate.curbrush == n);
		p.x += toolcell.x;
	}
	
	p.y += toolcell.y;
	p.x = i->r.min.x;
	for (n = 0; n < NUMCELLS; n++) {
		drcols(i, p, n, tstate.curcolor == n);
		p.x += toolcell.x;
	}
}

static int
p9savedata(Layer *l)
{
	Data *d;
	d = (Data*)l->data;
	return writelayer(l) && p9writedata(l, d);
}

static int
p9savetools(Layer*)
{
	/* at the moment, this will write the data for each layer
	 * into the same file, which is not wrong, but could
	 * be improved.
	 */
	return writecolors();
}

static Redrawwin
drawalphabrush(Layer *l, Data *d, Brush *brush, ulong color, Rectangle r)
{
	int x, y;
	uchar *f, *t;
	uchar *col;
	uchar alpha, a;
	
	col = (uchar*)&color;
	alpha = rgb2k(col[0], col[1], col[2]);
	
	if (r.max.y > d->img->r.max.y)
		r.max.y = d->img->r.max.y;
	if (r.max.x > d->img->r.max.x)
		r.max.x = d->img->r.max.x;
	
	for (y = r.min.y; y < r.max.y; y++) {
		for (x = r.min.x; x < r.max.x; x++) {
			if (x < d->img->r.min.x)
				continue;
			if (y < d->img->r.min.y)
				continue;
			t = byteaddr(d->img, Pt(x, y));
			f = byteaddr(brush->i, Pt(x-r.min.x, y-r.min.y));
			a = 0;
			switch (brush->i->chan) {
			case GREY8:
				a = f[0];
				break;
			case RGB24:
				a = rgb2k(f[3], f[2], f[1]);
				break;
			case RGBA32:
				a = f[0];
				break;
			}
			t[0] = ilerp(t[0], alpha, a);
			t[1] = imul(t[0], t[1]);
			t[2] = imul(t[0], t[2]);
			t[3] = imul(t[0], t[3]);
		}
	}
	updateimask(d);
	dirtylayer(l);
	return Rdrawing;
}

static Redrawwin
drawbrush(Layer *l, int buttons, Point xy)
{
	Data *d;
	Rectangle r;
	Brush *brush;
	Memimage *color;
	
	d = (Data*)l->data;
	
	if (!buttons)
		return Rnil;
	
	brush = getcurrentbrush();
	color = getcurrentcolor();
	
	if (!(brush && brush->i && color))
		return Rnil;
	
	r = insetrect(d->img->r, -brush->r);
	if (!ptinrect(xy, r))
		return Rnil;
	
	r = rectaddpt(brush->i->r,
		subpt(
			addpt(d->img->r.min, xy),
			Pt(brush->r, brush->r)
		)
	);
	
	color->clipr = brush->i->r;

	if (tstate.drawtarget == DTimg)
		goto Imgdraw;
	if (tstate.drawtarget == DTmask)
		return drawalphabrush(l, d, brush, getcurrentcolorval(), r);
	
	return Rnil;
	
Imgdraw:
	memimagedraw(d->img, r, color, ZP, brush->i, ZP, SoverD);
	updateimask(d);
	setdrawingdirty(Dcontent);
	dirtylayer(l);
	return Rdrawing;
}

static Redrawwin
p9drawinput(Layer *l, int e, Event ev)
{
	switch (e) {
	case Ekeyboard:
		break;
	case Emouse:
		return drawbrush(l, ev.mouse.buttons, ev.mouse.xy);
		break;
	}
	return Rnil;
}

static void
selectbrush(int num)
{
	if (num < 0 || num >= NUMCELLS)
		return;
	tstate.curbrush = num;
}

static void
selectcolor(int num)
{
	if (num < 0 || num >= NUMCELLS)
		return;
	tstate.curcolor = num;
}

static void
setbrush(int num)
{
	// TODO: implement (brush logic)
}

static void
setcolor(int num)
{
	ulong col;
	
	if (num < 0 || num >= NUMCELLS)
		return;
	
	if (!eentercolor("color", &col, &vstate.lastmouse))
		return;
	tstate.colordata[num] = col;
}

static void
configcirclebrush(Mouse *m)
{
	char buf[256];
	char *args[3];
	int r;
	double e, mu;
	
	snprint(buf, sizeof buf, "%d %f %f", tstate.cbrad, tstate.cbexp, tstate.cbmult);
	if (eenter("circle (rad, exp, mult)", buf, sizeof buf, m) < 0)
		return;
	
	if (tokenize(buf, args, 3) != 3)
		return;
	
	r = atoi(args[0]);
	e = atof(args[1]);
	mu = atof(args[2]);
	setcirclebrush(r, e, mu);
}

static Redrawwin
p9toolinput(Layer*, int e, Event ev)
{
	Point xy;
	if (e != Emouse)
		return Rnil;
	
	if (!ev.mouse.buttons)
		return Rnil;
	xy.x = ev.mouse.xy.x / toolcell.x;
	xy.y = ev.mouse.xy.y / toolcell.y;
	
	if (ev.mouse.buttons & 1) {
		/* left mouse button */
		switch (xy.y) {
		case 0:
			selectbrush(xy.x);
			return Rtools;
		case 1:
			selectcolor(xy.x);
			return Rtools;
		}
		return Rnil;
	}
	if (ev.mouse.buttons & 4) {
		/* right mouse button */
		switch (xy.y) {
		case 0:
			if (xy.x == 0) {
				/* special case for circle brush */
				configcirclebrush(&ev.mouse);
				return Rnil;
			}
			setbrush(xy.x);
			return Rtools;
		case 1:
			setcolor(xy.x);
			return Rtools;
		}
		return Rnil;
	}
	return Rnil;
}

static void
p9drawlwin(Layer*, Image *i, Rectangle r)
{
	Point p;
	p = r.min;
	p.x += lwinoffset;
	
	drcells(i, p, "C", tstate.mode == Composite);
	p.x += toolcell.x;
	drcells(i, p, "S", tstate.mode == Img);
	p.x += toolcell.x;
	drcells(i, p, "M", tstate.mode == Mask);
}

static Redrawwin
p9lwininput(Layer*, int, Event ev)
{
	ev.mouse.xy.x -= lwinoffset;
	
	if (ev.mouse.xy.y / toolcell.y != 0)
		return Rnil;
	
	switch (ev.mouse.xy.x / toolcell.x) {
	case 0:
		tstate.mode = Composite;
		tstate.drawtarget = DTimg;
		goto Out;
	case 1:
		tstate.mode = Img;
		tstate.drawtarget = DTimg;
		goto Out;
	case 2:
		tstate.mode = Mask;
		tstate.drawtarget = DTmask;
		goto Out;
	}
	return Rnil;
Out:
	setdrawingdirty(Dcontent);
	return Rlayers|Rdrawing;
}

Editor p9image = {
	.name = "p9img",
	.init = p9initialize,
	.initlayer = p9init,
	.raw = p9raw,
	.mask = p9mask,
	.overlay = p9overlay,
	.toolrect = p9toolrect,
	.drawtools = p9drawtools,
	.drawlwin = p9drawlwin,
	.savedata = p9savedata,
	.savetools = p9savetools,
	.drawinput = p9drawinput,
	.toolinput = p9toolinput,
	.lwininput = p9lwininput,
};