ref: ffb97abccf0d8a01cf7db41c495103585d810699
dir: /picker.c/
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <draw.h>
#include <memdraw.h>
#include <mouse.h>
#include <keyboard.h>
#include <bio.h>
#include <plumb.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,
Cloaded,
Numchan,
Offset = 6,
Sliderheight = 48,
};
typedef struct Color Color;
typedef struct Space Space;
struct Color {
char *id;
Image *i;
double v[4];
double rgba[4];
Rectangle r;
u32int u;
int nchan;
Color *next, *prev;
};
struct Space {
char *name;
char opt;
char single;
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)
{
rgb[0] = v[0];
rgb[1] = v[1];
rgb[2] = v[2];
}
static void
_fromrgb(double *rgb, double *v)
{
v[0] = rgb[0];
v[1] = rgb[1];
v[2] = rgb[2];
}
static Space spaces[] = {
{
.name = "HSLuv",
.opt = 's',
.single = 0,
.torgb = _hsluv2rgb,
.fromrgb = _rgb2hsluv,
.max = {360.0, 100.0, 100.0, 1.0},
},
{
.name = "HPLuv",
.opt = 'l',
.single = 0,
.torgb = _hpluv2rgb,
.fromrgb = _rgb2hpluv,
.max = {360.0, 100.0, 100.0, 1.0},
},
{
.name = "RGB",
.opt = 'r',
.single = 1,
.torgb = _torgb,
.fromrgb = _fromrgb,
.max = {1.0, 1.0, 1.0, 1.0},
},
};
static char *menu2i[nelem(spaces)+4];
static Menu menu2 = { .item = menu2i };
static Color *colors, *color, *last;
static Channel *loaded;
static int ncolors;
static Rectangle srects[3];
static Space *space;
static Image *bg;
static char hex[12];
static int once;
static ulong
rgba2u(double *rgba)
{
return D2C(rgba[0])<<24 | D2C(rgba[1])<<16 | D2C(rgba[2])<<8 | D2C(rgba[3]);
}
#pragma varargck type "©" Color*
static int
colorfmt(Fmt *f)
{
char s[16];
Color *c;
c = va_arg(f->args, Color*);
if (c->nchan < 4)
sprint(s, "%06ux", c->u>>8);
else
sprint(s, "%08ux", c->u);
return fmtstrcpy(f, s);
}
static Image *
slider(int si, int w)
{
static Image *s, *sliders[4];
static u8int *b, *buf[4];
double c[4], rgba[4], 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, color->v, sizeof(c));
if (space->single) {
memset(c, 0, 3*sizeof(double));
c[si] = color->v[si];
}
dt = space->max[si] / w;
mi = c[si] / dt;
c[si] = 0.0;
for (i = n = 0; i < w; i++, n += 4) {
space->torgb(c, rgba);
rgba[3] = c[3];
u = rgba2u(rgba);
u = setalpha(u, u&0xff);
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(space->max[si], c[si] + dt);
}
loadimage(s, rect, b, 4*w);
return s;
}
static void
redraw(void)
{
Rectangle r, cr;
Color *c;
Image *im;
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 < color->nchan; i++) {
srects[i] = r;
im = slider(i, Dx(r));
draw(screen, r, bg, nil, ZP);
draw(screen, r, im, nil, ZP);
r.min.y += Sliderheight + Offset;
r.max.y += Sliderheight + Offset;
}
/* current color is changed on redraw, always */
freeimage(color->i);
color->i = nil;
/* palette */
colw = MIN(Sliderheight, (Dx(r)-(ncolors-1)*Offset)/ncolors);
cr = r;
cr.min.x += (Dx(cr) - colw*ncolors - (ncolors-1)*Offset) / 2;
for (c = colors; c != nil; c = c->next) {
if (c->i == nil) {
if ((c->i = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, setalpha(c->u|0xff, c->u&0xff))) == nil)
sysfatal("allocimage: %r");
}
cr.max.x = cr.min.x + colw;
draw(screen, cr, bg, nil, ZP);
draw(screen, cr, c->i, nil, ZP);
border(screen, cr, 1, display->black, ZP);
c->r = insetrect(cr, -3);
if (c == color)
border(screen, c->r, 3, 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, bg, nil, ZP);
draw(screen, r, color->i, nil, ZP);
/* current color id */
r.min.x += Dx(r)/2 - stringwidth(font, color->id)/2;
stringbg(screen, r.min, display->white, ZP, font, color->id, display->black, ZP);
/* current color in hex */
r.min.y += font->height;
sprint(hex, "%©", color);
r.min.x = screen->r.min.x + Dx(screen->r)/2 - stringwidth(font, hex)/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 [-e] [-", argv0);
for (i = 0; i < nelem(spaces); i++)
print("%c", spaces[i].opt);
print("]\n");
threadexitsall("usage");
}
static void
loadbg(void)
{
Rectangle r;
u8int *d;
int i, j;
r = Rect(0, 0, Sliderheight, Sliderheight);
d = calloc(1, Sliderheight*Sliderheight);
for (i = 0; i < Sliderheight/2; i++) {
for (j = 0; j < Sliderheight/2; j++)
d[j*Sliderheight+i] = 0xff;
}
for (; i < Sliderheight; i++) {
for (j = Sliderheight/2; j < Sliderheight; j++)
d[j*Sliderheight+i] = 0xff;
}
if ((bg = allocimage(display, r, GREY8, 1, DNofill)) == nil)
sysfatal("allocimage: %r");
if (loadimage(bg, r, d, Sliderheight*Sliderheight) < 0)
sysfatal("loadimage: %r");
free(d);
}
static int
printcolor(int f, Color *c)
{
char s[64];
int n;
n = snprint(s, sizeof(s), "%s\t%©\n", c->id, c);
return write(f, s, n) == n ? 0 : -1;
}
static int
hex2color(char *h, Color *c)
{
vlong v;
int i, n;
char *e;
n = strlen(h);
if (n != 6 && n != 8) {
werrstr("components: %d", n);
return -1;
}
c->nchan = n == 6 ? 3 : 4;
if ((v = strtoll(h, &e, 16)) == 0 &&
(e == h || *e || v < 0 || (n == 6 && v > 0xffffff) || (n == 8 && v > 0xffffffff))) {
werrstr("color: '%s'", h);
return -1;
}
if (c->nchan < 4) {
v <<= 8;
v |= 0xff;
}
c->u = v;
for (i = 0; i < 4; i++) {
c->rgba[i] = (double)((v>>24)&0xff) / 255.0;
v <<= 8;
}
c->v[3] = c->rgba[3];
space->fromrgb(c->rgba, c->v);
return 0;
}
static int
line2color(char *s, Color *c)
{
char *a[3];
int n;
if ((n = tokenize(s, a, nelem(a))) < 2) {
werrstr("columns: %d", n);
return -1;
}
if (hex2color(a[1], c) != 0)
return -1;
c->id = strdup(a[0]);
return 0;
}
static void
readcolors(void *x)
{
Color *c, *new;
Biobuf *b;
char *s;
char tmp[64];
int i, n;
b = x;
n = sprint(tmp, "readcolors ");
fd2path(Bfildes(b), tmp+n, sizeof(tmp)-n);
threadsetname(tmp);
new = nil;
for (i = 1; (s = Brdstr(b, '\n', 1)) != nil; i++) {
if (new == nil)
new = calloc(1, sizeof(Color));
n = line2color(s, new);
free(s);
if (n != 0) {
fprint(2, "%d: %r\n", i);
continue;
}
lockdisplay(display);
for (c = colors; c != nil; c = c->next) {
if (strcmp(c->id, new->id) == 0) {
free(c->id);
new->prev = c->prev;
new->next = c->next;
memmove(c, new, sizeof(*c));
break;
}
}
if (c == nil) {
c = new;
if (last != nil)
last->next = new;
new->prev = last;
last = new;
if (colors == nil)
colors = color = new;
new = nil;
ncolors++;
}
if (!once)
printcolor(1, c);
unlockdisplay(display);
sendul(loaded, 0);
}
Bterm(b);
threadexits(nil);
}
static void
dump(int f)
{
Color *c;
for (c = colors; c != nil; c = c->next)
printcolor(f, c);
}
static void
loadtheme(char *filename)
{
Biobuf *b;
if ((b = Bopen(filename, OREAD)) != nil)
proccreate(readcolors, b, 4096);
}
static void
plumbproc(void *)
{
int f;
Plumbmsg *m;
threadsetname("plumb");
if ((f = plumbopen("picker", OREAD)) >= 0) {
while ((m = plumbrecv(f)) != nil) {
loadtheme(m->data);
plumbfree(m);
}
}
threadexits(nil);
}
void
threadmain(int argc, char **argv)
{
Mousectl *mctl;
Keyboardctl *kctl;
Biobuf *b;
Color *c;
Rune r;
Mouse m;
Alt a[Numchan+1] = {
[Ckey] = { nil, &r, CHANRCV },
[Cmouse] = { nil, &m, CHANRCV },
[Cresize] = { nil, nil, CHANRCV },
[Cloaded] = { nil, nil, CHANRCV },
{ nil, nil, CHANEND },
};
int i, oldbuttons, slider;
ulong u;
char buf[16];
fmtinstall(L'©', colorfmt);
space = &spaces[0];
ARGBEGIN{
case 'e':
once = 1;
break;
default:
space = nil;
for (i = 0; i < nelem(spaces); i++) {
if (spaces[i].opt == ARGC()) {
space = &spaces[i];
break;
}
}
if (space == nil) {
fprint(2, "unknown color space '%c'\n", ARGC());
usage();
}
break;
}ARGEND
if (argc > 1)
usage();
b = argc == 1 ? Bopen(argv[0], OREAD) : Bfdopen(0, OREAD);
if (b == nil)
sysfatal("no colors: %r");
loaded = chancreate(sizeof(ulong), 0);
for (i = 0; i < nelem(spaces); i++)
menu2i[i] = spaces[i].name;
menu2i[i++] = "snarf";
menu2i[i++] = "snarf all";
menu2i[i] = "exit";
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;
a[Cloaded].c = loaded;
display->locking = 1;
unlockdisplay(display);
loadbg();
slider = -1;
proccreate(readcolors, b, 4096);
proccreate(plumbproc, nil, 4096);
for (;;) {
next:
c = color;
oldbuttons = m.buttons;
switch (alt(a)) {
case -1:
goto end;
case Ckey:
if (c == nil)
break;
switch (r) {
case Kleft:
if (c->prev != nil)
color = c->prev;
redraw();
break;
case Kright:
if (c->next != nil)
color = c->next;
redraw();
break;
case Kdel:
goto end;
}
break;
case Cmouse:
if (c == nil)
break;
if (m.buttons == 1) {
m.xy.x = MAX(screen->r.min.x, MIN(screen->r.max.x, m.xy.x));
for (i = 0; i < c->nchan; i++) {
Rectangle r = srects[i];
r.max.x += 1;
if (oldbuttons == 0 && ptinrect(m.xy, r))
slider = i;
if (slider != i)
continue;
c->v[i] = MIN(space->max[i], (double)(m.xy.x - r.min.x) * space->max[i]/(double)(Dx(r)-1));
c->rgba[3] = c->v[3];
changed:
space->torgb(c->v, c->rgba);
u = rgba2u(c->rgba);
if (c->u != u) {
c->u = u;
if (!once)
printcolor(1, c);
}
redraw();
goto next;
}
for (c = colors; c != nil; c = c->next) {
if (ptinrect(m.xy, c->r)) {
color = c;
space->fromrgb(c->rgba, c->v);
for (i = 0; i < 3; i++)
c->v[i] = MAX(0.0, MIN(space->max[i], c->v[i]));
goto changed;
}
}
} else if (m.buttons == 4 && (i = menuhit(3, mctl, &menu2, nil)) >= 0) {
if (i == nelem(spaces)+2)
goto end;
if (i < nelem(spaces)) {
space = &spaces[i];
space->fromrgb(c->rgba, c->v);
for (i = 0; i < 3; i++)
c->v[i] = MAX(0.0, MIN(space->max[i], c->v[i]));
goto changed;
}
int f;
if ((f = open("/dev/snarf", OWRITE)) >= 0) {
if (i == nelem(spaces))
write(f, hex, strlen(hex));
else
dump(f);
close(f);
}
} else if (m.buttons == 2) {
strcpy(buf, hex);
if (enter("rgb(a):", buf, sizeof(buf), mctl, kctl, nil) > 0) {
u = c->u;
if (hex2color(buf, c) == 0 && c->u != u) {
c->u = ~c->u; /* just for the update to kick in */
goto changed;
}
}
}
slider = -1;
break;
case Cresize:
getwindow(display, Refnone);
if (color != nil)
redraw();
break;
case Cloaded:
redraw();
break;
}
}
end:
if (once)
dump(1);
threadexitsall(nil);
}