<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/rsc/mug.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/rsc/mug.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
#include &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;draw.h&gt;
#include &lt;event.h&gt;
#include &lt;cursor.h&gt;

#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\n", 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-&gt;r), Dy(orig-&gt;r)));

       for(i=0; i&lt;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&lt;4; i++)
               rface[i] = fr[i];
       for(i=4; i&lt;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-&gt;black, ZP);
       if(face[i])
               draw(screen, rface[i], face[i]-&gt;small, nil, ZP);
       else
               draw(screen, rface[i], display-&gt;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-&gt;white*255.0);
       r.min.x += (int)(s-&gt;black*255.0);
       r.min.y += gamma2y(s-&gt;gamma);
       r.max.y = r.min.y+1;
       rectclip(&amp;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(&amp;r, rramp);
       draw(screen, r, color, nil, ZP);
       combinerect(&amp;br, r);

       r = liner;
       r.max.x = r.min.x+3;
       rectclip(&amp;r, rramp);
       draw(screen, r, color, nil, ZP);
       combinerect(&amp;br, r);

       r = liner;
       r.min.x = r.max.x-3;
       rectclip(&amp;r, rramp);
       draw(screen, r, color, nil, ZP);
       combinerect(&amp;br, r);
}

void
drawscreen(int clear)
{
       int i;

       if(clear){
               geometry(screen-&gt;r);
               draw(screen, screen-&gt;r, bkgd, nil, ZP);
       }

       border(screen, rbig, -1, display-&gt;black, ZP);
       draw(screen, rbig, orig, nil, orig-&gt;r.min);

       border(screen, rramp, -1, display-&gt;black, ZP);
       draw(screen, rramp, ramp, nil, ramp-&gt;r.min);
       drawrampbar(red, &amp;state);

       border(screen, rectaddpt(state.selr, subpt(rbig.min, orig-&gt;r.min)), -2, red, ZP);
       if(clear){
               drawface(-1);
               for(i=0; i&lt;nelem(face); i++)
                       drawface(i);
       }
}

void
moveframe(Rectangle old, Rectangle new)
{
       border(screen, rectaddpt(old, subpt(rbig.min, orig-&gt;r.min)), -2, orig, old.min);
       border(screen, rectaddpt(new, subpt(rbig.min, orig-&gt;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&lt;256; y++) {
               g = y2gamma(y);
               for(x=0; x&lt;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-&gt;r, dat, sizeof dat) != sizeof dat)
               sysfatal("loadimage: %r");
}

void
initclamp(void)
{
       int i;

       for(i=0; i&lt;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 &lt;= 2)
               return;

       k = emalloc(ksize*sizeof(k[0]));

       /* center of box */
       for(i=1; i&lt;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&lt;ksize; i++)
               sum += k[i];

       for(i=0; i&lt;ksize; i++)
               k[i] /= sum;

       ksizeby2 = ksize/2;

       for(i=0; i&lt;48; i++)
               tmp[i] = emalloc(datadx*sizeof(tmp[i][0]));

       /* squeeze vertically */
       for(i=0; i&lt;48; i++) {
               ii = r.min.y+i*dy/48;
               tt = tmp[i];
               uu = ii - ksizeby2;
               for(j=r.min.x-ksize; j&lt;r.max.x+ksize; j++) {
                       if(j&lt;datar.min.x || j&gt;=datar.max.x)
                               continue;
                       w = 0.0;

                       uuu = uu*datadx+j;
                       if(uu&gt;=datar.min.y &amp;&amp; uu+ksize&lt;datar.max.y)
                               for(u=0; u&lt;ksize; u++){
                                       w += k[u]*data[uuu];
                                       uuu += datadx;
                               }
                       else
                               for(u=0; u&lt;ksize; u++){
                                       if(uu+u&gt;=datar.min.y &amp;&amp; uu+u&lt;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&lt;48; i++) {
               tt = tmp[i];
               for(j=0; j&lt;48; j++) {
                       jj = r.min.x+j*dx/48;
                       w = 0.0;
                       for(v=0; v&lt;ksize; v++) {
                               vv = jj - ksizeby2 + v;
                               if(vv&lt;datar.min.x || vv&gt;=datar.max.x) {
                                       w += k[v];              /* assume white surround */
                                       continue;
                               }
                               w += k[v]*tt[vv-datar.min.x];
                       }
                       if(w &lt; black || black==white)
                               w = 0.0;
                       else if(w &gt; 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-&gt;chan != GREY8) {
               u = 0;
               dp = small-&gt;depth;
               for(i=0; i&lt;48; i++) {
                       sm = 0xFF ^ (0xFF&gt;&gt;dp);
                       sh = 0;
                       v = 0;
                       for(j=0; j&lt;48; j++) {
                               ii = 48*i+j;
                               sd = clamp[sdata[ii]+256];
                               sv = sd&amp;sm;
                               v |= sv&gt;&gt;sh;
                               sh += dp;
                               if(sh == 8) {
                                       bdata[u++] = v;
                                       v = 0;
                                       sh = 0;
                               }

                               /* propagate error, with decay (sum errors &lt; 1) */
                               error = sd - sv;
                               if(ii+49 &lt; 48*48) {  /* one test is enough, really */
                                       sdata[ii+1] = sdata[ii+1]+((3*error)&gt;&gt;4);
                                       sdata[ii+48] = sdata[ii+48]+((3*error)&gt;&gt;4);
                                       sdata[ii+49] = sdata[ii+49]+((3*error)&gt;&gt;3);
                               }

                               /* produce correct color map value by copying bits */
                               switch(dp){
                               case 1:
                                       sv |= sv&gt;&gt;1;
                               case 2:
                                       sv |= sv&gt;&gt;2;
                               case 4:
                                       sv |= sv&gt;&gt;4;
                               }
                               sdata[ii] = sv;
                       }
               }
               for(i=0; i&lt;nelem(bdata); i++)
                       bdata[i] = sdata[i];
               if(loadimage(tmp8, tmp8-&gt;r, bdata, sizeof bdata) != sizeof bdata)
                       sysfatal("loadimage: %r");
               draw(small, small-&gt;r, tmp8, nil, tmp8-&gt;r.min);
       } else {
               for(i=0; i&lt;nelem(bdata); i++)
                       bdata[i] = sdata[i];
               if(loadimage(small, small-&gt;r, bdata, sizeof bdata) != sizeof bdata)
                       sysfatal("loadimage: %r");
       }

       free(k);
       for(i=0; i&lt;48; i++)
               free(tmp[i]);
}

void
initval2cmap(void)
{
       int i;

       for(i=0; i&lt;256; i++)
               val2cmap[i] = rgb2cmap(i, i, i);
}

void
setgtab(State *s)
{
       int i;

       for(i=0; i&lt;=1000; i++)
               s-&gt;gtab[i] = val2cmap[(int)(255.0*pow((i/1000.0), 1.0/s-&gt;gamma))];
}

int
section(int x)
{
       int ib, iw;

       ib = state.black * 255.0;
       iw = state.white * 255.0;

       if(x&lt;ib-5 || iw+5&lt;x)
               return -1;

       iw -= ib;
       x -= ib;
       if(x &lt; iw/3)
               return 0;
       if(x &lt; 2*iw/3)
               return 1;
       return 2;
}

Image*
copyimage(Image *i)
{
       Image *n;

       if(i == nil)
               return nil;

       n = allocimage(display, i-&gt;r, i-&gt;chan, 0, DNofill);
       if(n == nil)
               sysfatal("allocimage: %r");

       draw(n, n-&gt;r, i, nil, i-&gt;r.min);
       return n;
}

Image*
grey8image(Image *i)
{
       Image *n;

       if(i-&gt;chan == GREY8)
               return i;

       n = allocimage(display, i-&gt;r, GREY8, 0, DNofill);
       if(n == nil)
               sysfatal("allocimage: %r");

       draw(n, n-&gt;r, i, nil, i-&gt;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-&gt;r, state.selr, small);
       drawface(-1);
       drawscreen(0);
}

void
saveface(Face *f, int slot)
{
       if(slot == -1){
               mark();
               state = f-&gt;state;
               small = copyimage(f-&gt;small);
               drawface(-1);
               drawscreen(0);
               return;
       }

       if(face[slot]==nil)
               face[slot] = emalloc(sizeof(*face[slot]));
       else{
               freeimage(face[slot]-&gt;small);
               face[slot]-&gt;small = nil;
       }

       if(f == nil){
               face[slot]-&gt;small = copyimage(small);
               face[slot]-&gt;state = state;
       }else{
               face[slot]-&gt;small = copyimage(f-&gt;small);
               face[slot]-&gt;state = f-&gt;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)) &lt; 0)
                       return -1;
       }

       switch(image-&gt;chan) {
       default:
               rv = -1;
               break;

       case GREY1:
               if(unloadimage(image, image-&gt;r, data, 48*48/8) != 48*48/8)
                       sysfatal("unloadimage: %r");
               for(y=0; y&lt;48; y++) {
                       for(i=0; i&lt;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-&gt;r, data, 48*48/4) != 48*48/4)
                       sysfatal("unloadimage: %r");
               for(y=0; y&lt;48; y++) {
                       for(i=0; i&lt;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 &lt; b)
               return a;
       return b;
}

int
max(int a, int b)
{
       if(a &gt; 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&gt;=Dx(r) || i&gt;=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 &lt; 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 &gt;= 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&gt;=Dx(r) || i&gt;=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 &lt; 0){
                       i = -min(-i, a[Bottom]);
                       i = max(i, a[Top]);
               }
               i = max(i, a[Left]);
               if(i &gt;= 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 &gt;= 0)
                       d.x = min(d.x, a[Right]);
               else
                       d.x = max(d.x, a[Left]);
               if(d.y &gt;= 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 &gt; 0){
                       i = min(i, a[Bottom]);
                       i = -max(-i, a[Top]);
               }
               i = min(i, a[Right]);
               if(-i &gt;= 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&gt;=Dx(r) || i&gt;=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 &gt; 0){
                       i = min(i, a[Right]);
                       i = -max(-i, a[Left]);
               }
               i = min(i, a[Bottom]);
               if(i &gt;= 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&gt;=Dx(r) || i&gt;=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)&lt;3 || Dy(r)&lt;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 &lt; 0)
               return -a;
       return a;
}

void
usage(void)
{
       fprint(2, "usage: mug [file.bit]\n");
       exits("usage");
}

void
eresized(int new)
{
       if(new &amp;&amp; getwindow(display, Refmesg) &lt; 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] = {
       &amp;tl,        &amp;t, &amp;tr,
       &amp;l, &amp;boxcursor, &amp;r,
       &amp;bl,        &amp;b, &amp;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-&gt;r, subpt(lastp, d)), back, nil, back-&gt;r.min);
       draw(back, back-&gt;r, screen, nil, addpt(back-&gt;r.min, subpt(p, d)));
       border(screen, rectaddpt(face-&gt;r, subpt(p, d)),
                -1, display-&gt;black, ZP);
       draw(screen, rectaddpt(face-&gt;r, subpt(p, d)),
               face, nil, face-&gt;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-&gt;image-&gt;chan, 0, DNofill);
               if(back == nil)
                       sysfatal("dragface backing store: %r");
       }

       lastp = m-&gt;xy;
       draw(back, back-&gt;r, screen, nil, addpt(back-&gt;r.min, subpt(lastp, d)));
       esetcursor(&amp;clearcursor);
       do{
               moveface(back, lastp, im, m-&gt;xy, d);
               lastp = m-&gt;xy;
       }while(*m=emouse(), m-&gt;buttons==1);

       draw(screen, rectaddpt(back-&gt;r, subpt(lastp, d)), back, nil, back-&gt;r.min);
       esetcursor(nil);
       if(m-&gt;buttons==0){
               for(i=0; i&lt;nelem(face); i++)
                       if(ptinrect(m-&gt;xy, rface[i]))
                               return i;
               if(ptinrect(m-&gt;xy, rsmall))
                       return -1;
               return x;
       }
       while(*m=emouse(), m-&gt;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(&amp;state);
       state.selr = insetrect(orig-&gt;r, 5);
       sdx = Dx(state.selr);
       sdy = Dy(state.selr);
       if(sdx &gt; 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 &gt; 1)
               usage();
       if(argc == 1){
               if((fd = open(argv[0], OREAD)) &lt; 0)
                       sysfatal("open %s: %r", argv[0]);
       }else
               fd = 0;

       initdraw(0, 0, "mug");

       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-&gt;image-&gt;chan, 1, DRed);
       green = allocimage(display, Rect(0,0,1,1), display-&gt;image-&gt;chan, 1, DGreen);
       blue = allocimage(display, Rect(0,0,1,1), display-&gt;image-&gt;chan, 1, DBlue);
       if(bkgd==nil || small==nil || tmp8==nil || red==nil || green==nil || blue==nil)
               sysfatal("allocimage: %r");

       n = Dx(orig-&gt;r)*Dy(orig-&gt;r);
       data = emalloc(n*sizeof data[0]);
       rdata = emalloc(n*sizeof rdata[0]);

       if(unloadimage(orig, orig-&gt;r, data, n) != n)
               sysfatal("unloadimage: %r");

       for(i=0; i&lt;256; i++)
               gammatab[i] = pow((255-i)/(double)255.0, GAMMA);

       for(i=0; i&lt;n; i++)
               rdata[i] = gammatab[255-data[i]];

       initstate();
       process(rdata, orig-&gt;r, state.selr, small);
       drawscreen(1);
       flushimage(display, 1);
       einit(Emouse|Ekeyboard);
       ccursor = 9;
       for(;;){
               if((n=eread(Emouse|Ekeyboard, &amp;e))==Ekeyboard)
                       continue;
               if(n != Emouse)
                       break;

               m = e.mouse;
               if(m.buttons&amp;4){
                       ccursor = 9;
                       esetcursor(corners[ccursor]);
                       switch(emenuhit(3, &amp;m, &amp;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-&gt;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-&gt;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-&gt;r.min)), rbig9);
                       for(i=0; i&lt;9; i++)
                               if(ptinrect(m.xy, rbig9[i]))
                                       break;
                       if(i != ccursor){
                               ccursor = i;
                               esetcursor(corners[ccursor]);
                       }
                       if(i==9)
                               continue;

                       if(m.buttons &amp; 1){
                               mark();
                               lastp = m.xy;
                               while(m=emouse(), m.buttons&amp;1){
                                       if(move(state.selr, orig-&gt;r, subpt(m.xy, lastp), i, &amp;nselr)){
                                               moveframe(state.selr, nselr);
                                               state.selr = nselr;
                                               lastp = m.xy;
                                               process(rdata, orig-&gt;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)) &gt; 5)
                               continue;
                       k = section(m.xy.x-rramp.min.x);
                       drawrampbar(green, &amp;state);
                       lastp = m.xy;
                       while(m=emouse(), m.buttons&amp;1){
                               if(!ptinrect(m.xy, rramp))
                                       continue;
                               switch(k){
                               case -1:
                                       continue;
                               case 0:
                                       if((m.xy.x-rramp.min.x)/255.0 &lt; 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(&amp;state);
                                       break;
                               case 2:
                                       if((m.xy.x-rramp.min.x)/255.0 &gt; 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-&gt;r, state.selr, small);
                               drawface(-1);
                               drawrampbar(green, &amp;state);
                       }
                       if(m.buttons == 0){
                               process(rdata, orig-&gt;r, state.selr, small);
                               drawface(-1);
                               drawrampbar(red, &amp;state);
                       }else
                               undo();
                       continue;
               }

               if(ptinrect(m.xy, rsmall)){
                       if(m.buttons != 1)
                               continue;
                       n=dragface(&amp;m, small, subpt(m.xy, rsmall.min), -1);
                       if(n == -1)
                               continue;
                       saveface(nil, n);
               }

               for(i=0; i&lt;nelem(face); i++)
                       if(ptinrect(m.xy, rface[i]))
                               break;
               if(i&lt;nelem(face) &amp;&amp; face[i] != nil){
                       if(m.buttons != 1)
                               continue;
                       n=dragface(&amp;m, face[i]-&gt;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);
}
<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:[email protected]">[email protected]</a>.</span>
</font></center>
</body>
</html>