ref: 13590f759547f4ef544b251f2966b4ed2d82038c
dir: /p9image.c/
#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,
};