ref: 53822eb6d27c620c3d317a3857191de965d89e80
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, 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 int ncolors; static Rectangle srects[3]; static Space *space; static Image *bg; static char hex[12]; 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(Biobuf *b) { Color *c, *new; char *s; int i, n; 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; } 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) continue; if (last != nil) last->next = new; new->prev = last; last = new; if (colors == nil) colors = new; new = nil; ncolors++; } } 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) { lockdisplay(display); readcolors(b); unlockdisplay(display); Bterm(b); } } static void plumbproc(void *) { int f; Plumbmsg *m; threadsetname("plumb"); if ((f = plumbopen("picker", OREAD)) >= 0) { while ((m = plumbrecv(f)) != nil) { loadtheme(m->data); redraw(); dump(1); 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 }, { nil, nil, CHANEND }, }; int i, once, oldbuttons, slider; ulong u; char buf[16]; fmtinstall(L'©', colorfmt); space = &spaces[0]; once = 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"); readcolors(b); if (ncolors < 1) sysfatal("no colors"); Bterm(b); color = colors; 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; display->locking = 1; unlockdisplay(display); loadbg(); redraw(); slider = -1; proccreate(plumbproc, nil, mainstacksize); for (;;) { next: c = color; oldbuttons = m.buttons; switch (alt(a)) { case -1: goto end; case Ckey: 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 (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; redraw(); goto next; } } } 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); redraw(); break; } } end: if (once) dump(1); threadexitsall(nil); }