ref: d11ca3b545a69c957f8ab7f019d7f373afe19e57
parent: bd1305a0b9841d65f95b4d81fa0d29067425833a
author: phil9 <telephil9@gmail.com>
date: Mon May 6 12:24:25 EDT 2024
vcrop - graphical image cropping tool vcrop is a graphical version of crop(1).
--- /dev/null
+++ b/sys/man/1/vcrop
@@ -1,0 +1,28 @@
+.TH VCROP 1
+.SH NAME
+vcrop \- crop image visually
+.SH SYNOPSIS
+.B vcrop
+[
+.I file
+]
+.SH DESCRIPTION
+.I Vcrop
+displays an image read either from
+.B file
+or from
+.B standard input
+and allows to crop it interactively.
+.PP
+Button 1 pans the image within the window
+.PP
+Button 2 triggers the cropping area selection. Area can then be selected by sweeping Button 1.
+.PP
+Button 3 brings up a menu of actions.
+.SH SOURCE
+/sys/src/cmd/vcrop.c
+.SH "SEE ALSO"
+.IR crop (1)
+.SH HISTORY
+vcrop first appeared in 9front (May, 2024).
+
--- /dev/null
+++ b/sys/src/cmd/vcrop.c
@@ -1,0 +1,212 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+
+enum
+{
+ Threshold = 5,
+};
+
+enum
+{
+ Emouse,
+ Eresize,
+ Ekeyboard,
+};
+
+Mousectl *mctl;
+Keyboardctl *kctl;
+Image *bg;
+Image *p;
+Image *n;
+Point pos, oldpos;
+enum { Mcrop, Mundo, Msave, Mexit };
+char *menustr[] = { "crop", "undo", "save", "exit", 0 };
+Menu menu = { menustr };
+
+void
+redraw(void)
+{
+ draw(screen, screen->r, bg, nil, ZP);
+ draw(screen, rectaddpt(n->r, addpt(pos, screen->r.min)), n, nil, n->r.min);
+ flushimage(display, 1);
+}
+
+void
+translate(Point d)
+{
+ Rectangle r, nr;
+
+ if(d.x==0 && d.y==0)
+ return;
+ r = rectaddpt(n->r, addpt(pos, screen->r.min));
+ pos = addpt(pos, d);
+ nr = rectaddpt(r, d);
+ draw(screen, screen->r, bg, nil, ZP);
+ draw(screen, nr, n, nil, n->r.min);
+ flushimage(display, 1);
+}
+
+void
+crop(void)
+{
+ Rectangle r;
+ Point o;
+ Image *i;
+
+ r = getrect(1, mctl);
+ if(eqrect(r, ZR) || badrect(r) || (Dx(r)<Threshold && Dy(r)<Threshold))
+ return;
+ o = subpt(r.min, screen->r.min);
+ o = addpt(n->r.min, o);
+ o = subpt(o, addpt(n->r.min, pos));
+ /* clamp rect to image bounds */
+ if(o.x < n->r.min.x) o.x = n->r.min.x;
+ if(o.y < n->r.min.y) o.y = n->r.min.y;
+ if((o.x + Dx(r)) > n->r.max.x) r.max.x = r.min.x + n->r.max.x - o.x;
+ if((o.y + Dy(r)) > n->r.max.y) r.max.y = r.min.y + n->r.max.y - o.y;
+ i = allocimage(display, r, n->chan, 0, DTransparent);
+ if(i==nil)
+ sysfatal("allocimage: %r");
+ draw(i, i->r, n, nil, o);
+ if(p)
+ freeimage(p);
+ p = n;
+ n = i;
+ oldpos = pos;
+ pos = subpt(ZP, n->r.min);
+ redraw();
+}
+
+void
+save(void)
+{
+ char buf[4096] = {0};
+ int i, fd;
+
+ i = enter("Save as:", buf, sizeof buf, mctl, kctl, nil);
+ if(i<=0)
+ return;
+ fd = create(buf, OWRITE, 0644);
+ if(fd<0)
+ sysfatal("create: %r");
+ i = writeimage(fd, n, 0);
+ if(i<0)
+ sysfatal("writeimage: %r");
+ close(fd);
+}
+
+void
+undo(void)
+{
+ if(p==nil)
+ return;
+ freeimage(n);
+ n = p;
+ p = nil;
+ pos = oldpos;
+ redraw();
+}
+
+void
+menu3hit(void)
+{
+ int i;
+
+ i = menuhit(3, mctl, &menu, nil);
+ switch(i){
+ case Mcrop:
+ crop();
+ break;
+ case Mundo:
+ undo();
+ break;
+ case Msave:
+ save();
+ break;
+ case Mexit:
+ threadexitsall(nil);
+ }
+}
+
+void
+usage(char *n)
+{
+ fprint(2, "usage: %s <[image]>\n", n);
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ Mouse m;
+ Rune k;
+ Point o;
+ int fd;
+ Alt a[] = {
+ { nil, &m, CHANRCV },
+ { nil, nil, CHANRCV },
+ { nil, &k, CHANRCV },
+ { nil, nil, CHANEND },
+ };
+
+ if(argc > 2)
+ usage(argv[0]);
+ fd = 0;
+ if(argc==2){
+ fd = open(argv[1], OREAD);
+ if(fd<0)
+ sysfatal("open: %r");
+ }
+ if(initdraw(nil, nil, "vcrop")<0)
+ sysfatal("initdraw: %r");
+ display->locking = 0;
+ if((mctl = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ if((kctl = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+ a[Emouse].c = mctl->c;
+ a[Eresize].c = mctl->resizec;
+ a[Ekeyboard].c = kctl->c;
+ bg = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
+ n = readimage(display, fd, 0);
+ if(n==nil)
+ sysfatal("readimage: %r");
+ close(fd);
+ p = nil;
+ pos = subpt(ZP, n->r.min);
+ oldpos = pos;
+ redraw();
+ for(;;){
+ switch(alt(a)){
+ case Emouse:
+ if(m.buttons==1){
+ for(;;) {
+ o = m.xy;
+ if(!readmouse(mctl))
+ break;
+ if((mctl->buttons & 1) == 0)
+ break;
+ translate(subpt(mctl->xy, o));
+ }
+ }else if(m.buttons==2){
+ mctl->buttons = 1;
+ crop();
+ }else if(m.buttons==4)
+ menu3hit();
+ break;
+ case Eresize:
+ if(getwindow(display, Refnone)<0)
+ sysfatal("cannot reattach: %r");
+ redraw();
+ break;
+ case Ekeyboard:
+ if(k==Kdel)
+ threadexitsall(nil);
+ break;
+ }
+ }
+}