mirror of
https://github.com/9fans/plan9port.git
synced 2025-01-12 11:10:07 +00:00
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.
This commit is contained in:
parent
c63d31a8c1
commit
a309537fdc
4 changed files with 1213 additions and 0 deletions
85
man/man1/paint.1
Normal file
85
man/man1/paint.1
Normal file
|
@ -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).
|
258
src/cmd/paint/eenter.c
Normal file
258
src/cmd/paint/eenter.c
Normal file
|
@ -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;
|
||||
}
|
||||
|
11
src/cmd/paint/mkfile
Normal file
11
src/cmd/paint/mkfile
Normal file
|
@ -0,0 +1,11 @@
|
|||
<$PLAN9/src/mkhdr
|
||||
|
||||
TARG=paint
|
||||
|
||||
OFILES=\
|
||||
eenter.$O\
|
||||
paint.$O\
|
||||
|
||||
HFILES=paint.h\
|
||||
|
||||
<$PLAN9/src/mkone
|
859
src/cmd/paint/paint.c
Normal file
859
src/cmd/paint/paint.c
Normal file
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue