summaryrefslogtreecommitdiff
path: root/sys/src/cmd/mug.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/mug.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/mug.c')
-rwxr-xr-xsys/src/cmd/mug.c1240
1 files changed, 1240 insertions, 0 deletions
diff --git a/sys/src/cmd/mug.c b/sys/src/cmd/mug.c
new file mode 100755
index 000000000..27c4d6644
--- /dev/null
+++ b/sys/src/cmd/mug.c
@@ -0,0 +1,1240 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <cursor.h>
+
+#define initstate muginitstate
+
+typedef struct State State;
+struct State {
+ double black;
+ double white;
+ double stretch;
+ double gamma;
+ int depth;
+ int gtab[1001];
+ Rectangle selr;
+};
+
+typedef struct Face Face;
+struct Face {
+ Rectangle r;
+ State state;
+ Image *small;
+};
+
+double GAMMA = 1.0; /* theory tells me this should be 2.2, but 1.0 sure looks better */
+enum {
+ Left=0,
+ Right,
+ Top,
+ Bottom,
+
+ RTopLeft=0,
+ RTop,
+ RTopRight,
+ RLeft,
+ RMiddle,
+ RRight,
+ RBotLeft,
+ RBot,
+ RBotRight,
+};
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = malloc(sz);
+ if(v == nil)
+ sysfatal("malloc %lud fails", sz);
+ memset(v, 0, sz);
+ return v;
+}
+
+Face *face[8];
+int nface;
+uchar grey2cmap[256];
+Image *bkgd;
+Image *orig;
+Image *ramp, *small, *osmall, *tmp8, *red, *green, *blue;
+State state, ostate;
+uchar val2cmap[256];
+uchar clamp[3*256];
+Rectangle rbig, rramp, rface[nelem(face)], rsmall;
+double *rdata;
+int sdy, sdx;
+
+void
+geometry(Rectangle r)
+{
+ int i;
+ Rectangle fr[9];
+
+ rramp.min = addpt(r.min, Pt(4,4));
+ rramp.max = addpt(rramp.min, Pt(256,256));
+
+ rbig.min = Pt(rramp.max.x+6, rramp.min.y);
+ rbig.max = addpt(rbig.min, Pt(Dx(orig->r), Dy(orig->r)));
+
+ for(i=0; i<9; i++)
+ fr[i] = rectaddpt(Rect(0,0,48,48), Pt(rramp.min.x+48+56*(i%3), rramp.max.y+6+56*(i/3)));
+
+ rsmall = fr[4];
+ for(i=0; i<4; i++)
+ rface[i] = fr[i];
+ for(i=4; i<8; i++)
+ rface[i] = fr[i+1];
+}
+
+double
+y2gamma(int y)
+{
+ double g;
+
+ g = (double)y / 128.0;
+ return 0.5+g*g; /* gamma from 0.5 to 4.5, with 1.0 near the middle */
+}
+
+int
+gamma2y(double g)
+{
+ g -= 0.5;
+ return (int)(128.0*sqrt(g)+0.5);
+}
+
+void
+drawface(int i)
+{
+ if(i==-1){
+ border(screen, rsmall, -3, blue, ZP);
+ draw(screen, rsmall, small, nil, ZP);
+ return;
+ }
+ border(screen, rface[i], -1, display->black, ZP);
+ if(face[i])
+ draw(screen, rface[i], face[i]->small, nil, ZP);
+ else
+ draw(screen, rface[i], display->white, nil, ZP);
+}
+
+void
+drawrampbar(Image *color, State *s)
+{
+ Rectangle liner, r;
+ static Rectangle br;
+
+ if(Dx(br))
+ draw(screen, br, ramp, nil, subpt(br.min, rramp.min));
+
+ r = rramp;
+ r.max.x = r.min.x + (int)(s->white*255.0);
+ r.min.x += (int)(s->black*255.0);
+ r.min.y += gamma2y(s->gamma);
+ r.max.y = r.min.y+1;
+ rectclip(&r, rramp);
+ draw(screen, r, color, nil, ZP);
+ br = r;
+
+ r.min.y -= 2;
+ r.max.y += 2;
+
+ liner = r;
+ r.min.x += Dx(liner)/3;
+ r.max.x -= Dx(liner)/3;
+ rectclip(&r, rramp);
+ draw(screen, r, color, nil, ZP);
+ combinerect(&br, r);
+
+ r = liner;
+ r.max.x = r.min.x+3;
+ rectclip(&r, rramp);
+ draw(screen, r, color, nil, ZP);
+ combinerect(&br, r);
+
+ r = liner;
+ r.min.x = r.max.x-3;
+ rectclip(&r, rramp);
+ draw(screen, r, color, nil, ZP);
+ combinerect(&br, r);
+}
+
+void
+drawscreen(int clear)
+{
+ int i;
+
+ if(clear){
+ geometry(screen->r);
+ draw(screen, screen->r, bkgd, nil, ZP);
+ }
+
+ border(screen, rbig, -1, display->black, ZP);
+ draw(screen, rbig, orig, nil, orig->r.min);
+
+ border(screen, rramp, -1, display->black, ZP);
+ draw(screen, rramp, ramp, nil, ramp->r.min);
+ drawrampbar(red, &state);
+
+ border(screen, rectaddpt(state.selr, subpt(rbig.min, orig->r.min)), -2, red, ZP);
+ if(clear){
+ drawface(-1);
+ for(i=0; i<nelem(face); i++)
+ drawface(i);
+ }
+}
+
+void
+moveframe(Rectangle old, Rectangle new)
+{
+ border(screen, rectaddpt(old, subpt(rbig.min, orig->r.min)), -2, orig, old.min);
+ border(screen, rectaddpt(new, subpt(rbig.min, orig->r.min)), -2, red, ZP);
+}
+
+
+/*
+ * Initialize gamma ramp; should dither for
+ * benefit of non-true-color displays.
+ */
+void
+initramp(void)
+{
+ int k, x, y;
+ uchar dat[256*256];
+ double g;
+
+ k = 0;
+ for(y=0; y<256; y++) {
+ g = y2gamma(y);
+ for(x=0; x<256; x++)
+ dat[k++] = 255.0 * pow(x/255.0, g);
+ }
+ assert(k == sizeof dat);
+
+ ramp = allocimage(display, Rect(0,0,256,256), GREY8, 0, DNofill);
+ if(ramp == nil)
+ sysfatal("allocimage: %r");
+
+ if(loadimage(ramp, ramp->r, dat, sizeof dat) != sizeof dat)
+ sysfatal("loadimage: %r");
+}
+
+void
+initclamp(void)
+{
+ int i;
+
+ for(i=0; i<256; i++) {
+ clamp[i] = 0;
+ clamp[256+i] = i;
+ clamp[512+i] = 255;
+ }
+}
+
+void
+changestretch(double stretch)
+{
+ state.stretch = stretch;
+}
+
+/*
+ * There is greyscale data for the rectangle datar in data;
+ * extract square r and write it into the 48x48 pixel image small.
+ */
+void
+process(double *data, Rectangle datar, Rectangle r, Image *small)
+{
+ double black, center, delta, *k, shrink, sum, *tmp[48], *tt, w, white, x;
+ int datadx, dp, dx, dy, error, i, ii, j, jj;
+ int ksize, ksizeby2, sdata[48*48], sd, sh, sm, sv, u, uu, uuu, v, vv;
+ uchar bdata[48*48];
+
+ datadx = Dx(datar);
+ dx = Dx(r);
+ dy = Dy(r);
+ shrink = dx/48.0;
+
+ ksize = 1+2*(int)(shrink/2.0);
+ if(ksize <= 2)
+ return;
+
+ k = emalloc(ksize*sizeof(k[0]));
+
+ /* center of box */
+ for(i=1; i<ksize-1; i++)
+ k[i] = 1.0;
+
+ /* edges */
+ x = shrink - floor(shrink);
+ k[0] = x;
+ k[ksize-1] = x;
+
+ sum = 0.0;
+ for(i=0; i<ksize; i++)
+ sum += k[i];
+
+ for(i=0; i<ksize; i++)
+ k[i] /= sum;
+
+ ksizeby2 = ksize/2;
+
+ for(i=0; i<48; i++)
+ tmp[i] = emalloc(datadx*sizeof(tmp[i][0]));
+
+ /* squeeze vertically */
+ for(i=0; i<48; i++) {
+ ii = r.min.y+i*dy/48;
+ tt = tmp[i];
+ uu = ii - ksizeby2;
+ for(j=r.min.x-ksize; j<r.max.x+ksize; j++) {
+ if(j<datar.min.x || j>=datar.max.x)
+ continue;
+ w = 0.0;
+
+ uuu = uu*datadx+j;
+ if(uu>=datar.min.y && uu+ksize<datar.max.y)
+ for(u=0; u<ksize; u++){
+ w += k[u]*data[uuu];
+ uuu += datadx;
+ }
+ else
+ for(u=0; u<ksize; u++){
+ if(uu+u>=datar.min.y && uu+u<datar.max.y)
+ w += k[u]*data[uuu];
+ uuu+=datadx;
+ }
+ tt[j-datar.min.x] = w;
+ }
+ }
+
+ /* stretch value scale */
+ center = (state.black+state.white)/2;
+ delta = state.stretch*(state.white-state.black)/2;
+ black = center - delta;
+ white = center + delta;
+
+ /* squeeze horizontally */
+ for(i=0; i<48; i++) {
+ tt = tmp[i];
+ for(j=0; j<48; j++) {
+ jj = r.min.x+j*dx/48;
+ w = 0.0;
+ for(v=0; v<ksize; v++) {
+ vv = jj - ksizeby2 + v;
+ if(vv<datar.min.x || vv>=datar.max.x) {
+ w += k[v]; /* assume white surround */
+ continue;
+ }
+ w += k[v]*tt[vv-datar.min.x];
+ }
+ if(w < black || black==white)
+ w = 0.0;
+ else if(w > white)
+ w = 1.0;
+ else
+ w = (w-black)/(white-black);
+ sdata[i*48+j] = state.gtab[(int)(1000.0*w)];
+ }
+ }
+
+ /* dither to lower depth before copying into GREY8 version */
+ if(small->chan != GREY8) {
+ u = 0;
+ dp = small->depth;
+ for(i=0; i<48; i++) {
+ sm = 0xFF ^ (0xFF>>dp);
+ sh = 0;
+ v = 0;
+ for(j=0; j<48; j++) {
+ ii = 48*i+j;
+ sd = clamp[sdata[ii]+256];
+ sv = sd&sm;
+ v |= sv>>sh;
+ sh += dp;
+ if(sh == 8) {
+ bdata[u++] = v;
+ v = 0;
+ sh = 0;
+ }
+
+ /* propagate error, with decay (sum errors < 1) */
+ error = sd - sv;
+ if(ii+49 < 48*48) { /* one test is enough, really */
+ sdata[ii+1] = sdata[ii+1]+((3*error)>>4);
+ sdata[ii+48] = sdata[ii+48]+((3*error)>>4);
+ sdata[ii+49] = sdata[ii+49]+((3*error)>>3);
+ }
+
+ /* produce correct color map value by copying bits */
+ switch(dp){
+ case 1:
+ sv |= sv>>1;
+ case 2:
+ sv |= sv>>2;
+ case 4:
+ sv |= sv>>4;
+ }
+ sdata[ii] = sv;
+ }
+ }
+ for(i=0; i<nelem(bdata); i++)
+ bdata[i] = sdata[i];
+ if(loadimage(tmp8, tmp8->r, bdata, sizeof bdata) != sizeof bdata)
+ sysfatal("loadimage: %r");
+ draw(small, small->r, tmp8, nil, tmp8->r.min);
+ } else {
+ for(i=0; i<nelem(bdata); i++)
+ bdata[i] = sdata[i];
+ if(loadimage(small, small->r, bdata, sizeof bdata) != sizeof bdata)
+ sysfatal("loadimage: %r");
+ }
+
+ free(k);
+ for(i=0; i<48; i++)
+ free(tmp[i]);
+}
+
+void
+initval2cmap(void)
+{
+ int i;
+
+ for(i=0; i<256; i++)
+ val2cmap[i] = rgb2cmap(i, i, i);
+}
+
+void
+setgtab(State *s)
+{
+ int i;
+
+ for(i=0; i<=1000; i++)
+ s->gtab[i] = val2cmap[(int)(255.0*pow((i/1000.0), 1.0/s->gamma))];
+}
+
+int
+section(int x)
+{
+ int ib, iw;
+
+ ib = state.black * 255.0;
+ iw = state.white * 255.0;
+
+ if(x<ib-5 || iw+5<x)
+ return -1;
+
+ iw -= ib;
+ x -= ib;
+ if(x < iw/3)
+ return 0;
+ if(x < 2*iw/3)
+ return 1;
+ return 2;
+}
+
+Image*
+copyimage(Image *i)
+{
+ Image *n;
+
+ if(i == nil)
+ return nil;
+
+ n = allocimage(display, i->r, i->chan, 0, DNofill);
+ if(n == nil)
+ sysfatal("allocimage: %r");
+
+ draw(n, n->r, i, nil, i->r.min);
+ return n;
+}
+
+Image*
+grey8image(Image *i)
+{
+ Image *n;
+
+ if(i->chan == GREY8)
+ return i;
+
+ n = allocimage(display, i->r, GREY8, 0, DNofill);
+ if(n == nil)
+ sysfatal("allocimage: %r");
+
+ draw(n, n->r, i, nil, i->r.min);
+ freeimage(i);
+ return n;
+}
+
+
+void
+mark(void)
+{
+ if(osmall != small){
+ freeimage(osmall);
+ osmall = small;
+ }
+ ostate = state;
+}
+
+void
+undo(void)
+{
+ if(small != osmall){
+ freeimage(small);
+ small = osmall;
+ }
+ state = ostate;
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ drawscreen(0);
+}
+
+void
+saveface(Face *f, int slot)
+{
+ if(slot == -1){
+ mark();
+ state = f->state;
+ small = copyimage(f->small);
+ drawface(-1);
+ drawscreen(0);
+ return;
+ }
+
+ if(face[slot]==nil)
+ face[slot] = emalloc(sizeof(*face[slot]));
+ else{
+ freeimage(face[slot]->small);
+ face[slot]->small = nil;
+ }
+
+ if(f == nil){
+ face[slot]->small = copyimage(small);
+ face[slot]->state = state;
+ }else{
+ face[slot]->small = copyimage(f->small);
+ face[slot]->state = f->state;
+ }
+ drawface(slot);
+}
+
+int
+writeface(char *outfile, Image *image)
+{
+ int i, fd, rv, y;
+ uchar data[48*48/2];
+
+ if(outfile == nil)
+ fd = 1;
+ else{
+ if((fd = create(outfile, OWRITE, 0666)) < 0)
+ return -1;
+ }
+
+ switch(image->chan) {
+ default:
+ rv = -1;
+ break;
+
+ case GREY1:
+ if(unloadimage(image, image->r, data, 48*48/8) != 48*48/8)
+ sysfatal("unloadimage: %r");
+ for(y=0; y<48; y++) {
+ for(i=0; i<3; i++)
+ fprint(fd, "0x%.2x%.2x,", data[y*6+i*2+0], data[y*6+i*2+1]);
+ fprint(fd, "\n");
+ }
+ rv = 0;
+ break;
+
+ case GREY2:
+ if(unloadimage(image, image->r, data, 48*48/4) != 48*48/4)
+ sysfatal("unloadimage: %r");
+ for(y=0; y<48; y++) {
+ for(i=0; i<3; i++)
+ fprint(fd, "0x%.2x%.2x,%.2x%.2x,",
+ data[y*12+i*4+0], data[y*12+i*4+1],
+ data[y*12+i*4+2], data[y*12+i*4+3]);
+ fprint(fd, "\n");
+ }
+ rv = 0;
+ break;
+
+ case GREY4:
+ case GREY8:
+ rv = writeimage(fd, image, 0); /* dolock? */
+ break;
+ }
+
+ if(outfile)
+ close(fd);
+ return rv;
+}
+
+void
+room(Rectangle out, Rectangle in, int *a)
+{
+ a[Left] = out.min.x - in.min.x;
+ a[Right] = out.max.x - in.max.x;
+ a[Top] = out.min.y - in.min.y;
+ a[Bottom] = out.max.y - in.max.y;
+}
+
+int
+min(int a, int b)
+{
+ if(a < b)
+ return a;
+ return b;
+}
+
+int
+max(int a, int b)
+{
+ if(a > b)
+ return a;
+ return b;
+}
+
+int
+move(Rectangle r, Rectangle picr, Point d, int k, Rectangle *rp)
+{
+ int a[4], i;
+ Rectangle oldr;
+ static int toggle;
+
+ oldr = r;
+ room(picr, r, a);
+ switch(k){
+ case RTopLeft:
+ i = (d.x+d.y)/2;
+ if(i>=Dx(r) || i>=Dy(r))
+ break;
+ i = max(i, a[Left]);
+ i = max(i, a[Top]);
+ r.min.x += i;
+ r.min.y += i;
+ break;
+ case RTop:
+ i = d.y;
+ if(i < 0){
+ /*
+ * should really check i/2, but this is safe and feedback
+ * makes the control feel right
+ */
+ i = -min(-i, a[Right]);
+ i = max(i, a[Left]);
+ }
+ i = max(i, a[Top]);
+ if(i >= Dy(r))
+ break;
+ r.min.y += i;
+ /* divide the half bit equally */
+ toggle = 1-toggle;
+ if(toggle){
+ r.min.x += i/2;
+ r.max.x = r.min.x+Dy(r);
+ }else{
+ r.max.x -= i/2;
+ r.min.x = r.max.x-Dy(r);
+ }
+ break;
+ case RTopRight:
+ i = (-d.x+d.y)/2;
+ if(i>=Dx(r) || i>=Dy(r))
+ break;
+ i = -min(-i, a[Right]);
+ i = max(i, a[Top]);
+ r.max.x -= i;
+ r.min.y += i;
+ break;
+ case RLeft:
+ i = d.x;
+ if(i < 0){
+ i = -min(-i, a[Bottom]);
+ i = max(i, a[Top]);
+ }
+ i = max(i, a[Left]);
+ if(i >= Dx(r))
+ break;
+ r.min.x += i;
+ /* divide the half bit equally */
+ toggle = 1-toggle;
+ if(toggle){
+ r.min.y += i/2;
+ r.max.y = r.min.y+Dx(r);
+ }else{
+ r.max.y -= i/2;
+ r.min.y = r.max.y-Dx(r);
+ }
+ break;
+ case RMiddle:
+ if(d.x >= 0)
+ d.x = min(d.x, a[Right]);
+ else
+ d.x = max(d.x, a[Left]);
+ if(d.y >= 0)
+ d.y = min(d.y, a[Bottom]);
+ else
+ d.y = max(d.y, a[Top]);
+ r = rectaddpt(r, d);
+ break;
+ case RRight:
+ i = d.x;
+ if(i > 0){
+ i = min(i, a[Bottom]);
+ i = -max(-i, a[Top]);
+ }
+ i = min(i, a[Right]);
+ if(-i >= Dx(r))
+ break;
+ r.max.x += i;
+ /* divide the half bit equally */
+ toggle = 1-toggle;
+ if(toggle){
+ r.min.y -= i/2;
+ r.max.y = r.min.y+Dx(r);
+ }else{
+ r.max.y += i/2;
+ r.min.y = r.max.y-Dx(r);
+ }
+ break;
+ case RBotLeft:
+ i = (d.x+-d.y)/2;
+ if(i>=Dx(r) || i>=Dy(r))
+ break;
+ i = max(i, a[Left]);
+ i = -min(-i, a[Bottom]);
+ r.min.x += i;
+ r.max.y -= i;
+ break;
+ case RBot:
+ i = d.y;
+ if(i > 0){
+ i = min(i, a[Right]);
+ i = -max(-i, a[Left]);
+ }
+ i = min(i, a[Bottom]);
+ if(i >= Dy(r))
+ break;
+ r.max.y += i;
+ /* divide the half bit equally */
+ toggle = 1-toggle;
+ if(toggle){
+ r.min.x -= i/2;
+ r.max.x = r.min.x+Dy(r);
+ }else{
+ r.max.x += i/2;
+ r.min.x = r.max.x-Dy(r);
+ }
+ break;
+ case RBotRight:
+ i = (-d.x+-d.y)/2;
+ if(i>=Dx(r) || i>=Dy(r))
+ break;
+ i = -min(-i, a[Right]);
+ i = -min(-i, a[Bottom]);
+ r.max.x -= i;
+ r.max.y -= i;
+ break;
+ }
+ if(Dx(r)<3 || Dy(r)<3){
+ *rp = oldr;
+ return 0;
+ }
+ *rp = r;
+ return !eqrect(r, oldr);
+}
+
+void
+rlist(Rectangle r, Rectangle *ra)
+{
+ Rectangle tr;
+
+ tr = r;
+ tr.max.y = r.min.y+Dy(r)/4;
+ ra[0] = tr;
+ ra[0].max.x = tr.min.x+Dx(tr)/4;
+ ra[1] = tr;
+ ra[1].min.x = ra[0].max.x;
+ ra[1].max.x = tr.max.x-Dx(tr)/4;
+ ra[2] = tr;
+ ra[2].min.x = ra[1].max.x;
+
+ tr.min.y = tr.max.y;
+ tr.max.y = r.max.y-Dy(r)/4;
+ ra[3] = tr;
+ ra[3].max.x = tr.min.x+Dx(tr)/4;
+ ra[4] = tr;
+ ra[4].min.x = ra[3].max.x;
+ ra[4].max.x = tr.max.x-Dx(tr)/4;
+ ra[5] = tr;
+ ra[5].min.x = ra[4].max.x;
+
+ tr.min.y = tr.max.y;
+ tr.max.y = r.max.y;
+ ra[6] = tr;
+ ra[6].max.x = tr.min.x+Dx(tr)/4;
+ ra[7] = tr;
+ ra[7].min.x = ra[6].max.x;
+ ra[7].max.x = tr.max.x-Dx(tr)/4;
+ ra[8] = tr;
+ ra[8].min.x = ra[7].max.x;
+}
+
+int
+abs(int a)
+{
+ if(a < 0)
+ return -a;
+ return a;
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: mug [file.bit]\n");
+ exits("usage");
+}
+
+void
+eresized(int new)
+{
+ if(new && getwindow(display, Refmesg) < 0)
+ fprint(2,"can't reattach to window");
+ drawscreen(1);
+
+}
+
+/*
+interface notes
+
+cursor changes while in rbig to indicate region.
+only button 1 works for resizing region
+only button 1 works for moving thingy in ramp
+
+button-3 menu: Reset, Depth, Undo, Save, Write
+*/
+
+Cursor tl = {
+ {-4, -4},
+ {0xfe, 0x00, 0x82, 0x00, 0x8c, 0x00, 0x87, 0xff,
+ 0xa0, 0x01, 0xb0, 0x01, 0xd0, 0x01, 0x11, 0xff,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x1f, 0x00, },
+ {0x00, 0x00, 0x7c, 0x00, 0x70, 0x00, 0x78, 0x00,
+ 0x5f, 0xfe, 0x4f, 0xfe, 0x0f, 0xfe, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, }
+};
+
+Cursor t = {
+ {-7, -8},
+ {0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x06, 0xc0,
+ 0x1c, 0x70, 0x10, 0x10, 0x0c, 0x60, 0xfc, 0x7f,
+ 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+ 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80, 0x03, 0x80,
+ 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor tr = {
+ {-11, -4},
+ {0x00, 0x7f, 0x00, 0x41, 0x00, 0x31, 0xff, 0xe1,
+ 0x80, 0x05, 0x80, 0x0d, 0x80, 0x0b, 0xff, 0x88,
+ 0x00, 0x88, 0x0, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0xf8, },
+ {0x00, 0x00, 0x00, 0x3e, 0x00, 0x0e, 0x00, 0x1e,
+ 0x7f, 0xfa, 0x7f, 0xf2, 0x7f, 0xf0, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x00, }
+};
+
+Cursor r = {
+ {-8, -7},
+ {0x07, 0xc0, 0x04, 0x40, 0x04, 0x40, 0x04, 0x58,
+ 0x04, 0x68, 0x04, 0x6c, 0x04, 0x06, 0x04, 0x02,
+ 0x04, 0x06, 0x04, 0x6c, 0x04, 0x68, 0x04, 0x58,
+ 0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x07, 0xc0, },
+ {0x00, 0x00, 0x03, 0x80, 0x03, 0x80, 0x03, 0x80,
+ 0x03, 0x90, 0x03, 0x90, 0x03, 0xf8, 0x03, 0xfc,
+ 0x03, 0xf8, 0x03, 0x90, 0x03, 0x90, 0x03, 0x80,
+ 0x03, 0x80, 0x03, 0x80, 0x03, 0x80, 0x00, 0x00, }
+};
+
+Cursor br = {
+ {-11, -11},
+ {0x00, 0xf8, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88,
+ 0xff, 0x88, 0x80, 0x0b, 0x80, 0x0d, 0x80, 0x05,
+ 0xff, 0xe1, 0x00, 0x31, 0x00, 0x41, 0x00, 0x7f, },
+ {0x00, 0x00, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x0, 0x70, 0x00, 0x70, 0x00, 0x70, 0x00, 0x70,
+ 0x00, 0x70, 0x7f, 0xf0, 0x7f, 0xf2, 0x7f, 0xfa,
+ 0x00, 0x1e, 0x00, 0x0e, 0x00, 0x3e, 0x00, 0x00, }
+};
+
+Cursor b = {
+ {-7, -7},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
+ 0xfc, 0x7f, 0x0c, 0x60, 0x10, 0x10, 0x1c, 0x70,
+ 0x06, 0xc0, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x7f, 0xfe, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x03, 0x80, 0x03, 0x80, 0x0f, 0xe0, 0x03, 0x80,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Cursor bl = {
+ {-4, -11},
+ {0x1f, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0x00, 0x11, 0x00, 0x11, 0x00, 0x11, 0x00,
+ 0x11, 0xff, 0xd0, 0x01, 0xb0, 0x01, 0xa0, 0x01,
+ 0x87, 0xff, 0x8c, 0x00, 0x82, 0x00, 0xfe, 0x00, },
+ {0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x0e, 0x00,
+ 0x0e, 0x00, 0x0f, 0xfe, 0x4f, 0xfe, 0x5f, 0xfe,
+ 0x78, 0x00, 0x70, 0x00, 0x7c, 0x00, 0x00, 0x0, }
+};
+
+Cursor l = {
+ {-7, -7},
+ {0x03, 0xe0, 0x02, 0x20, 0x02, 0x20, 0x1a, 0x20,
+ 0x16, 0x20, 0x36, 0x20, 0x60, 0x20, 0x40, 0x20,
+ 0x60, 0x20, 0x36, 0x20, 0x16, 0x20, 0x1a, 0x20,
+ 0x02, 0x20, 0x02, 0x20, 0x02, 0x20, 0x03, 0xe0, },
+ {0x00, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0,
+ 0x09, 0xc0, 0x09, 0xc0, 0x1f, 0xc0, 0x3f, 0xc0,
+ 0x1f, 0xc0, 0x09, 0xc0, 0x09, 0xc0, 0x01, 0xc0,
+ 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, }
+};
+
+Cursor boxcursor = {
+ {-7, -7},
+ {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
+ 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, },
+ {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
+ 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00, }
+};
+
+Cursor clearcursor;
+
+Cursor *corners[10] = {
+ &tl, &t, &tr,
+ &l, &boxcursor, &r,
+ &bl, &b, &br,
+ nil, /* default arrow */
+};
+
+char *item[] = {
+ "Reset",
+ "Depth",
+ "Undo",
+ "Write",
+ "Exit",
+ nil
+};
+
+Menu menu = {
+ item,
+ nil,
+ 2
+};
+
+/*BUG make less flashy */
+void
+moveface(Image *back, Point lastp, Image *face, Point p, Point d)
+{
+ draw(screen, rectaddpt(back->r, subpt(lastp, d)), back, nil, back->r.min);
+ draw(back, back->r, screen, nil, addpt(back->r.min, subpt(p, d)));
+ border(screen, rectaddpt(face->r, subpt(p, d)),
+ -1, display->black, ZP);
+ draw(screen, rectaddpt(face->r, subpt(p, d)),
+ face, nil, face->r.min);
+}
+
+int
+dragface(Mouse *m, Image *im, Point d, int x)
+{
+ int i;
+ Point lastp;
+ static Image *back;
+
+ if(back == nil){
+ back = allocimage(display, Rect(-1,-1,49,49), display->image->chan, 0, DNofill);
+ if(back == nil)
+ sysfatal("dragface backing store: %r");
+ }
+
+ lastp = m->xy;
+ draw(back, back->r, screen, nil, addpt(back->r.min, subpt(lastp, d)));
+ esetcursor(&clearcursor);
+ do{
+ moveface(back, lastp, im, m->xy, d);
+ lastp = m->xy;
+ }while(*m=emouse(), m->buttons==1);
+
+ draw(screen, rectaddpt(back->r, subpt(lastp, d)), back, nil, back->r.min);
+ esetcursor(nil);
+ if(m->buttons==0){
+ for(i=0; i<nelem(face); i++)
+ if(ptinrect(m->xy, rface[i]))
+ return i;
+ if(ptinrect(m->xy, rsmall))
+ return -1;
+ return x;
+ }
+ while(*m=emouse(), m->buttons)
+ ;
+ return x;
+}
+
+void
+initstate(void)
+{
+ state.black = 0.0;
+ state.white = 1.0;
+ state.stretch = 1.0;
+ state.depth = 4;
+ state.gamma = 1.0;
+ setgtab(&state);
+ state.selr = insetrect(orig->r, 5);
+ sdx = Dx(state.selr);
+ sdy = Dy(state.selr);
+ if(sdx > sdy)
+ state.selr.max.x = state.selr.min.x+sdy;
+ else
+ state.selr.max.y = state.selr.min.y+sdx;
+}
+
+void
+main(int argc, char **argv)
+{
+ int ccursor, i, fd, k, n, y;
+ uchar *data;
+ double gammatab[256];
+ Event e;
+ Mouse m;
+ Point lastp, p;
+ Rectangle nselr, rbig9[9];
+
+ ARGBEGIN{
+ default:
+ usage();
+ }ARGEND
+
+ if(argc > 1)
+ usage();
+ if(argc == 1){
+ if((fd = open(argv[0], OREAD)) < 0)
+ sysfatal("open %s: %r", argv[0]);
+ }else
+ fd = 0;
+
+ if (initdraw(0, 0, "mug") < 0)
+ sysfatal("initdraw failed");
+
+ if((orig = readimage(display, fd, 0)) == nil)
+ sysfatal("readimage: %r");
+
+ orig = grey8image(orig);
+
+ initramp();
+ initclamp();
+ initval2cmap();
+ bkgd = allocimagemix(display, DPaleyellow, DWhite);
+ small = allocimage(display, Rect(0,0,48,48), GREY4, 0, DWhite);
+ tmp8 = allocimage(display, Rect(0,0,48,48), GREY8, 0, DWhite);
+ red = allocimage(display, Rect(0,0,1,1), display->image->chan, 1, DRed);
+ green = allocimage(display, Rect(0,0,1,1), display->image->chan, 1, DGreen);
+ blue = allocimage(display, Rect(0,0,1,1), display->image->chan, 1, DBlue);
+ if(bkgd==nil || small==nil || tmp8==nil || red==nil || green==nil || blue==nil)
+ sysfatal("allocimage: %r");
+
+ n = Dx(orig->r)*Dy(orig->r);
+ data = emalloc(n*sizeof data[0]);
+ rdata = emalloc(n*sizeof rdata[0]);
+
+ if(unloadimage(orig, orig->r, data, n) != n)
+ sysfatal("unloadimage: %r");
+
+ for(i=0; i<256; i++)
+ gammatab[i] = pow((255-i)/(double)255.0, GAMMA);
+
+ for(i=0; i<n; i++)
+ rdata[i] = gammatab[255-data[i]];
+
+ initstate();
+ process(rdata, orig->r, state.selr, small);
+ drawscreen(1);
+ flushimage(display, 1);
+ einit(Emouse|Ekeyboard);
+ ccursor = 9;
+ for(;;){
+ if((n=eread(Emouse|Ekeyboard, &e))==Ekeyboard)
+ continue;
+ if(n != Emouse)
+ break;
+
+ m = e.mouse;
+ if(m.buttons&4){
+ ccursor = 9;
+ esetcursor(corners[ccursor]);
+ switch(emenuhit(3, &m, &menu)){
+ case -1:
+ continue;
+ case 0: /* Reset */
+ mark();
+ initstate();
+ small = allocimage(display, Rect(0,0,48,48), CHAN1(CGrey, state.depth), 0, DWhite);
+ if(small == nil)
+ sysfatal("allocimage: %r");
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ drawscreen(0);
+ break;
+ case 1: /* Depth */
+ mark();
+ /* osmall = small, so no freeimage */
+ state.depth /= 2;
+ if(state.depth == 0)
+ state.depth = 8;
+ small = allocimage(display, Rect(0,0,48,48), CHAN1(CGrey, state.depth), 0, DWhite);
+ if(small == nil)
+ sysfatal("allocimage: %r");
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ break;
+ case 2: /* Undo */
+ undo();
+ break;
+ case 3: /* Write */
+ writeface(nil, small);
+ break;
+ case 4: /* Exit */
+ exits(nil);
+ break;
+ }
+ }
+
+ if(ptinrect(m.xy, rbig)){
+ rlist(rectaddpt(state.selr, subpt(rbig.min, orig->r.min)), rbig9);
+ for(i=0; i<9; i++)
+ if(ptinrect(m.xy, rbig9[i]))
+ break;
+ if(i != ccursor){
+ ccursor = i;
+ esetcursor(corners[ccursor]);
+ }
+ if(i==9)
+ continue;
+
+ if(m.buttons & 1){
+ mark();
+ lastp = m.xy;
+ while(m=emouse(), m.buttons&1){
+ if(move(state.selr, orig->r, subpt(m.xy, lastp), i, &nselr)){
+ moveframe(state.selr, nselr);
+ state.selr = nselr;
+ lastp = m.xy;
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ }
+ }
+ }
+ continue;
+ }
+
+ if(ccursor != 9){ /* default cursor */
+ ccursor = 9;
+ esetcursor(corners[ccursor]);
+ }
+
+ if(ptinrect(m.xy, rramp)){
+ if(m.buttons != 1)
+ continue;
+ mark();
+ y = gamma2y(state.gamma);
+ if(abs(y-(m.xy.y-rramp.min.y)) > 5)
+ continue;
+ k = section(m.xy.x-rramp.min.x);
+ drawrampbar(green, &state);
+ lastp = m.xy;
+ while(m=emouse(), m.buttons&1){
+ if(!ptinrect(m.xy, rramp))
+ continue;
+ switch(k){
+ case -1:
+ continue;
+ case 0:
+ if((m.xy.x-rramp.min.x)/255.0 < state.white){
+ state.black = (m.xy.x-rramp.min.x)/255.0;
+ break;
+ }
+ continue;
+ case 1:
+ state.gamma = y2gamma(m.xy.y-rramp.min.y);
+ setgtab(&state);
+ break;
+ case 2:
+ if((m.xy.x-rramp.min.x)/255.0 > state.black){
+ state.white = (m.xy.x-rramp.min.x)/255.0;
+ break;
+ }
+ continue;
+ case 10:
+ state.black += (m.xy.x-lastp.x)/255.0;
+ state.white += (m.xy.x-lastp.x)/255.0;
+ state.gamma = y2gamma(p.y);
+ break;
+ }
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ drawrampbar(green, &state);
+ }
+ if(m.buttons == 0){
+ process(rdata, orig->r, state.selr, small);
+ drawface(-1);
+ drawrampbar(red, &state);
+ }else
+ undo();
+ continue;
+ }
+
+ if(ptinrect(m.xy, rsmall)){
+ if(m.buttons != 1)
+ continue;
+ n=dragface(&m, small, subpt(m.xy, rsmall.min), -1);
+ if(n == -1)
+ continue;
+ saveface(nil, n);
+ }
+
+ for(i=0; i<nelem(face); i++)
+ if(ptinrect(m.xy, rface[i]))
+ break;
+ if(i<nelem(face) && face[i] != nil){
+ if(m.buttons != 1)
+ continue;
+ n=dragface(&m, face[i]->small, subpt(m.xy, rface[i].min), i);
+ if(n == i)
+ continue;
+ saveface(face[i], n);
+ continue;
+ }
+
+ do
+ m = emouse();
+ while(m.buttons==1);
+ }
+ exits(nil);
+}