ref: 72ea71f4d629f81f7c5eb34d31cf4b873162d06b
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, };