git: 9front

ref: 4535ff763ae737a5795066b6027ff71a880b0a4e
dir: /sys/src/cmd/image/histogram.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <mouse.h>
#include <keyboard.h>
#include <geometry.h>
#include "fns.h"

enum {
	Hmargin	= 10,
	Vmargin = 15,
};

typedef struct Sampler Sampler;
typedef struct Histogram Histogram;
typedef struct Slider Slider;

struct Sampler
{
	Memimage	*i;
	uchar		*a;
	int		bpl;
	int		cmask;
};

struct Histogram
{
	char	*title;
	Image	*img;
	Screen	*scr;
	Image	*win;
	uvlong	vals[256];
	ulong	col;
	int	pid;
};

struct Slider
{
	Point2	p0;
	Point2	p1;
	int	left;
	int	min;
	int	max;
	int	val;
};

Histogram histos[4] = {
	{ .title = "red",   .col = 0xCC0000FF },
	{ .title = "green", .col = 0x00BB00FF },
	{ .title = "blue",  .col = 0x0000CCFF },
	{ .title = "alpha", .col = DBlack },
};

Memimage *mimage;
Image *image;
char title[64];
char winname[128];
Matrix warpmat;
Warp warp;
int smoothen;

int
sgn(double n)
{
	return n > 0? 1: (n < 0? -1: 0);
}

void
mktranslate(Matrix m, double x, double y)
{
	identity(m);
	m[0][2] = x;
	m[1][2] = y;
}

void
mkscale(Matrix m, double s)
{
	identity(m);
	m[0][0] = m[1][1] = s;
}

void
translate(Matrix m, double x, double y)
{
	Matrix t;

	memmove(t, m, sizeof(Matrix));
	mktranslate(m, x, y);
	mulm(m, t);
}

void
scale(Matrix m, double s)
{
	Matrix t;

	memmove(t, m, sizeof(Matrix));
	mkscale(m, s);
	mulm(m, t);
}

void
initsampler(Sampler *s, Memimage *i)
{
	s->i = i;
	s->a = i->data->bdata + i->zero;
	s->bpl = sizeof(ulong)*i->width;
	s->cmask = (1ULL << i->depth) - 1;
}

ulong
getpixel(Sampler *s, Point pt)
{
	uchar *p, r, g, b, a;
	ulong val, chan, ctype, ov, v;
	int nb, off, bpp, npack;

	val = 0;
	a = 0xFF;
	r = g = b = 0xAA;	/* garbage */
	p = s->a + pt.y*s->bpl + (pt.x*s->i->depth >> 3);

	/* pixelbits() */
	switch(bpp = s->i->depth){
	case 1:
	case 2:
	case 4:
		npack = 8/bpp;
		off = pt.x%npack;
		val = p[0] >> bpp*(npack-1-off);
		val &= s->cmask;
		break;
	case 8:
		val = p[0];
		break;
	case 16:
		val = p[0]|(p[1]<<8);
		break;
	case 24:
		val = p[0]|(p[1]<<8)|(p[2]<<16);
		break;
	case 32:
		val = p[0]|(p[1]<<8)|(p[2]<<16)|(p[3]<<24);
		break;
	}

	while(bpp < 32){
		val |= val<<bpp;
		bpp <<= 1;
	}

	/* imgtorgba() */
	for(chan = s->i->chan; chan; chan >>= 8){
		if((ctype = TYPE(chan)) == CIgnore){
			val >>= s->i->nbits[ctype];
			continue;
		}
		nb = s->i->nbits[ctype];
		ov = v = val & s->i->mask[ctype];
		val >>= nb;

		while(nb < 8){
			v |= v<<nb;
			nb <<= 1;
		}
		v >>= nb-8;

		switch(ctype){
		case CRed:
			r = v;
			break;
		case CGreen:
			g = v;
			break;
		case CBlue:
			b = v;
			break;
		case CAlpha:
			a = v;
			break;
		case CGrey:
			r = g = b = v;
			break;
		case CMap:
			p = s->i->cmap->cmap2rgb+3*ov;
			r = p[0];
			g = p[1];
			b = p[2];
			break;
		}
	}
	return (r<<24)|(g<<16)|(b<<8)|a;
}

ulong
clralpha(ulong c)
{
	int r, g, b, a;

	r = (c >> 3*8) & 0xFF;
	g = (c >> 2*8) & 0xFF;
	b = (c >> 1*8) & 0xFF;
	a = (c >> 0*8) & 0xFF;
	r = (r * 255)/a;
	g = (g * 255)/a;
	b = (b * 255)/a;
	return (r<<24)|(g<<16)|(b<<8)|a;
}

/*
 * to do this correctly you want a putpixel() that writes into b, but
 * we only use this with RGBA32, so it doesn't matter.
 */
int
fillcolor(Image *i, ulong c)
{
	uchar b[4];

	b[0] = (c >> 0*8) & 0xFF;
	b[1] = (c >> 1*8) & 0xFF;
	b[2] = (c >> 2*8) & 0xFF;
	b[3] = (c >> 3*8);
	return loadimage(i, i->r, b, (i->depth+7)/8);
}

void
drawgradient(Image *dst, Rectangle r, ulong min, ulong max)
{
	Image *a, *b;
	Rectangle dr;
	ulong dx, v;

	a = eallocimage(display, Rect(0,0,1,1), RGBA32, 1, DNofill);
	b = eallocimage(display, Rect(0,0,1,1), RGBA32, 1, DNofill);
	dr = r;
	dr.max.x = dr.min.x + 1;

	dx = Dx(r);
	for(; dr.min.x < r.max.x; dr.min.x = dr.max.x++){
		v = (dr.min.x-r.min.x)*0xFF/dx;
		fillcolor(a, setalpha(min, 0xFF - v));
		fillcolor(b, setalpha(max, v));
		draw(dst, dr, a, nil, ZP);
		draw(dst, dr, b, nil, ZP);
	}
	freeimage(a);
	freeimage(b);
}

void
measureimage(Memimage *i)
{
	Histogram *h;
	Image *brush;
	Rectangle hr, dr, gr, r;
	Sampler s;
	Point sp;
	ulong c;
	int chan, ht, j;
	uvlong vmax;

	initsampler(&s, i);
	for(sp.y = i->r.min.y; sp.y < i->r.max.y; sp.y++)
	for(sp.x = i->r.min.x; sp.x < i->r.max.x; sp.x++){
		c = getpixel(&s, sp);
		if(i->flags & Falpha)
			c = clralpha(c);
		for(chan = 4-1; chan >= 0; chan--)
			histos[4-1-chan].vals[(c >> chan*8)&0xFF]++;
	}

	brush = eallocimage(display, Rect(0,0,1,1), RGBA32, 1, DNofill);
	hr = Rect(0, 0, Hmargin+256+Hmargin, 4+font->height+Vmargin+150+3+8+1+Vmargin);
	dr = Rect(Hmargin, 4+font->height+Vmargin, hr.max.x-Hmargin, hr.max.y-Vmargin-1-8-3);
	gr = Rect(dr.min.x, dr.max.y+3, dr.max.x, dr.max.y+3+8);
	ht = Dy(dr);

	for(h = histos; h < histos+nelem(histos); h++){
		if(loadimage(brush, brush->r, (uchar*)&h->col, 4) < 0)
			sysfatal("loadimage2: %r");

		h->img = eallocimage(display, hr, screen->chan, 0, DWhite);
		string(h->img, addpt(hr.min, Pt(4, 4)), display->black, ZP, font, h->title);
		border(h->img, dr, -1, display->black, ZP);

		for(vmax = j = 0; j < 256; j++)
			if(h->vals[j] > vmax)
				vmax = h->vals[j];

		r = dr;
		r.max.x = r.min.x + 1;
		for(j = 0; j < 256; j++){
			r.min.y = r.max.y;
			r.min.y -= h->vals[j]*ht / vmax;
			draw(h->img, r, brush, nil, ZP);
			r.min.x = r.max.x++;
		}

		border(h->img, gr, -1, display->black, ZP);
		drawgradient(h->img, gr, DBlack, DWhite);
	}
	freeimage(brush);
}

int
winctl(Display *d, char *fmt, ...)
{
	char buf[128];
	va_list a;
	int fd, n;

	n = 0;
	snprint(buf, sizeof buf, "%s/wctl", d->windir);
	fd = open(buf, OWRITE|OCEXEC);
	if(fd >= 0){
		va_start(a, fmt);
		n = vfprint(fd, fmt, a);
		va_end(a);
		close(fd);
	}
	return n;
}

int
winmove(Display *d, Point p)
{
	return winctl(d, "move -minx %d -miny %d", p.x, p.y);
}

int
winresize(Display *d, Point sz)
{
	return winctl(d, "resize -dx %d -dy %d", sz.x+2*Borderwidth, sz.y+2*Borderwidth);
}

int
winsetlabel(Display *d, char *fmt, ...)
{
	char buf[128];
	va_list a;
	int fd, n;

	n = 0;
	snprint(buf, sizeof buf, "%s/label", d->windir);
	fd = open(buf, OWRITE|OCEXEC);
	if(fd >= 0){
		va_start(a, fmt);
		n = vfprint(fd, fmt, a);
		va_end(a);
		close(fd);
	}
	return n;
}

void
histredraw(Histogram *h)
{
	draw(h->win, h->win->r, h->img, nil, ZP);
	flushimage(display, 1);
}

void
histresize(Histogram *h)
{
	lockdisplay(display);
	if(gengetwindow(display, winname, &h->win, &h->scr, Refnone) < 0)
		sysfatal("gengetwindow2: %r");
	unlockdisplay(display);
	if((Dx(h->win->r) != Dx(h->img->r)
	||  Dy(h->win->r) != Dy(h->img->r)))
		winresize(display, subpt(h->img->r.max, h->img->r.min));
	histredraw(h);
}

void
histmouse(Histogram *h, Mousectl *mc)
{
	static Mouse omtab[nelem(histos)];
	Mouse *om;

	om = &omtab[h-histos];
	if((om->buttons & 4) && (mc->buttons & 4)){
		line(h->win, om->xy, mc->xy, Enddisc, Enddisc, 1, display->black, ZP);
		flushimage(display, 1);
	}
	*om = mc->Mouse;
}

void
histkey(Rune r)
{
	switch(r){
	case 'q':
	case Kdel:
		threadexitsall(nil);
	}
}

void
histproc(void *arg)
{
	Histogram *histo;
	Mousectl *mc;
	Keyboardctl *kc;
	Rune r;

	histo = arg;
	histo->pid = getpid();

	threadsetname("%s", histo->title);

	lockdisplay(display);	/* avoid races while attaching to new window */
	newwindow(nil);
	winsetlabel(display, "%s", histo->title);
	winresize(display, subpt(histo->img->r.max, histo->img->r.min));
	winmove(display, Pt(0, (Dy(histo->img->r)+2*Borderwidth)*(histo-histos)));

	if(gengetwindow(display, winname, &histo->win, &histo->scr, Refnone) < 0)
		sysfatal("gengetwindow: %r");
	unlockdisplay(display);
	if((mc = initmouse(nil, histo->win)) == nil)
		sysfatal("initmouse2: %r");
	if((kc = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard2: %r");

	histredraw(histo);

	enum { MOUSE, RESIZE, KEY };
	Alt a[] = {
		{mc->c, &mc->Mouse, CHANRCV},
		{mc->resizec, nil, CHANRCV},
		{kc->c, &r, CHANRCV},
		{nil, nil, CHANEND}
	};
	for(;;)
		switch(alt(a)){
		default: sysfatal("alt interrupted");
		case MOUSE:
			histmouse(histo, mc);
			break;
		case RESIZE:
			histresize(histo);
			break;
		case KEY:
			histkey(r);
			break;
		}
}

void
redraw(void)
{
	static Point titlep = {10, 10};

	draw(screen, screen->r, display->black, nil, ZP);
	affinewarp(screen, screen->r, image, image->r.min, warp, smoothen);
	stringbg(screen, addpt(screen->r.min, titlep), display->white, ZP, font, title, display->black, ZP);
	flushimage(display, 1);
}

void
resize(void)
{
	lockdisplay(display);
	if(getwindow(display, Refnone) < 0)
		fprint(2, "can't reattach to window\n");
	unlockdisplay(display);
	redraw();
}

static char *
genrmbmenu(int idx)
{
	if(idx > 0)
		return nil;
	return smoothen? "sharpen": "smoothen";
}

void
rmb(Mousectl *mc)
{
	static Menu menu = { .gen = genrmbmenu };

	switch(menuhit(3, mc, &menu, _screen)){
	case 0:
		smoothen ^= 1;
		break;
	}
	redraw();
}

void
mouse(Mousectl *mc)
{
	enum {
		ScrollzoomΔ	= 0.05,
		Scrollzoomin	= 1.00+ScrollzoomΔ,
		Scrollzoomout	= 1.00-ScrollzoomΔ,
	};
	static Mouse om;
	static Point p;
	int tainted;

	tainted = 0;
	if((om.buttons & 1) && (mc->buttons & 1)){
		translate(warpmat, mc->xy.x - om.xy.x, mc->xy.y - om.xy.y);
		tainted++;
	}else if(mc->buttons & 2){
		if((om.buttons & 2) == 0)
			p = subpt(mc->xy, screen->r.min);
		switch(sgn(mc->xy.y - om.xy.y)){
		case  1: goto zoomout;
		case -1: goto zoomin;
		}
	}else if((om.buttons & 4) == 0 && (mc->buttons & 4))
		rmb(mc);
	if(mc->buttons & 8){
		p = subpt(mc->xy, screen->r.min);
zoomin:
		translate(warpmat, -p.x, -p.y);
		scale(warpmat, Scrollzoomin);
		translate(warpmat, p.x, p.y);
		tainted++;
	}else if(mc->buttons & 16){
		p = subpt(mc->xy, screen->r.min);
zoomout:
		translate(warpmat, -p.x, -p.y);
		scale(warpmat, Scrollzoomout);
		translate(warpmat, p.x, p.y);
		tainted++;
	}
	if(tainted){
		mkwarp(warp, warpmat);
		redraw();
	}
	om = mc->Mouse;
}

void
key(Rune r)
{
	switch(r){
	case Kdel:
	case 'q':
		threadexitsall(nil);
	}
}

void
usage(void)
{
	fprint(2, "usage: %s [file]\n", argv0);
	exits("usage");
}

void
threadmain(int argc, char *argv[])
{
	Mousectl *mc;
	Keyboardctl *kc;
	Rune r;
	char cs[10];
	int fd, i;

	fd = 0;
	ARGBEGIN{
	default: usage();
	}ARGEND;
	if(argc == 1){
		fd = open(argv[0], OREAD);
		if(fd < 0)
			sysfatal("open: %r");
	}else if(argc > 1)
		usage();

	if(initdraw(nil, nil, "histogram") < 0)
		sysfatal("initdraw: %r");
	if((mc = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	if((kc = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");

	mimage = ereadmemimage(fd);
	image = memimage2image(display, mimage);
	snprint(title, sizeof title, "%s %dx%d %s",
		chantostr(cs, image->chan)? cs: "unknown", Dx(image->r), Dy(image->r),
		argc > 0? argv[0]: "main");
	identity(warpmat);
	mkwarp(warp, warpmat);
	redraw();

	measureimage(mimage);
	unlockdisplay(display);
	snprint(winname, sizeof winname, "%s/winname", display->windir);
	for(i = 0; i < nelem(histos); i++)
		proccreate(histproc, &histos[i], mainstacksize);

	enum { MOUSE, RESIZE, KEY };
	Alt a[] = {
		{mc->c, &mc->Mouse, CHANRCV},
		{mc->resizec, nil, CHANRCV},
		{kc->c, &r, CHANRCV},
		{nil, nil, CHANEND}
	};
	for(;;)
		switch(alt(a)){
		default: sysfatal("alt interrupted");
		case MOUSE:
			mouse(mc);
			break;
		case RESIZE:
			resize();
			break;
		case KEY:
			key(r);
			break;
		}
}