ref: cb6f6fc863f56fecdf9adc97e0cd48255b14841c
dir: /prez.c/
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <event.h>
#include <keyboard.h>
#include "dat.h"
#include "fns.h"
int zoom = 1;
int mode = Modenormal;
/*
* get bounding rectangle for stroke from r.min to r.max with
* specified brush (size).
*/
Rectangle
strokerect(Rectangle r, int brush)
{
r = canonrect(r);
return Rect(r.min.x-brush, r.min.y-brush, r.max.x+brush+1, r.max.y+brush+1);
}
Rectangle
textrect(Point p, char *s)
{
return Rpt(p, addpt(p, Pt(strlen(s) * font->width, font->height)));
}
/*
* draw stroke from r.min to r.max to dst with color ink and
* brush (size).
*/
void
strokedraw(Image *dst, Rectangle r, Image *ink, int brush)
{
if(!eqpt(r.min, r.max))
line(dst, r.min, r.max, Enddisc, Enddisc, brush, ink, ZP);
fillellipse(dst, r.max, brush, brush, ink, ZP);
}
/*
* A draw operation that touches only the area contained in bot but not in top.
* mp and sp get aligned with bot.min.
*/
void
gendrawdiff(Image *dst, Rectangle bot, Rectangle top,
Image *src, Point sp, Image *mask, Point mp, int op)
{
Rectangle r;
Point origin;
Point delta;
if(Dx(bot)*Dy(bot) == 0)
return;
/* no points in bot - top */
if(rectinrect(bot, top))
return;
/* bot - top ≡ bot */
if(Dx(top)*Dy(top)==0 || rectXrect(bot, top)==0){
gendrawop(dst, bot, src, sp, mask, mp, op);
return;
}
origin = bot.min;
/* split bot into rectangles that don't intersect top */
/* left side */
if(bot.min.x < top.min.x){
r = Rect(bot.min.x, bot.min.y, top.min.x, bot.max.y);
delta = subpt(r.min, origin);
gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
bot.min.x = top.min.x;
}
/* right side */
if(bot.max.x > top.max.x){
r = Rect(top.max.x, bot.min.y, bot.max.x, bot.max.y);
delta = subpt(r.min, origin);
gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
bot.max.x = top.max.x;
}
/* top */
if(bot.min.y < top.min.y){
r = Rect(bot.min.x, bot.min.y, bot.max.x, top.min.y);
delta = subpt(r.min, origin);
gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
bot.min.y = top.min.y;
}
/* bottom */
if(bot.max.y > top.max.y){
r = Rect(bot.min.x, top.max.y, bot.max.x, bot.max.y);
delta = subpt(r.min, origin);
gendrawop(dst, r, src, addpt(sp, delta), mask, addpt(mp, delta), op);
bot.max.y = top.max.y;
}
}
int
alphachan(ulong chan)
{
for(; chan; chan >>= 8)
if(TYPE(chan) == CAlpha)
return 1;
return 0;
}
void
zoomdraw(Image *d, Rectangle r, Rectangle top, Image *b, Image *s, Point sp, int f)
{
Rectangle dr;
Image *t;
Point a;
int w;
a = ZP;
if(r.min.x < d->r.min.x){
sp.x += (d->r.min.x - r.min.x)/f;
a.x = (d->r.min.x - r.min.x)%f;
r.min.x = d->r.min.x;
}
if(r.min.y < d->r.min.y){
sp.y += (d->r.min.y - r.min.y)/f;
a.y = (d->r.min.y - r.min.y)%f;
r.min.y = d->r.min.y;
}
rectclip(&r, d->r);
w = s->r.max.x - sp.x;
if(w > Dx(r))
w = Dx(r);
dr = r;
dr.max.x = dr.min.x+w;
if(!alphachan(s->chan))
b = nil;
if(f <= 1){
if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
gendrawdiff(d, dr, top, s, sp, nil, ZP, SoverD);
return;
}
if((t = allocimage(display, dr, s->chan, 0, 0)) == nil)
return;
for(; dr.min.y < r.max.y; dr.min.y++){
dr.max.y = dr.min.y+1;
draw(t, dr, s, nil, sp);
if(++a.y == f){
a.y = 0;
sp.y++;
}
}
dr = r;
for(sp=dr.min; dr.min.x < r.max.x; sp.x++){
dr.max.x = dr.min.x+1;
if(b) gendrawdiff(d, dr, top, b, sp, nil, ZP, SoverD);
gendrawdiff(d, dr, top, t, sp, nil, ZP, SoverD);
for(dr.min.x++; ++a.x < f && dr.min.x < r.max.x; dr.min.x++){
dr.max.x = dr.min.x+1;
gendrawdiff(d, dr, top, d, Pt(dr.min.x-1, dr.min.y), nil, ZP, SoverD);
}
a.x = 0;
}
freeimage(t);
}
void
update(Rectangle *rp){
if(canvas==nil)
draw(screen, screen->r, back, nil, ZP);
else {
if(rp == nil)
rp = &canvas->r;
gendrawdiff(screen, screen->r, c2sr(canvas->r), back, ZP, nil, ZP, SoverD);
zoomdraw(screen, c2sr(*rp), ZR, back, canvas, rp->min, zoom);
}
flushimage(display, 1);
}
typedef struct {
Rectangle r;
Rectangle r0;
Image* dst;
int yscan; /* current scanline */
int wscan; /* bscan width in bytes */
Image* iscan; /* scanline image */
uchar* bscan; /* scanline buffer */
int nmask; /* size of bmask in bytes */
int wmask; /* width of bmask in bytes */
Image* imask; /* mask image */
uchar* bmask; /* mask buffer */
int ncmp;
uchar bcmp[4];
} Filldata;
void
fillscan(Filldata *f, Point p0)
{
int x, y;
uchar *b;
x = p0.x;
y = p0.y;
b = f->bmask + y*f->wmask;
if(b[x/8] & 0x80>>(x%8))
return;
if(f->yscan != y){
draw(f->iscan, f->iscan->r, f->dst, nil, Pt(f->r.min.x, f->r.min.y+y));
if(unloadimage(f->iscan, f->iscan->r, f->bscan, f->wscan) < 0)
return;
f->yscan = y;
}
for(x = p0.x; x >= 0; x--){
if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
break;
b[x/8] |= 0x80>>(x%8);
}
for(x = p0.x+1; x < f->r0.max.x; x++){
if(memcmp(f->bscan + x*f->ncmp, f->bcmp, f->ncmp))
break;
b[x/8] |= 0x80>>(x%8);
}
y = p0.y-1;
if(y >= 0){
for(x = p0.x; x >= 0; x--){
if((b[x/8] & 0x80>>(x%8)) == 0)
break;
fillscan(f, Pt(x, y));
}
for(x = p0.x+1; x < f->r0.max.x; x++){
if((b[x/8] & 0x80>>(x%8)) == 0)
break;
fillscan(f, Pt(x, y));
}
}
y = p0.y+1;
if(y < f->r0.max.y){
for(x = p0.x; x >= 0; x--){
if((b[x/8] & 0x80>>(x%8)) == 0)
break;
fillscan(f, Pt(x, y));
}
for(x = p0.x+1; x < f->r0.max.x; x++){
if((b[x/8] & 0x80>>(x%8)) == 0)
break;
fillscan(f, Pt(x, y));
}
}
}
void
floodfill(Image *dst, Rectangle r, Point p, Image *src)
{
Filldata f;
if(!rectclip(&r, dst->r))
return;
if(!ptinrect(p, r))
return;
memset(&f, 0, sizeof(f));
f.dst = dst;
f.r = r;
f.r0 = rectsubpt(r, r.min);
f.wmask = bytesperline(f.r0, 1);
f.nmask = f.wmask*f.r0.max.y;
if((f.bmask = mallocz(f.nmask, 1)) == nil)
goto out;
if((f.imask = allocimage(display, f.r0, GREY1, 0, DNofill)) == nil)
goto out;
r = f.r0;
r.max.y = 1;
if((f.iscan = allocimage(display, r, RGB24, 0, DNofill)) == nil)
goto out;
f.yscan = -1;
f.wscan = bytesperline(f.iscan->r, f.iscan->depth);
if((f.bscan = mallocz(f.wscan, 0)) == nil)
goto out;
r = Rect(0,0,1,1);
f.ncmp = (f.iscan->depth+7) / 8;
draw(f.iscan, r, dst, nil, p);
if(unloadimage(f.iscan, r, f.bcmp, sizeof(f.bcmp)) < 0)
goto out;
fillscan(&f, subpt(p, f.r.min));
loadimage(f.imask, f.imask->r, f.bmask, f.nmask);
draw(f.dst, f.r, src, f.imask, f.imask->r.min);
out:
free(f.bmask);
free(f.bscan);
if(f.iscan)
freeimage(f.iscan);
if(f.imask)
freeimage(f.imask);
}
void
translate(Point d)
{
Rectangle r, nr;
if(canvas==nil || d.x==0 && d.y==0)
return;
r = c2sr(canvas->r);
nr = rectaddpt(r, d);
rectclip(&r, screen->clipr);
draw(screen, rectaddpt(r, d), screen, nil, r.min);
zoomdraw(screen, nr, rectaddpt(r, d), back, canvas, canvas->r.min, zoom);
gendrawdiff(screen, screen->r, nr, back, ZP, nil, ZP, SoverD);
spos = addpt(spos, d);
flushimage(display, 1);
}
void
setzoom(Point o, int z)
{
if(z < 1)
return;
cpos = s2c(o);
spos = o;
zoom = z;
update(nil);
}
void
center(void)
{
cpos = ZP;
if(canvas)
cpos = addpt(canvas->r.min,
divpt(subpt(canvas->r.max, canvas->r.min), 2));
spos = addpt(screen->r.min,
divpt(subpt(screen->r.max, screen->r.min), 2));
update(nil);
}
static void
usage(void)
{
fprint(2, "usage: %s [-b] [-s width height] [file]\n", argv0);
exits("usage");
}
/* FIXME */
int pipeline(char*,...); // cmd.c
int hitpal(Mouse);
void
main(int argc, char *argv[])
{
char *filename, *s, buf[1024];
Rectangle r, rr;
Image *img;
int invbg, fd;
Event e;
Mouse m;
Point p, d, sz;
filename = nil;
invbg = 0;
sz = ZP;
ARGBEGIN {
case 'b':
invbg = 1;
break;
case 's':
sz.x = strtol(EARGF(usage()), nil, 0);
sz.y = strtol(EARGF(usage()), nil, 0);
if(sz.x < 2 || sz.y < 2)
usage();
break;
default:
usage();
} ARGEND;
if(argc == 1)
filename = strdup(argv[0]);
else if(argc != 0)
usage();
if(initdraw(0, 0, "paint") < 0)
sysfatal("initdraw: %r");
initcnv(sz, filename);
initpal(invbg);
drawpal();
center();
einit(Emouse | Ekeyboard);
initcmd();
for(;;) {
switch(event(&e)){
case Emouse:
if(hitpal(e.mouse))
continue;
img = ink;
switch(e.mouse.buttons & 7){
case 2:
img = back;
/* no break */
case 1:
p = s2c(e.mouse.xy);
if(brush == NBRUSH-1){
/* flood fill brush */
if(canvas == nil || !ptinrect(p, canvas->r)){
back = img;
drawpal();
update(nil);
break;
}
r = canvas->r;
save(r, 0);
floodfill(canvas, r, p, img);
update(&r);
/* wait for mouse release */
while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0)
;
break;
}
// FIXME: clean up
r = strokerect(Rpt(p, p), brush);
expand(r);
rr = r;
d = p;
save(canvas->r, 1);
if(mode == Modenormal){
strokedraw(canvas, Rpt(p, p), img, brush);
update(&r);
}else if(mode == Modetext){
if(eenter(nil, buf, sizeof(buf), &e.mouse) <= 0)
break;
string(canvas, p, img, ZP, font, buf);
r = textrect(p, buf);
update(&r);
shrinksaved(r);
break;
}
for(;;){
m = e.mouse;
if(event(&e) != Emouse)
break;
if((e.mouse.buttons ^ m.buttons) & 7)
break;
d = s2c(e.mouse.xy);
if(eqpt(d, p))
continue;
r = strokerect(Rpt(p, d), brush);
expand(r);
if(mode == Modenormal){
strokedraw(canvas, Rpt(p, d), img, brush);
update(&r);
combinerect(&rr, r);
p = d;
}
}
switch(mode){
case Modelines:
rr = r;
r = Rpt(p, d);
if(Dx(r) == 0 || Dy(r) == 0)
break;
strokedraw(canvas, r, img, brush);
update(&rr);
break;
case Moderects:
rr = r;
r = Rpt(p, d);
if(Dx(r) == 0 || Dy(r) == 0)
break;
strokedraw(canvas, Rpt(r.min, Pt(r.max.x, r.min.y)), img, brush);
strokedraw(canvas, Rpt(r.min, Pt(r.min.x, r.max.y)), img, brush);
strokedraw(canvas, Rpt(Pt(r.min.x, r.max.y), r.max), img, brush);
strokedraw(canvas, Rpt(Pt(r.max.x, r.min.y), r.max), img, brush);
update(&rr);
break;
}
shrinksaved(canonrect(rr));
break;
case 4:
for(;;){
m = e.mouse;
if(event(&e) != Emouse)
break;
if((e.mouse.buttons & 7) != 4)
break;
translate(subpt(e.mouse.xy, m.xy));
}
break;
}
break;
case Ekeyboard:
switch(e.kbdc){
case 'L':
mode = mode == Modelines ? Modenormal : Modelines;
break;
case 'R':
mode = mode == Moderects ? Modenormal : Moderects;
break;
case 'T':
mode = mode == Modetext ? Modenormal : Modetext;
break;
case Kesc:
zoom = 1;
center();
mode = Modenormal;
break;
case '+':
if(zoom < 0x1000)
setzoom(e.mouse.xy, zoom*2);
break;
case '-':
if(zoom > 1)
setzoom(e.mouse.xy, zoom/2);
break;
case 'c':
if(canvas == nil)
break;
save(canvas->r, 0);
freeimage(canvas);
canvas = nil;
update(nil);
break;
case 'u':
restore(1);
break;
case 'f':
brush = NBRUSH-1;
drawpal();
break;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
brush = e.kbdc - '0';
drawpal();
break;
default:
if(e.kbdc == Kdel)
e.kbdc = 'q';
buf[0] = 0;
if(filename && (e.kbdc == 'r' || e.kbdc == 'w'))
snprint(buf, sizeof(buf), "%C %s", e.kbdc, filename);
else if(e.kbdc > 0x20 && e.kbdc < 0x7f)
snprint(buf, sizeof(buf), "%C", e.kbdc);
if(eenter("Cmd", buf, sizeof(buf), &e.mouse) <= 0)
break;
if(strcmp(buf, "q") == 0)
exits(nil);
s = buf+1;
while(*s == ' ' || *s == '\t')
s++;
if(*s == 0)
break;
switch(buf[0]){
case 'r':
if((fd = open(s, OREAD)) < 0){
Error:
snprint(buf, sizeof(buf), "%r");
eenter(buf, nil, 0, &e.mouse);
break;
}
free(filename);
filename = strdup(s);
Readimage:
unlockdisplay(display);
img = readimage(display, fd, 1);
close(fd);
lockdisplay(display);
if(img == nil){
werrstr("readimage: %r");
goto Error;
}
if(canvas){
save(canvas->r, 0);
freeimage(canvas);
}
canvas = img;
center();
break;
case 'w':
if((fd = create(s, OWRITE, 0660)) < 0)
goto Error;
free(filename);
filename = strdup(s);
Writeimage:
if(canvas)
if(writeimage(fd, canvas, 0) < 0){
close(fd);
werrstr("writeimage: %r");
goto Error;
}
close(fd);
break;
case '<':
if((fd = pipeline("%s", s)) < 0)
goto Error;
goto Readimage;
case '>':
if((fd = pipeline("%s", s)) < 0)
goto Error;
goto Writeimage;
case '|':
if(canvas == nil)
break;
if((fd = pipeline("%s", s)) < 0)
goto Error;
switch(rfork(RFMEM|RFPROC|RFFDG)){
case -1:
close(fd);
werrstr("rfork: %r");
goto Error;
case 0:
writeimage(fd, canvas, 1);
exits(nil);
}
goto Readimage;
}
break;
}
break;
}
}
}
void
eresized(int)
{
if(getwindow(display, Refnone) < 0)
sysfatal("resize failed");
drawpal();
update(nil);
}