shithub: riscv

Download patch

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;
+		}
+	}
+}