ref: 99f6620b2cbfab36556a4c2d6c7a0f428fbe268d
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]; double rgba0[4]; /* that's for reverting back to the original */ 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)+6]; 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; v = strtoll(h, &e, 16); if(v == 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, "readcolors: line %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(new->rgba0, c->rgba0, sizeof(new->rgba0)); memmove(c, new, sizeof(*c)); break; } } if(c == nil){ c = new; memmove(c->rgba0, c->rgba, sizeof(c->rgba)); 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, menusnarf, menusnarfall, menurevert, menurevertall, menuexit; 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[menusnarf = i++] = "snarf"; menu2i[menusnarfall = i++] = "snarf all"; menu2i[menurevert = i++] = "revert"; menu2i[menurevertall = i++] = "revert all"; menu2i[menuexit = 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(r == Kdel || r == 'q') goto end; 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 '\n': goto enter; } 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 == menuexit) 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; }else if(i == menusnarf || i == menusnarfall){ int f; if((f = open("/dev/snarf", OWRITE)) >= 0){ if(i == menusnarf) write(f, hex, strlen(hex)); else dump(f); close(f); } }else if(i == menurevert){ space->fromrgb(c->rgba0, c->v); goto changed; }else if(i == menurevertall){ for(c = colors; c != nil; c = c->next){ memmove(c->rgba, c->rgba0, sizeof(c->rgba)); space->fromrgb(c->rgba, c->v); space->torgb(c->v, c->rgba); u = rgba2u(c->rgba); if(c->u != u){ c->u = u; if(!once) printcolor(1, c); freeimage(c->i); c->i = nil; } } redraw(); break; } }else if(m.buttons == 2){ enter: 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); }