1241 lines
24 KiB
C
1241 lines
24 KiB
C
#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: %r");
|
|
|
|
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);
|
|
}
|