shithub: picker

ref: 2a853dc838b882d3eb0dc3f5bcb7cfad32baf416
dir: /picker.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 "hsluv.h"

#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))
#define D2C(x) (int)MAX(0, MIN(0xff, x*256.0))

enum
{
	Ckey,
	Cmouse,
	Cresize,
	Numchan,

	Offset = 6,
	Sliderheight = 32,
};

typedef struct Color Color;
typedef struct Mode Mode;

struct Color {
	Rectangle r;
	double v[4];
	double rgba[4];
	ulong u;
	Image *i;
};

struct Mode {
	char *name;
	char opt;
	void (*torgb)(double *v, double *rgb);
	void (*fromrgb)(double *rgb, double *v);
	double max[4];
};

static void
_hsluv2rgb(double *v, double *rgb)
{
	hsluv2rgb(v[0], v[1], v[2], rgb+0, rgb+1, rgb+2);
}

static void
_rgb2hsluv(double *rgb, double *v)
{
	rgb2hsluv(rgb[0], rgb[1], rgb[2], v+0, v+1, v+2);
}

static void
_hpluv2rgb(double *v, double *rgb)
{
	hpluv2rgb(v[0], v[1], v[2], rgb+0, rgb+1, rgb+2);
}

static void
_rgb2hpluv(double *rgb, double *v)
{
	rgb2hpluv(rgb[0], rgb[1], rgb[2], v+0, v+1, v+2);
}

static void
_torgb(double *v, double *rgb)
{
	v[0] = rgb[0];
	v[1] = rgb[1];
	v[2] = rgb[2];
}

static void
_fromrgb(double *rgb, double *v)
{
	rgb[0] = v[0];
	rgb[1] = v[1];
	rgb[2] = v[2];
}

static Mode modes[] = {
	{
		.name = "HSLuv",
		.opt = 's',
		.torgb = _hsluv2rgb,
		.fromrgb = _rgb2hsluv,
		.max = {360.0, 100.0, 100.0, 1.0},
	},
	{
		.name = "HPLuv",
		.opt = 'l',
		.torgb = _hpluv2rgb,
		.fromrgb = _rgb2hpluv,
		.max = {360.0, 100.0, 100.0, 1.0},
	},
	{
		.name = "RGB",
		.opt = 'r',
		.torgb = _torgb,
		.fromrgb = _fromrgb,
		.max = {1.0, 1.0, 1.0, 1.0},
	},
};

static Color *colors;
static int ncolors, curcolor, nchan;
static Rectangle srects[3];
static Mode *mode;

static Image *
slider(int si, int w)
{
	static Image *s, *sliders[4];
	static u8int *b, *buf[4];
	double c[4], rgb[3], dt;
	Rectangle rect;
	int i, n, mi;
	ulong u;

	rect = Rect(0, 0, w, 1);
	s = sliders[si];
	if (s == nil || Dx(s->r) != w) {
		buf[si] = realloc(buf[si], 4*w);
		if (s != nil)
			freeimage(s);
		if ((s = sliders[si] = allocimage(display, rect, RGBA32, 1, DNofill)) == nil)
			sysfatal("allocimage: %r");
	}
	b = buf[si];

	memmove(c, colors[curcolor].v, sizeof(c));
	dt = mode->max[si] / w;
	mi = c[si] / dt;
	c[si] = 0.0;
	for (i = n = 0; i < w; i++, n += 4) {
		mode->torgb(c, rgb);
		u = setalpha(D2C(rgb[0])<<24 | D2C(rgb[1])<<16 | D2C(rgb[2])<<8, D2C(c[3]));
		b[n] = 0xff;
		if (mi-2 == i)
			memset(b+n+1, 0, 3);
		else if (mi-1 == i)
			memset(b+n+1, 0xff, 3);
		else if (mi+1 == i)
			memset(b+n+1, 0xff, 3);
		else if (mi+2 == i)
			memset(b+n+1, 0, 3);
		else {
			b[n+0] = u & 0xff;
			b[n+1] = (u>>8) & 0xff;
			b[n+2] = (u>>16) & 0xff;
			b[n+3] = (u>>24) & 0xff;
		}

		c[si] = MIN(mode->max[si], c[si] + dt);
	}
	loadimage(s, rect, b, 4*w);

	return s;
}

static void
redraw(void)
{
	Rectangle r, cr;
	Image *im;
	char hex[8];
	int i, colw;

	lockdisplay(display);

	draw(screen, screen->r, display->white, nil, ZP);
	r = screen->r;

	r.min.x += Offset;
	r.min.y += Offset;
	r.max.x -= Offset;
	r.max.y = r.min.y + Sliderheight;

	/* sliders */
	for (i = 0; i < nchan; i++) {
		srects[i] = r;
		im = slider(i, Dx(r));
		draw(screen, r, im, nil, ZP);
		r.min.y += Sliderheight + Offset;
		r.max.y += Sliderheight + Offset;
	}

	/* palette */
	colw = MIN(Sliderheight, (Dx(r)-(ncolors-1)*Offset)/ncolors);
	cr = r;
	cr.min.x += (Dx(cr) - colw*ncolors - (ncolors-1)*Offset) / 2;
	for (i = 0; i < ncolors; i++) {
		if (i == curcolor) {
			freeimage(colors[i].i);
			colors[i].i = nil;
		}
		if (colors[i].i == nil) {
			colors[i].i = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(colors[i].u | 0xff, colors[i].u & 0xff));
			if (colors[i].i == nil)
				sysfatal("allocimage: %r");
		}

		cr.max.x = cr.min.x + colw;
		draw(screen, cr, colors[i].i, nil, ZP);
		colors[i].r = insetrect(cr, -3);
		if (i == curcolor)
			border(screen, colors[i].r, 2, display->black, ZP);
		cr.min.x += colw + Offset;
	}
	r.min.y += Sliderheight + Offset;

	/* current color */
	r.max.y = screen->r.max.y - Offset;
	draw(screen, r, colors[curcolor].i, nil, ZP);

	/* current color in hex */
	if (nchan > 3)
		sprint(hex, "#%08lux", colors[curcolor].u);
	else
		sprint(hex, "#%06lux", colors[curcolor].u>>8);
	r.min.x += Dx(r)/2 - 7*stringwidth(font, "#")/2;
	r.min.y += Dy(r)/2 - font->height/2;
	stringbg(screen, r.min, display->white, ZP, font, hex, display->black, ZP);

	flushimage(display, 1);
	unlockdisplay(display);
}

static void
usage(void)
{
	int i;

	print("usage: %s [-a] [-", argv0);
	for (i = 0; i < nelem(modes); i++)
		print("%c", modes[i].opt);
	print("] rrggbbaa ...\n");

	threadexitsall("usage");
}

void
threadmain(int argc, char **argv)
{
	Mousectl *mctl;
	Keyboardctl *kctl;
	Rune r;
	Mouse m;
	Alt a[Numchan+1] = {
		[Ckey] = { nil, &r, CHANRCV },
		[Cmouse] = { nil, &m, CHANRCV },
		[Cresize] = { nil, nil, CHANRCV },
		{ nil, nil, CHANEND },
	};
	Color *c;
	char *s, buf[16];
	vlong v;
	int i, j;

	mode = &modes[0];
	nchan = 3;
	ARGBEGIN{
	case 'a':
		nchan = 4;
		break;
	default:
		mode = nil;
		for (i = 0; i < nelem(modes); i++) {
			if (modes[i].opt == ARGC()) {
				mode = &modes[i];
				break;
			}
		}
		if (mode == nil) {
			fprint(2, "unknown mode '%c'\n", ARGC());
			usage();
		}
		break;
	}ARGEND

	ncolors = argc;
	if (ncolors < 1) {
		fprint(2, "no colors specified\n");
		usage();
	}
	colors = calloc(ncolors, sizeof(Color));
	for (i = 0; i < ncolors; i++) {
		if (strlen(argv[i]) != nchan*2) {
			fprint(2, "wrong number of components: '%s'\n", argv[i]);
			usage();
		}
		if ((v = strtoll(argv[i], &s, 16)) == 0 && (s == argv[i] || *s || v < 0)) {
			fprint(2, "invalid color: '%s'\n", argv[i]);
			usage();
		}
		if (nchan < 4) {
			v <<= 8;
			v |= 0xff;
		}
		colors[i].u = v;
		for (j = 0; j < 4; j++) {
			colors[i].rgba[j] = (double)((v>>24)&0xff) / 255.0;
			v <<= 8;
		}
		colors[i].v[3] = colors[i].rgba[3];
		mode->fromrgb(colors[i].rgba, colors[i].v);
	}

	if (initdraw(nil, nil, "picker") < 0)
		sysfatal("initdraw: %r");
	if ((kctl = initkeyboard(nil)) == nil)
		sysfatal("initkeyboard: %r");
	a[Ckey].c = kctl->c;
	if ((mctl = initmouse(nil, screen)) == nil)
		sysfatal("initmouse: %r");
	a[Cmouse].c = mctl->c;
	a[Cresize].c = mctl->resizec;
	display->locking = 1;
	unlockdisplay(display);

	redraw();

	for (;;) {
next:
		c = &colors[curcolor];

		switch (alt(a)) {
		case -1:
			goto end;

		case Ckey:
			switch (r) {
			case Kleft:
				curcolor = MAX(0, curcolor-1);
				redraw();
				break;
			case Kright:
				curcolor = MIN(ncolors-1, curcolor+1);
				redraw();
				break;
			case Kdel:
				goto end;
			}
			break;

		case Cmouse:
			if (m.buttons == 1) {
				m.xy.x = MAX(screen->r.min.x, MIN(screen->r.max.x, m.xy.x));
				for (i = 0; i < nchan; i++) {
					Rectangle r = srects[i];
					r.max.x += 1;
					if (ptinrect(m.xy, r)) {
						ulong u;

						c->v[i] = MIN(mode->max[i], (double)(m.xy.x - r.min.x) * mode->max[i]/(double)(Dx(r)-1));
						mode->torgb(c->v, c->rgba);
						u = D2C(c->rgba[0])<<24 | D2C(c->rgba[1])<<16 | D2C(c->rgba[2])<<8 | D2C(c->v[3]);
						if (c->u != u) {
							c->u = u;
							if (nchan < 4)
								j = sprint(buf, "%d %06lux\n", curcolor, u>>8);
							else
								j = sprint(buf, "%d %08lux\n", curcolor, u);
							if (write(1, buf, j) != j)
								goto end;
							redraw();
						}
						goto next;
					}
				}
				for (i = 0; i < ncolors; i++) {
					if (ptinrect(m.xy, colors[i].r)) {
						curcolor = i;
						redraw();
						goto next;
					}
				}
			}
			break;

		case Cresize:
			getwindow(display, Refnone);
			redraw();
			break;
		}
	}

end:
	threadexitsall(nil);
}