shithub: picker

Download patch

ref: 33a7fcd3976241e3e22a69f24bff13e5999e2aa1
author: Sigrid Haflínudóttir <ftrvxmtrx@gmail.com>
date: Wed Mar 11 21:24:43 EDT 2020

initial wip version

--- /dev/null
+++ b/README.md
@@ -1,0 +1,4 @@
+# picker
+
+HSLuv color picker for Plan 9.
+It's WIP so the code look horrible and doesn't make any sense right now.
--- /dev/null
+++ b/hsluv.c
@@ -1,0 +1,454 @@
+/*
+ * HSLuv-C: Human-friendly HSL
+ * <http://github.com/hsluv/hsluv-c>
+ * <http://www.hsluv.org/>
+ *
+ * Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
+ * Copyright (c) 2015 Roger Tallada (Obj-C implementation)
+ * Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <u.h>
+#include <libc.h>
+#include "hsluv.h"
+
+#define DBL_MAX 1.797693134862315708145e+308
+#define cbrt(x) pow((x), 1.0/3.0)
+
+typedef struct Triplet_tag Triplet;
+struct Triplet_tag {
+    double a;
+    double b;
+    double c;
+};
+
+/* for RGB */
+static const Triplet m[3] = {
+    {  3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 },
+    { -0.96924363628087982613,  1.87596750150772066772,  0.04155505740717561247 },
+    {  0.05563007969699360846, -0.20397695888897656435,  1.05697151424287856072 }
+};
+
+/* for XYZ */
+static const Triplet m_inv[3] = {
+    {  0.41239079926595948129,  0.35758433938387796373,  0.18048078840183428751 },
+    {  0.21263900587151035754,  0.71516867876775592746,  0.07219231536073371500 },
+    {  0.01933081871559185069,  0.11919477979462598791,  0.95053215224966058086 }
+};
+
+static const double ref_u = 0.19783000664283680764;
+static const double ref_v = 0.46831999493879100370;
+
+static const double kappa = 903.29629629629629629630;
+static const double epsilon = 0.00885645167903563082;
+
+
+typedef struct Bounds_tag Bounds;
+struct Bounds_tag {
+    double a;
+    double b;
+};
+
+
+static void
+get_bounds(double l, Bounds bounds[6])
+{
+    double tl = l + 16.0;
+    double sub1 = (tl * tl * tl) / 1560896.0;
+    double sub2 = (sub1 > epsilon ? sub1 : (l / kappa));
+    int channel;
+    int t;
+
+    for(channel = 0; channel < 3; channel++) {
+        double m1 = m[channel].a;
+        double m2 = m[channel].b;
+        double m3 = m[channel].c;
+
+        for (t = 0; t < 2; t++) {
+            double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
+            double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 -  769860.0 * t * l;
+            double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t;
+
+            bounds[channel * 2 + t].a = top1 / bottom;
+            bounds[channel * 2 + t].b = top2 / bottom;
+        }
+    }
+}
+
+static double
+intersect_line_line(const Bounds* line1, const Bounds* line2)
+{
+    return (line1->b - line2->b) / (line2->a - line1->a);
+}
+
+static double
+dist_from_pole_squared(double x, double y)
+{
+    return x * x + y * y;
+}
+
+static double
+ray_length_until_intersect(double theta, const Bounds* line)
+{
+    return line->b / (sin(theta) - line->a * cos(theta));
+}
+
+static double
+max_safe_chroma_for_l(double l)
+{
+    double min_len_squared = DBL_MAX;
+    Bounds bounds[6];
+    int i;
+
+    get_bounds(l, bounds);
+    for(i = 0; i < 6; i++) {
+        double m1 = bounds[i].a;
+        double b1 = bounds[i].b;
+        /* x where line intersects with perpendicular running though (0, 0) */
+        Bounds line2 = { -1.0 / m1, 0.0 };
+        double x = intersect_line_line(&bounds[i], &line2);
+        double distance = dist_from_pole_squared(x, b1 + x * m1);
+
+        if(distance < min_len_squared)
+            min_len_squared = distance;
+    }
+
+    return sqrt(min_len_squared);
+}
+
+static double
+max_chroma_for_lh(double l, double h)
+{
+    double min_len = DBL_MAX;
+    double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */
+    Bounds bounds[6];
+    int i;
+
+    get_bounds(l, bounds);
+    for(i = 0; i < 6; i++) {
+        double len = ray_length_until_intersect(hrad, &bounds[i]);
+
+        if(len >= 0  &&  len < min_len)
+            min_len = len;
+    }
+    return min_len;
+}
+
+static double
+dot_product(const Triplet* t1, const Triplet* t2)
+{
+    return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c);
+}
+
+/* Used for rgb conversions */
+static double
+from_linear(double c)
+{
+    if(c <= 0.0031308)
+        return 12.92 * c;
+    else
+        return 1.055 * pow(c, 1.0 / 2.4) - 0.055;
+}
+
+static double
+to_linear(double c)
+{
+    if (c > 0.04045)
+        return pow((c + 0.055) / 1.055, 2.4);
+    else
+        return c / 12.92;
+}
+
+static void
+xyz2rgb(Triplet* in_out)
+{
+    double r = from_linear(dot_product(&m[0], in_out));
+    double g = from_linear(dot_product(&m[1], in_out));
+    double b = from_linear(dot_product(&m[2], in_out));
+    in_out->a = r;
+    in_out->b = g;
+    in_out->c = b;
+}
+
+static void
+rgb2xyz(Triplet* in_out)
+{
+    Triplet rgbl = { to_linear(in_out->a), to_linear(in_out->b), to_linear(in_out->c) };
+    double x = dot_product(&m_inv[0], &rgbl);
+    double y = dot_product(&m_inv[1], &rgbl);
+    double z = dot_product(&m_inv[2], &rgbl);
+    in_out->a = x;
+    in_out->b = y;
+    in_out->c = z;
+}
+
+/* http://en.wikipedia.org/wiki/CIELUV
+ * In these formulas, Yn refers to the reference white point. We are using
+ * illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
+ * simplified accordingly.
+ */
+static double
+y2l(double y)
+{
+    if(y <= epsilon)
+        return y * kappa;
+    else
+        return 116.0 * cbrt(y) - 16.0;
+}
+
+static double
+l2y(double l)
+{
+    if(l <= 8.0) {
+        return l / kappa;
+    } else {
+        double x = (l + 16.0) / 116.0;
+        return (x * x * x);
+    }
+}
+
+static void
+xyz2luv(Triplet* in_out)
+{
+    double var_u = (4.0 * in_out->a) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c));
+    double var_v = (9.0 * in_out->b) / (in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c));
+    double l = y2l(in_out->b);
+    double u = 13.0 * l * (var_u - ref_u);
+    double v = 13.0 * l * (var_v - ref_v);
+
+    in_out->a = l;
+    if(l < 0.00000001) {
+        in_out->b = 0.0;
+        in_out->c = 0.0;
+    } else {
+        in_out->b = u;
+        in_out->c = v;
+    }
+}
+
+static void
+luv2xyz(Triplet* in_out)
+{
+    if(in_out->a <= 0.00000001) {
+        /* Black will create a divide-by-zero error. */
+        in_out->a = 0.0;
+        in_out->b = 0.0;
+        in_out->c = 0.0;
+        return;
+    }
+
+    double var_u = in_out->b / (13.0 * in_out->a) + ref_u;
+    double var_v = in_out->c / (13.0 * in_out->a) + ref_v;
+    double y = l2y(in_out->a);
+    double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
+    double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
+    in_out->a = x;
+    in_out->b = y;
+    in_out->c = z;
+}
+
+static void
+luv2lch(Triplet* in_out)
+{
+    double l = in_out->a;
+    double u = in_out->b;
+    double v = in_out->c;
+    double h;
+    double c = sqrt(u * u + v * v);
+
+    /* Grays: disambiguate hue */
+    if(c < 0.00000001) {
+        h = 0;
+    } else {
+        h = atan2(v, u) * 57.29577951308232087680;  /* (180 / pi) */
+        if(h < 0.0)
+            h += 360.0;
+    }
+
+    in_out->a = l;
+    in_out->b = c;
+    in_out->c = h;
+}
+
+static void
+lch2luv(Triplet* in_out)
+{
+    double hrad = in_out->c * 0.01745329251994329577;  /* (pi / 180.0) */
+    double u = cos(hrad) * in_out->b;
+    double v = sin(hrad) * in_out->b;
+
+    in_out->b = u;
+    in_out->c = v;
+}
+
+static void
+hsluv2lch(Triplet* in_out)
+{
+    double h = in_out->a;
+    double s = in_out->b;
+    double l = in_out->c;
+    double c;
+
+    /* White and black: disambiguate chroma */
+    if(l > 99.9999999 || l < 0.00000001)
+        c = 0.0;
+    else
+        c = max_chroma_for_lh(l, h) / 100.0 * s;
+
+    /* Grays: disambiguate hue */
+    if (s < 0.00000001)
+        h = 0.0;
+
+    in_out->a = l;
+    in_out->b = c;
+    in_out->c = h;
+}
+
+static void
+lch2hsluv(Triplet* in_out)
+{
+    double l = in_out->a;
+    double c = in_out->b;
+    double h = in_out->c;
+    double s;
+
+    /* White and black: disambiguate saturation */
+    if(l > 99.9999999 || l < 0.00000001)
+        s = 0.0;
+    else
+        s = c / max_chroma_for_lh(l, h) * 100.0;
+
+    /* Grays: disambiguate hue */
+    if (c < 0.00000001)
+        h = 0.0;
+
+    in_out->a = h;
+    in_out->b = s;
+    in_out->c = l;
+}
+
+static void
+hpluv2lch(Triplet* in_out)
+{
+    double h = in_out->a;
+    double s = in_out->b;
+    double l = in_out->c;
+    double c;
+
+    /* White and black: disambiguate chroma */
+    if(l > 99.9999999 || l < 0.00000001)
+        c = 0.0;
+    else
+        c = max_safe_chroma_for_l(l) / 100.0 * s;
+
+    /* Grays: disambiguate hue */
+    if (s < 0.00000001)
+        h = 0.0;
+
+    in_out->a = l;
+    in_out->b = c;
+    in_out->c = h;
+}
+
+static void
+lch2hpluv(Triplet* in_out)
+{
+    double l = in_out->a;
+    double c = in_out->b;
+    double h = in_out->c;
+    double s;
+
+    /* White and black: disambiguate saturation */
+    if (l > 99.9999999 || l < 0.00000001)
+        s = 0.0;
+    else
+        s = c / max_safe_chroma_for_l(l) * 100.0;
+
+    /* Grays: disambiguate hue */
+    if (c < 0.00000001)
+        h = 0.0;
+
+    in_out->a = h;
+    in_out->b = s;
+    in_out->c = l;
+}
+
+
+
+void
+hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
+{
+    Triplet tmp = { h, s, l };
+
+    hsluv2lch(&tmp);
+    lch2luv(&tmp);
+    luv2xyz(&tmp);
+    xyz2rgb(&tmp);
+
+    *pr = tmp.a;
+    *pg = tmp.b;
+    *pb = tmp.c;
+}
+
+void
+hpluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
+{
+    Triplet tmp = { h, s, l };
+
+    hpluv2lch(&tmp);
+    lch2luv(&tmp);
+    luv2xyz(&tmp);
+    xyz2rgb(&tmp);
+
+    *pr = tmp.a;
+    *pg = tmp.b;
+    *pb = tmp.c;
+}
+
+void
+rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl)
+{
+    Triplet tmp = { r, g, b };
+
+    rgb2xyz(&tmp);
+    xyz2luv(&tmp);
+    luv2lch(&tmp);
+    lch2hsluv(&tmp);
+
+    *ph = tmp.a;
+    *ps = tmp.b;
+    *pl = tmp.c;
+}
+
+void
+rgb2hpluv(double r, double g, double b, double* ph, double* ps, double* pl)
+{
+    Triplet tmp = { r, g, b };
+
+    rgb2xyz(&tmp);
+    xyz2luv(&tmp);
+    luv2lch(&tmp);
+    lch2hpluv(&tmp);
+
+    *ph = tmp.a;
+    *ps = tmp.b;
+    *pl = tmp.c;
+}
--- /dev/null
+++ b/hsluv.h
@@ -1,0 +1,90 @@
+/*
+ * HSLuv-C: Human-friendly HSL
+ * <http://github.com/hsluv/hsluv-c>
+ * <http://www.hsluv.org/>
+ *
+ * Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
+ * Copyright (c) 2015 Roger Tallada (Obj-C implementation)
+ * Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef HSLUV_H
+#define HSLUV_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * Convert HSLuv to RGB.
+ *
+ * @param h Hue. Between 0.0 and 360.0.
+ * @param s Saturation. Between 0.0 and 100.0.
+ * @param l Lightness. Between 0.0 and 100.0.
+ * @param[out] pr Red component. Between 0.0 and 1.0.
+ * @param[out] pg Green component. Between 0.0 and 1.0.
+ * @param[out] pb Blue component. Between 0.0 and 1.0.
+ */
+void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
+
+/**
+ * Convert RGB to HSLuv.
+ *
+ * @param r Red component. Between 0.0 and 1.0.
+ * @param g Green component. Between 0.0 and 1.0.
+ * @param b Blue component. Between 0.0 and 1.0.
+ * @param[out] ph Hue. Between 0.0 and 360.0.
+ * @param[out] ps Saturation. Between 0.0 and 100.0.
+ * @param[out] pl Lightness. Between 0.0 and 100.0.
+ */
+void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl);
+
+/**
+ * Convert HPLuv to RGB.
+ *
+ * @param h Hue. Between 0.0 and 360.0.
+ * @param s Saturation. Between 0.0 and 100.0.
+ * @param l Lightness. Between 0.0 and 100.0.
+ * @param[out] pr Red component. Between 0.0 and 1.0.
+ * @param[out] pg Green component. Between 0.0 and 1.0.
+ * @param[out] pb Blue component. Between 0.0 and 1.0.
+ */
+void hpluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
+
+/**
+ * Convert RGB to HPLuv.
+ *
+ * @param r Red component. Between 0.0 and 1.0.
+ * @param g Green component. Between 0.0 and 1.0.
+ * @param b Blue component. Between 0.0 and 1.0.
+ * @param[out] ph Hue. Between 0.0 and 360.0.
+ * @param[out] ps Saturation. Between 0.0 and 100.0.
+ * @param[out] pl Lightness. Between 0.0 and 100.0.
+ */
+void rgb2hpluv(double r, double g, double b, double* ph, double* ps, double* pl);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* HSLUV_H */
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,15 @@
+</$objtype/mkfile
+
+TARG=picker
+BIN=/$objtype/bin/
+MAN=/sys/man/1
+
+OFILES=\
+	hsluv.$O\
+	picker.$O\
+
+default:V:	all
+
+</sys/src/cmd/mkone
+
+install:V: $MAN/$TARG
--- /dev/null
+++ b/picker.c
@@ -1,0 +1,315 @@
+#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))
+
+enum
+{
+	Ckey,
+	Cmouse,
+	Cresize,
+	Numchan,
+};
+
+typedef struct Mode Mode;
+
+struct Mode {
+	char *name;
+	char opt;
+	void (*torgb)(double *x, double *r, double *g, double *b);
+	void (*fromrgb)(double r, double g, double b, double *x);
+	double min[3], max[3], init[3];
+};
+
+static void
+_hsluv2rgb(double *x, double *r, double *g, double *b)
+{
+	hsluv2rgb(x[0], x[1], x[2], r, g, b);
+}
+
+static void
+_rgb2hsluv(double r, double g, double b, double *x)
+{
+	rgb2hsluv(r, g, b, x+0, x+1, x+2);
+}
+
+static void
+_hpluv2rgb(double *x, double *r, double *g, double *b)
+{
+	hpluv2rgb(x[0], x[1], x[2], r, g, b);
+}
+
+static void
+_rgb2hpluv(double r, double g, double b, double *x)
+{
+	rgb2hpluv(r, g, b, x+0, x+1, x+2);
+}
+
+static void
+_rgbto(double *x, double *r, double *g, double *b)
+{
+	*r = x[0];
+	*g = x[1];
+	*b = x[2];
+}
+
+static void
+_rgbfrom(double r, double g, double b, double *x)
+{
+	x[0] = r;
+	x[1] = g;
+	x[2] = b;
+}
+
+static Mode modes[] = {
+	{
+		.name = "HSLuv",
+		.opt = 's',
+		.torgb = _hsluv2rgb,
+		.fromrgb = _rgb2hsluv,
+		.min = {0.0, 0.0, 0.0},
+		.max = {360.0, 100.0, 100.0},
+		.init = {0.0, 100.0, 50.0},
+	},
+	{
+		.name = "HPLuv",
+		.opt = 'l',
+		.torgb = _hpluv2rgb,
+		.fromrgb = _rgb2hpluv,
+		.min = {0.0, 0.0, 0.0},
+		.max = {360.0, 100.0, 100.0},
+		.init = {0.0, 100.0, 50.0},
+	},
+	{
+		.name = "RGB",
+		.opt = 'r',
+		.torgb = _rgbto,
+		.fromrgb = _rgbfrom,
+		.min = {0.0, 0.0, 0.0},
+		.max = {1.0, 1.0, 1.0},
+		.init = {0.5, 0.5, 0.5},
+	},
+};
+
+static double *colors;
+static int ncolors, alpha;
+static Mode *mode;
+
+static Image *
+gradient(double *col_, int w, int e)
+{
+	Rectangle rect;
+	u8int *data;
+	Image *im;
+	double color[3];
+	double r, g, b, x;
+	int i, mi;
+
+	rect = Rect(0, 0, w, 1);
+	if ((im = allocimage(display, rect, BGR24, 1, DNofill)) == nil)
+		sysfatal("allocimage: %r");
+
+	color[0] = col_[0];
+	color[1] = col_[1];
+	color[2] = col_[2];
+	data = malloc(3*w);
+	x = (mode->max[e] - mode->min[e]) / w;
+	mi = (color[e] - mode->min[e]) / x;
+	color[e] = mode->min[e];
+	for (i = 0; i < w; i++) {
+		mode->torgb(color, &r, &g, &b);
+		data[i*3+0] = r*255.0;
+		data[i*3+1] = g*255.0;
+		data[i*3+2] = b*255.0;
+		if (mi == i) {
+			data[i*3+0] = ~data[i*3+0];
+			data[i*3+1] = ~data[i*3+1];
+			data[i*3+2] = ~data[i*3+2];
+		}
+		color[e] += x;
+		if (color[e] > mode->max[e])
+			color[e] = mode->max[e];
+	}
+	loadimage(im, rect, data, 3*w);
+	free(data);
+
+	return im;
+}
+
+static void
+redraw(void)
+{
+	Rectangle re;
+	int dh, i;
+	Image *im;
+	double r, g, b;
+	ulong u;
+	char hex[8];
+
+	lockdisplay(display);
+
+	//draw(screen, screen->r, display->white, nil, ZP);
+	re = screen->r;
+	dh = Dy(re) / 4;
+	re.max.y = re.min.y + dh;
+
+	for (i = 0; i < 3; i++) {
+		im = gradient(&colors[4*0], Dx(screen->r), i);
+		draw(screen, re, im, nil, ZP);
+		freeimage(im);
+		re.min.y += dh;
+		re.max.y += dh;
+	}
+
+	mode->torgb(&colors[4*0], &r, &g, &b);
+	u = (int)(r*255.0)<<24 | (int)(r*255.0)<<24 | (int)(g*255.0)<<16 | (int)(b*255.0)<<8 | 0xff;
+	im = allocimage(display, Rect(0, 0, 1, 1), BGR24, 1, u);
+	draw(screen, re, im, nil, ZP);
+	freeimage(im);
+
+	im = allocimage(display, Rect(0, 0, 1, 1), BGR24, 1, ~u);
+	sprint(hex, "#%06lux", u>>8);
+	re.min.x += Dx(re)/2 - 7*stringwidth(font, "#")/2;
+	re.max.x += dh/2 - font->height/2;
+	string(screen, re.min, im, ZP, font, hex);
+	freeimage(im);
+
+	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("] 0xrrggbbaa ...\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 },
+	};
+	char *s;
+	vlong v;
+	int i;
+
+	mode = &modes[0];
+	ARGBEGIN{
+	case 'a':
+		alpha = 1;
+		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
+
+	//setfcr(FPRZ|FPPDBL);
+	ncolors = argc;
+	if (ncolors < 1) {
+		fprint(2, "no colors specified\n");
+		usage();
+	}
+	colors = calloc(ncolors, 4*sizeof(double));
+	for (i = 0; i < ncolors; i++) {
+		double r, g, b;
+		if ((v = strtoll(argv[i], &s, 0)) == 0 && (s == argv[i] || *s || v > 0xffffffff || v < 0)) {
+			fprint(2, "invalid color '%s'\n", argv[i]);
+			usage();
+		}
+		mode->fromrgb(
+			(double)((v>>24)&0xff) / 255.0,
+			(double)((v>>16)&0xff) / 255.0,
+			(double)((v>>8)&0xff) / 255.0,
+			&colors[i*4]
+		);
+		colors[i*4+3] = (double)(v&0xff) / 255.0;
+		mode->torgb(&colors[i*4], &r, &g, &b);
+	}
+
+	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 (;;) {
+
+		switch (alt(a)) {
+		case -1:
+			goto end;
+
+		case Ckey:
+			switch (r) {
+			case Kdel:
+				goto end;
+			}
+			break;
+
+		case Cmouse:
+			if (m.buttons == 1) {
+				Point p;
+				int dh;
+				p = screen->r.min;
+				dh = Dy(screen->r)/4;
+				m.xy.x = MAX(screen->r.min.x, MIN(screen->r.max.x, m.xy.x));
+				for (i = 0; i < 3; i++) {
+					if (m.xy.y >= p.y && m.xy.y < p.y+dh) {
+						colors[4*0 + i] = MIN(mode->max[i], MAX(mode->min[i], mode->min[i] + (m.xy.x - screen->r.min.x) * (mode->max[i] - mode->min[i])/Dx(screen->r)));
+						redraw();
+						break;
+					}
+					p.y += dh;
+				}
+			}
+			break;
+
+		case Cresize:
+			getwindow(display, Refnone);
+			redraw();
+			break;
+		}
+	}
+
+end:
+	threadexitsall(nil);
+}