diff options
author | Tobias Heinicke <theinicke@bss-wf.de> | 2017-09-06 14:30:17 +0200 |
---|---|---|
committer | Russ Cox <rsc@swtch.com> | 2018-11-15 22:10:26 -0500 |
commit | a309537fdc8d86131522d43f9a9b2a0f58d9bda9 (patch) | |
tree | d716fe1cca6a85ea630140962b36f0a8dff7b5ff | |
parent | c63d31a8c1d02e7fa45403a5a10373439d29b250 (diff) | |
download | plan9port-a309537fdc8d86131522d43f9a9b2a0f58d9bda9.tar.gz plan9port-a309537fdc8d86131522d43f9a9b2a0f58d9bda9.tar.bz2 plan9port-a309537fdc8d86131522d43f9a9b2a0f58d9bda9.zip |
paint: add drawing program from 9front (#112)
Paint first appeared in 9front. The 9front license is reproduced
in the related source files - the original repository is located at
https://code.9front.org/hg/plan9front.
-rw-r--r-- | man/man1/paint.1 | 85 | ||||
-rw-r--r-- | src/cmd/paint/eenter.c | 258 | ||||
-rw-r--r-- | src/cmd/paint/mkfile | 11 | ||||
-rw-r--r-- | src/cmd/paint/paint.c | 859 |
4 files changed, 1213 insertions, 0 deletions
diff --git a/man/man1/paint.1 b/man/man1/paint.1 new file mode 100644 index 00000000..2bcbf245 --- /dev/null +++ b/man/man1/paint.1 @@ -0,0 +1,85 @@ +.TH PAINT 1 +.CT 1 graphics +.SH NAME +paint \- create image files by drawing with a mouse or other pointing device +.SH SYNOPSIS +.B paint +[ +.I file +] +.SH DESCRIPTION +.I Paint +displays a canvas upon which can be drawn lines using the mouse holding +down buttons 1 or 2 for foreground or background color. The canvas +may be moved with button 3. Colors and brush sizes may be selected by +clicking on the palette at the bottom of the screen with buttons 1 or 2. +.PP +If the optional +.I file +argument is specified, then it is read and used as the canvas. +.I Paint +only recognizes Plan 9 bitmap format (see +.IR image (6)). +.PP +A number of immediate keyboard commands are recognized: +.TP +.B u +Undos the previous action. +.TP +.B c +Clears the canvas with the background color. +.TP +.B 1-9 +Select brush size. +.TP +.B f +Select flood fill brush. +.TP +.B + +Doubles magnification. +.TP +.B - +Halves magnification. +.TP +.B esc +Centers the canvas and resets magnification. +.PP +Hitting any other key on the keyboard shows a command prompt +where the following commands may be entered: +.TP +.BI r file +Reads the canvas from +.I file. +.TP +.BI w file +Writes the canvas to +.I file. +.TP +.BI < command +Executes +.I command +and reads the canvas from its standard output. +.TP +.BI > command +Executes +.I command +and writes the canvas to its standard input. +.TP +.BI | command +Transforms the canvas by piping it thru +.I command. +.TP +.B q +Quits the program. +.SH SOURCE +.B /sys/src/cmd/paint.c +.SH "SEE ALSO" +.IR resample (1), +.IR rotate (1), +.IR crop (1), +.IR jpg (1), +.IR page (1), +.IR image (6) +.SH HISTORY +.I Paint +first appeared in 9front (October, 2011). diff --git a/src/cmd/paint/eenter.c b/src/cmd/paint/eenter.c new file mode 100644 index 00000000..6645820c --- /dev/null +++ b/src/cmd/paint/eenter.c @@ -0,0 +1,258 @@ +/* +This code was taken from 9front repository (https://code.9front.org/hg/plan9front). +It is subject to license from 9front, below is a reproduction of the license. + +Copyright (c) 20XX 9front + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <keyboard.h> + +/* additional keyboard codes needed - defined here to avoid API change */ +enum { + Spec= 0xF800, + Knack= 0x15, + Ksoh= 0x01, + Kenq= 0x05, + Ketb= 0x17 +}; + +int +eenter(char *ask, char *buf, int len, Mouse *m) +{ + int done, down, tick, n, h, w, l, i; + Image *b, *save, *backcol, *bordcol; + Point p, o, t; + Rectangle r, sc; + Event ev; + Rune k; + + o = screen->r.min; + backcol = allocimagemix(display, DPurpleblue, DWhite); + bordcol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + if(backcol == nil || bordcol == nil) + return -1; + + while(ecankbd()) + ekbd(); + + if(m) o = m->xy; + + if(buf && len > 0) + n = strlen(buf); + else { + buf = nil; + len = 0; + n = 0; + } + + k = -1; + tick = n; + save = nil; + done = down = 0; + + p = stringsize(font, " "); + h = p.y; + w = p.x; + + b = screen; + sc = b->clipr; + replclipr(b, 0, b->r); + + while(!done){ + p = stringsize(font, buf ? buf : ""); + if(ask && ask[0]){ + if(buf) p.x += w; + p.x += stringwidth(font, ask); + } + r = rectaddpt(insetrect(Rpt(ZP, p), -4), o); + p.x = 0; + r = rectsubpt(r, p); + + p = ZP; + if(r.min.x < screen->r.min.x) + p.x = screen->r.min.x - r.min.x; + if(r.min.y < screen->r.min.y) + p.y = screen->r.min.y - r.min.y; + r = rectaddpt(r, p); + p = ZP; + if(r.max.x > screen->r.max.x) + p.x = r.max.x - screen->r.max.x; + if(r.max.y > screen->r.max.y) + p.y = r.max.y - screen->r.max.y; + r = rectsubpt(r, p); + + r = insetrect(r, -2); + if(save == nil){ + save = allocimage(display, r, b->chan, 0, DNofill); + if(save == nil){ + n = -1; + break; + } + draw(save, r, b, nil, r.min); + } + draw(b, r, backcol, nil, ZP); + border(b, r, 2, bordcol, ZP); + p = addpt(r.min, Pt(6, 6)); + if(ask && ask[0]){ + p = string(b, p, bordcol, ZP, font, ask); + if(buf) p.x += w; + } + if(buf){ + t = p; + p = stringn(b, p, display->black, ZP, font, buf, utfnlen(buf, tick)); + draw(b, Rect(p.x-1, p.y, p.x+2, p.y+3), display->black, nil, ZP); + draw(b, Rect(p.x, p.y, p.x+1, p.y+h), display->black, nil, ZP); + draw(b, Rect(p.x-1, p.y+h-3, p.x+2, p.y+h), display->black, nil, ZP); + p = string(b, p, display->black, ZP, font, buf+tick); + } + flushimage(display, 1); + +nodraw: + i = Ekeyboard; + if(m != nil) + i |= Emouse; + + replclipr(b, 0, sc); + i = eread(i, &ev); + + /* screen might have been resized */ + if(b != screen || !eqrect(screen->clipr, sc)){ + freeimage(save); + save = nil; + } + b = screen; + sc = b->clipr; + replclipr(b, 0, b->r); + + switch(i){ + default: + done = 1; + n = -1; + break; + case Ekeyboard: + k = ev.kbdc; + if(buf == nil || k == Keof || k == '\n'){ + done = 1; + break; + } + if(k == Knack || k == Kesc){ + done = !n; + buf[n = tick = 0] = 0; + break; + } + if(k == Ksoh || k == Khome){ + tick = 0; + continue; + } + if(k == Kenq || k == Kend){ + tick = n; + continue; + } + if(k == Kright){ + if(tick < n) + tick += chartorune(&k, buf+tick); + continue; + } + if(k == Kleft){ + for(i = 0; i < n; i += l){ + l = chartorune(&k, buf+tick); + if(i+l >= tick){ + tick = i; + break; + } + } + continue; + } + if(k == Ketb){ + while(tick > 0){ + tick--; + if(tick == 0 || + strchr(" !\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", buf[tick-1])) + break; + } + buf[n = tick] = 0; + break; + } + if(k == Kbs){ + if(tick <= 0) + continue; + for(i = 0; i < n; i += l){ + l = chartorune(&k, buf+i); + if(i+l >= tick){ + memmove(buf+i, buf+i+l, n - (i+l)); + buf[n -= l] = 0; + tick -= l; + break; + } + } + break; + } + if(k < 0x20 || k == Kdel || (k & 0xFF00) == KF || (k & 0xFF00) == Spec) + continue; + if((len-n) <= (l = runelen(k))) + continue; + memmove(buf+tick+l, buf+tick, n - tick); + runetochar(buf+tick, &k); + buf[n += l] = 0; + tick += l; + break; + case Emouse: + *m = ev.mouse; + if(!ptinrect(m->xy, r)){ + down = 0; + goto nodraw; + } + if(m->buttons & 7){ + down = 1; + if(buf && m->xy.x >= (t.x - w)){ + down = 0; + for(i = 0; i < n; i += l){ + l = chartorune(&k, buf+i); + t.x += stringnwidth(font, buf+i, 1); + if(t.x > m->xy.x) + break; + } + tick = i; + } + continue; + } + done = down; + break; + } + if(save){ + draw(b, save->r, save, nil, save->r.min); + freeimage(save); + save = nil; + } + } + + replclipr(b, 0, sc); + + freeimage(backcol); + freeimage(bordcol); + flushimage(display, 1); + + return n; +} + diff --git a/src/cmd/paint/mkfile b/src/cmd/paint/mkfile new file mode 100644 index 00000000..272db4b4 --- /dev/null +++ b/src/cmd/paint/mkfile @@ -0,0 +1,11 @@ +<$PLAN9/src/mkhdr + +TARG=paint + +OFILES=\ + eenter.$O\ + paint.$O\ + +HFILES=paint.h\ + +<$PLAN9/src/mkone diff --git a/src/cmd/paint/paint.c b/src/cmd/paint/paint.c new file mode 100644 index 00000000..7dce3710 --- /dev/null +++ b/src/cmd/paint/paint.c @@ -0,0 +1,859 @@ +/* +This code was taken from 9front repository (https://code.9front.org/hg/plan9front). +It is subject to license from 9front, below is a reproduction of the license. + +Copyright (c) 20XX 9front + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <keyboard.h> + +/* additional libdraw function needed - defined here to avoid API change */ +extern int eenter(char*, char*, int, Mouse*); + +char *filename; +int zoom = 1; +int brush = 1; +Point spos; /* position on screen */ +Point cpos; /* position on canvas */ +Image *canvas; +Image *ink; +Image *back; +Image *pal[16]; /* palette */ +Rectangle palr; /* palette rect on screen */ +Rectangle penr; /* pen size rect on screen */ + +enum { + NBRUSH = 10+1, +}; + +int nundo = 0; +Image *undo[1024]; + +int c64[] = { /* c64 color palette */ + 0x000000, + 0xFFFFFF, + 0x68372B, + 0x70A4B2, + 0x6F3D86, + 0x588D43, + 0x352879, + 0xB8C76F, + 0x6F4F25, + 0x433900, + 0x9A6759, + 0x444444, + 0x6C6C6C, + 0x9AD284, + 0x6C5EB5, + 0x959595, +}; + +/* + * get bounding rectnagle for stroke from r.min to r.max with + * specified brush (size). + */ +static 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); +} + +/* + * draw stroke from r.min to r.max to dst with color ink and + * brush (size). + */ +static 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. + */ +static 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); +} + +Point +s2c(Point p){ + p = subpt(p, spos); + if(p.x < 0) p.x -= zoom-1; + if(p.y < 0) p.y -= zoom-1; + return addpt(divpt(p, zoom), cpos); +} + +Point +c2s(Point p){ + return addpt(mulpt(subpt(p, cpos), zoom), spos); +} + +Rectangle +c2sr(Rectangle r){ + return Rpt(c2s(r.min), c2s(r.max)); +} + +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); +} + +void +expand(Rectangle r) +{ + Rectangle nr; + Image *tmp; + + if(canvas==nil){ + if((canvas = allocimage(display, r, screen->chan, 0, DNofill)) == nil) + sysfatal("allocimage: %r"); + draw(canvas, canvas->r, back, nil, ZP); + return; + } + nr = canvas->r; + combinerect(&nr, r); + if(eqrect(nr, canvas->r)) + return; + if((tmp = allocimage(display, nr, canvas->chan, 0, DNofill)) == nil) + return; + draw(tmp, canvas->r, canvas, nil, canvas->r.min); + gendrawdiff(tmp, tmp->r, canvas->r, back, ZP, nil, ZP, SoverD); + freeimage(canvas); + canvas = tmp; +} + +void +save(Rectangle r, int mark) +{ + Image *tmp; + int x; + + if(mark){ + x = nundo++ % nelem(undo); + if(undo[x]) + freeimage(undo[x]); + undo[x] = nil; + } + if(canvas==nil || nundo<0) + return; + if(!rectclip(&r, canvas->r)) + return; + if((tmp = allocimage(display, r, canvas->chan, 0, DNofill)) == nil) + return; + draw(tmp, r, canvas, nil, r.min); + x = nundo++ % nelem(undo); + if(undo[x]) + freeimage(undo[x]); + undo[x] = tmp; +} + +void +restore(int n) +{ + Image *tmp; + int x; + + while(nundo > 0){ + if(n-- == 0) + return; + x = --nundo % nelem(undo); + if((tmp = undo[x]) == nil) + return; + undo[x] = nil; + if(canvas == nil || canvas->chan != tmp->chan){ + freeimage(canvas); + canvas = tmp; + update(nil); + } else { + expand(tmp->r); + draw(canvas, tmp->r, tmp, nil, tmp->r.min); + update(&tmp->r); + freeimage(tmp); + } + } +} + +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); +} + +void +drawpal(void) +{ + Rectangle r, rr; + int i; + + r = screen->r; + r.min.y = r.max.y - 20; + replclipr(screen, 0, r); + + penr = r; + penr.min.x = r.max.x - NBRUSH*Dy(r); + + palr = r; + palr.max.x = penr.min.x; + + r = penr; + draw(screen, r, back, nil, ZP); + for(i=0; i<NBRUSH; i++){ + r.max.x = penr.min.x + (i+1)*Dx(penr) / NBRUSH; + rr = r; + if(i == brush) + rr.min.y += Dy(r)/3; + if(i == NBRUSH-1){ + /* last is special brush for fill draw */ + draw(screen, rr, ink, nil, ZP); + } else { + rr.min = addpt(rr.min, divpt(subpt(rr.max, rr.min), 2)); + rr.max = rr.min; + strokedraw(screen, rr, ink, i); + } + r.min.x = r.max.x; + } + + r = palr; + for(i=1; i<=nelem(pal); i++){ + r.max.x = palr.min.x + i*Dx(palr) / nelem(pal); + rr = r; + if(ink == pal[i-1]) + rr.min.y += Dy(r)/3; + draw(screen, rr, pal[i-1], nil, ZP); + gendrawdiff(screen, r, rr, back, ZP, nil, ZP, SoverD); + r.min.x = r.max.x; + } + + r = screen->r; + r.max.y -= Dy(palr); + replclipr(screen, 0, r); +} + +int +hitpal(Mouse m) +{ + if(ptinrect(m.xy, penr)){ + if(m.buttons & 7){ + brush = ((m.xy.x - penr.min.x) * NBRUSH) / Dx(penr); + drawpal(); + } + return 1; + } + if(ptinrect(m.xy, palr)){ + Image *col; + + col = pal[(m.xy.x - palr.min.x) * nelem(pal) / Dx(palr)]; + switch(m.buttons & 7){ + case 1: + ink = col; + drawpal(); + break; + case 2: + back = col; + drawpal(); + update(nil); + break; + } + return 1; + } + return 0; +} + +void +catch(void * _, char *msg) +{ + USED(_); + if(strstr(msg, "closed pipe")) + noted(NCONT); + noted(NDFLT); +} + +int +pipeline(char *fmt, ...) +{ + char buf[1024]; + va_list a; + int p[2]; + + va_start(a, fmt); + vsnprint(buf, sizeof(buf), fmt, a); + va_end(a); + if(pipe(p) < 0) + return -1; + switch(rfork(RFPROC|RFMEM|RFFDG|RFNOTEG)){ // RFEND not available in libc port + case -1: + close(p[0]); + close(p[1]); + return -1; + case 0: + close(p[1]); + dup(p[0], 0); + dup(p[0], 1); + close(p[0]); + execl("/bin/rc", "rc", "-c", buf, nil); + exits("exec"); + } + close(p[0]); + return p[1]; +} + +void +usage(void) +{ + fprint(2, "usage: %s [ file ]\n", argv0); + exits("usage"); +} + +void +main(int argc, char *argv[]) +{ + char *s, buf[1024]; + Rectangle r; + Image *img; + int i, fd; + Event e; + Mouse m; + Point p, d; + + ARGBEGIN { + default: + usage(); + } ARGEND; + + if(argc == 1) + filename = strdup(argv[0]); + else if(argc != 0) + usage(); + + if(initdraw(0, 0, "paint") < 0) + sysfatal("initdraw: %r"); + + if(filename){ + if((fd = open(filename, OREAD)) < 0) + sysfatal("open: %r"); + if((canvas = readimage(display, fd, 0)) == nil) + sysfatal("readimage: %r"); + close(fd); + } + + /* palette initialization */ + for(i=0; i<nelem(pal); i++){ + pal[i] = allocimage(display, Rect(0, 0, 1, 1), RGB24, 1, + c64[i % nelem(c64)]<<8 | 0xFF); + if(pal[i] == nil) + sysfatal("allocimage: %r"); + } + ink = pal[0]; + back = pal[1]; + drawpal(); + center(); + + einit(Emouse | Ekeyboard); + + notify(catch); + 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, 1); + floodfill(canvas, r, p, img); + update(&r); + + /* wait for mouse release */ + while(event(&e) == Emouse && (e.mouse.buttons & 7) != 0) + ; + break; + } + r = strokerect(Rpt(p, p), brush); + expand(r); + save(r, 1); + strokedraw(canvas, Rpt(p, p), img, brush); + update(&r); + 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); + save(r, 0); + strokedraw(canvas, Rpt(p, d), img, brush); + update(&r); + p = d; + } + 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 Kesc: + zoom = 1; + center(); + 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, 1); + freeimage(canvas); + canvas = nil; + update(nil); + break; + case 'u': + restore(16); + 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, 1); + 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 _) +{ + USED(_); + if(getwindow(display, Refnone) < 0) + sysfatal("resize failed"); + drawpal(); + update(nil); +} |