From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: zsh-workers-return-43747-ml=inbox.vuxu.org@zsh.org X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-1.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID, DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from primenet.com.au (ns1.primenet.com.au [203.24.36.2]) by inbox.vuxu.org (OpenSMTPD) with ESMTP id 10b65051 for ; Tue, 30 Oct 2018 23:48:45 +0000 (UTC) Received: (qmail 19758 invoked by alias); 30 Oct 2018 23:48:30 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: List-Unsubscribe: X-Seq: 43747 Received: (qmail 18107 invoked by uid 1010); 30 Oct 2018 23:48:30 -0000 X-Qmail-Scanner-Diagnostics: from park01.gkg.net by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.99.2/21882. spamassassin: 3.4.1. Clear:RC:0(205.235.26.22):SA:0(-1.6/5.0):. Processed in 3.013637 secs); 30 Oct 2018 23:48:30 -0000 X-Envelope-From: SRS0=mk4O=NK=yahoo.co.uk=okiddle@bounces.park01.gkg.net X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | X-Virus-Scanned: by amavisd-new at gkg.net Authentication-Results: amavisd4.gkg.net (amavisd-new); dkim=pass (2048-bit key) header.d=yahoo.co.uk X-Greylist: domain auto-whitelisted by SQLgrey-1.8.0 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.co.uk; s=s2048; t=1540942911; bh=Y1mq0M3Jr9KNhSbMpjB2j9t09E5A/YeGZ/MMM18Bi18=; h=From:References:To:Subject:Date:From:Subject; b=h3rh17SCSEA3VOdwyLEr4wWWqo8W2fejxKmNoYXy+OeQkX95CeHIM02UH+aOwdB1sVvtWyRjq1GCnL2TSLz2QwVbjAeAatoE1kkIi5cPEd7kDWTB84F/2pMrss78eSBAgZKTozC/L2SgO2h8E2L1BREExq1/8M2+zLlAL4muE/TcyvBaLiKKCoc3fVm1XmGEp6cAnkmzfIsVNcteS03E0GD6L+9xqAI9NdV9mvMm3NHgmkp+Gzhv7f7T60a/rqqheTxgpmQluZA3xk3lsF49XHFeHcDJuxzFG6XFQvMWWdSMFWjJ0im5faKX88f8w6khJRfgWK2yybiB1yRA/MMAVw== X-YMail-OSG: ugJm0t4VM1mxPwbBN6SlD8gaUfqNsMQHPcxqeEuYKz_NMKsKrM2dEZNMTXcovzZ Rc3T3SHTsai.b_br_nCSZvwRtmrd9AmyT5bWQhO6rGXy0jyHHgn.ah2QT8mX2apEkSIs6wLjwdtW zBgpdqduzcS3sqipDUfQ1ryU82Tu0QBIHEW9tcFOCQVQ3GFDP0V_NPorCV9kfvyycnBAzYrRzQMG xoVtDSxxV3YCDx3mMTp2tf7QIICK0vF5HctWoJyyOx0aatOhA3uXWLCA7vc4AdIfUnAyBeyInDac jCkazjvFpth.b4dTNk5jvR6i_dSoIF1LnqgVfBXQpnqk3..0oXr4qA6.QxeX8a05Ojngw.aCw5xB MTky_nO8sZG9f8GRwjScgx15N83RrHvKgvte6MDWgb5tGyYCv2n4vtXmLbiy12OOXl2dlyqhQoLt 5Hf_yxZnQg_F_DtDMPnU8KwIzN95i8ltiyzsD3_qVSyf.uXQRqMc16h1RjO5_GOCILA5XOLFTblS DwyxbhOLBWuK1i38DCCXwJVf5b4w3IhBLTKM.tOm39GbZFBLxd7QyYKLFOjPo1_6vto4fGiCu446 GH3zInQuVErdA80d_raP4pDWNWEedojS1OB0Jb1.10V6eauxyNXnSZ3CGdnPtAWkskOQazv7HGBl r6FQ3bfdugz3LSKDUxQ3.Ev6WVj_gaWYR2N8McdFez_TtaI_j9mN12w16lfkWcBJzzpKV6h2_3xp gHuD1RJJkWY4ObQYlUB5YSKj1eDPS7lgmV1Ip70jkPSQAGZFj3ppOVdWLc8XPMbmDZynkyqVDety acIJwMr.bsoalqDa_EfLeP923Qn9A_C96YMbyChN2j6lcOOE2YtUjnWRyNWxNCwT6LSIBibQ_NBI J.VKKz5pPJtQHJ7u9lhWQp174aqQ90eYeAb10RsHArL62OY5tfTDiTw4wsMB1Gx_Hk0RN0zSDPz9 WciXxG3aR7s70bUoxUUfFN1wiaexdgxyLCvgHAjfkVXsOYZlQpnSTuZxTOgFVepDa.KoVTCJLXhE QaSUkIHKLKPys5Cs- cc: Zsh workers In-reply-to: From: Oliver Kiddle References: To: Sebastian Gniazdowski Subject: Re: =?UTF-8?Q?Terminal_theme_tool_=E2=80=93_a_workaround_for_lack_of_24=2D?= =?UTF-8?Q?bit_color_in_Zsh=3F?= MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <58110.1540942908.1@hydra> Date: Wed, 31 Oct 2018 00:41:48 +0100 Message-ID: <58111-1540942908.680582@SXAw.BXd_.-x5N> On 15 Oct, Sebastian Gniazdowski wrote: > There are 2 escape sequences, ESC]4;n;#rrggbb\a and ESC]Pnrrggbb, that > allow to change terminal's current palette. First works on most > I'm curious on opinions whether this really is a workaround for no > true-color support in Zsh and maybe on ideas of what such > terminal-theme manager should do. That does cause the colours further back in the scrollback buffer to change. I find 256 colours to be plenty for my needs but it would be nice to be able to specify colours consistently such as with hex triples. I still need to deal with 88-colour terminals and that makes it harder to setup consistent themes. For themes, agreeing on a particular associative array to map tokens to colours may work well enough. Perhaps with explicit support in zsh where colours are specified. The patch below takes a different approach to this. Colours specified as hex triples are matched to the nearest colour in the usual 256-colour palette. The patch adds a module with a hook function for retrieving the colour. Actual true colour support wouldn't fit well in a module as it would mostly affect the existing attribute tracking code. But this module can act as a substitute while allowing a consistent way to specify colours. True colour would require upping the attributes to 64-bits to make space for 24-bits for the colour. To do that, the same questions I posed for PM_ flags would apply. Can we just use uint64_t or is autoconf magic required? A second int is also possible but somewhat ugly because it means an extra parameter to some functions. An alternative that keeps the attributes within 32-bits would be to keep our own palette of 256 custom colours. Sebastian's approach of redefining the terminal's palette is also an option. The nearest colour matching code converts RGB colours to CIE L*a*b*. There are more sophisticated methods but this does a noticably better job than just taking distances in RGB space. It doesn't use any of the first 16 colours, those are regularly configured and can't be relied upon. While it is possible that someone has the whole range configured, I don't think that's very common and, if really needed, there is an escape sequence to get the colour values. It does compare directly against all possible colours. I experimented with trying to optimise it to avoid that but comparing 240 appears instant, even on my decade old PC, and alternatives just ended up with longer code. I'd be interested if anyone has any thoughts or views on any of this? Oliver PS. Thanks to Sebastian for testing out an earlier version of the colour matching code. diff --git a/Src/Modules/nearcolor.c b/Src/Modules/nearcolor.c new file mode 100644 index 000000000..2a763d470 --- /dev/null +++ b/Src/Modules/nearcolor.c @@ -0,0 +1,178 @@ +#include "nearcolor.mdh" +#include "nearcolor.pro" + +#include + +struct cielab { + float L, a, b; +}; +typedef struct cielab *Cielab; + +static float +deltae(Cielab lab1, Cielab lab2) +{ + /* taking square root unnecessary as we're just comparing values */ + return powf(lab1->L - lab2->L, 2) + + powf(lab1->a - lab2->a, 2) + + powf(lab1->b - lab2->b, 2); +} + +static void +RGBtoLAB(int red, int green, int blue, Cielab lab) +{ + float R = (float)red / 255.0; + float G = (float)green / 255.0; + float B = (float)blue / 255.0; + R = 100.0 * (R > 0.04045 ? powf((R + 0.055) / 1.055, 2.4) : R / 12.92); + G = 100.0 * (G > 0.04045 ? powf((G + 0.055) / 1.055, 2.4) : G / 12.92); + B = 100.0 * (B > 0.04045 ? powf((B + 0.055) / 1.055, 2.4) : B / 12.92); + + /* Observer. = 2 degrees, Illuminant = D65 */ + float X = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 95.047; + float Y = (R * 0.2126 + G * 0.7152 + B * 0.0722) / 100.0; + float Z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 108.883; + + X = (X > 0.008856) ? powf(X, 1.0/3.0) : (7.787 * X) + (16.0 / 116.0); + Y = (Y > 0.008856) ? powf(Y, 1.0/3.0) : (7.787 * Y) + (16.0 / 116.0); + Z = (Z > 0.008856) ? powf(Z, 1.0/3.0) : (7.787 * Z) + (16.0 / 116.0); + + lab->L = (116.0 * Y) - 16.0; + lab->a = 500.0 * (X - Y); + lab->b = 200.0 * (Y - Z); +} + +static int +mapRGBto88(int red, int green, int blue) +{ + int component[] = { 0, 0x8b, 0xcd, 0xff, 0x2e, 0x5c, 0x8b, 0xa2, 0xb9, 0xd0, 0xe7 }; + struct cielab orig, next; + float nextl, bestl = -1; + int r, g, b; + int comp_r = 0, comp_g = 0, comp_b = 0; + + /* Get original value */ + RGBtoLAB(red, green, blue, &orig); + + /* try every one of the 72 colours */ + for (r = 0; r < 11; r++) { + for (g = 0; g <= 3; g++) { + for (b = 0; b <= 3; b++) { + if (r > 3) g = b = r; /* advance inner loops to the block of greys */ + RGBtoLAB(component[r], component[g], component[b], &next); + nextl = deltae(&orig, &next); + if (nextl < bestl || bestl < 0) { + bestl = nextl; + comp_r = r; + comp_g = g; + comp_b = b; + } + } + } + } + + return (comp_r > 3) ? 77 + comp_r : + 16 + (comp_r * 16) + (comp_g * 4) + comp_b; +} + +/* + * Convert RGB to nearest colour in the 256 colour range + */ +static int +mapRGBto256(int red, int green, int blue) +{ + int component[] = { + 0, 0x5f, 0x87, 0xaf, 0xd7, 0xff, + 0x8, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, + 0x58, 0x62, 0x6c, 0x76, 0x80, 0x8a, 0x94, 0x9e, + 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee + }; + struct cielab orig, next; + float nextl, bestl = -1; + int r, g, b; + int comp_r = 0, comp_g = 0, comp_b = 0; + + /* Get original value */ + RGBtoLAB(red, green, blue, &orig); + + for (r = 0; r < sizeof(component)/sizeof(*component); r++) { + for (g = 0; g <= 5; g++) { + for (b = 0; b <= 5; b++) { + if (r > 5) g = b = r; /* advance inner loops to the block of greys */ + RGBtoLAB(component[r], component[g], component[b], &next); + nextl = deltae(&orig, &next); + if (nextl < bestl || bestl < 0) { + bestl = nextl; + comp_r = r; + comp_g = g; + comp_b = b; + } + } + } + } + + return (comp_r > 5) ? 226 + comp_r : + 16 + (comp_r * 36) + (comp_g * 6) + comp_b; +} + +static int +getnearestcolor(UNUSED(Hookdef dummy), Color_rgb col) +{ + if (tccolours == 256) + return mapRGBto256(col->red, col->green, col->blue); + if (tccolours == 88) + return mapRGBto88(col->red, col->green, col->blue); + return 0; +} + +static struct features module_features = { + NULL, 0, + NULL, 0, + NULL, 0, + NULL, 0, + 0 +}; + +/**/ +int +setup_(UNUSED(Module m)) +{ + return 0; +} + +/**/ +int +features_(Module m, char ***features) +{ + *features = featuresarray(m, &module_features); + return 0; +} + +/**/ +int +enables_(Module m, int **enables) +{ + return handlefeatures(m, &module_features, enables); +} + +/**/ +int +boot_(Module m) +{ + addhookfunc("get_color_attr", (Hookfn) getnearestcolor); + return 0; +} + +/**/ +int +cleanup_(Module m) +{ + deletehookfunc("get_color_attr", (Hookfn) getnearestcolor); + return setfeatureenables(m, &module_features, NULL); +} + +/**/ +int +finish_(UNUSED(Module m)) +{ + return 0; +} diff --git a/Src/Modules/nearcolor.mdd b/Src/Modules/nearcolor.mdd new file mode 100644 index 000000000..2fcdaf04e --- /dev/null +++ b/Src/Modules/nearcolor.mdd @@ -0,0 +1,5 @@ +name=zsh/nearcolor +link=dynamic +load=no + +objects="nearcolor.o" diff --git a/Src/init.c b/Src/init.c index cec914329..e7e62e2f7 100644 --- a/Src/init.c +++ b/Src/init.c @@ -94,6 +94,7 @@ mod_export struct hookdef zshhooks[] = { HOOKDEF("exit", NULL, HOOKF_ALL), HOOKDEF("before_trap", NULL, HOOKF_ALL), HOOKDEF("after_trap", NULL, HOOKF_ALL), + HOOKDEF("get_color_attr", NULL, HOOKF_ALL), }; /* keep executing lists until EOF found */ diff --git a/Src/prompt.c b/Src/prompt.c index 959ed8e3d..39edbdb2b 100644 --- a/Src/prompt.c +++ b/Src/prompt.c @@ -1621,7 +1621,24 @@ match_colour(const char **teststrp, int is_fg, int colour) int shft, on, named = 0, tc; if (teststrp) { - if ((named = ialpha(**teststrp))) { + if (**teststrp == '#' && isxdigit((*teststrp)[1])) { + struct color_rgb color; + char *end; + zlong col = zstrtol(*teststrp+1, &end, 16); + if (end - *teststrp == 4) { + color.red = col >> 8 | ((col >> 8) << 4); + color.green = (col & 0xf0) >> 4; + color.green |= color.green << 4; + color.blue = col & 0xf; + color.blue |= color.blue << 4; + } else if (end - *teststrp == 7) { + color.red = col >> 16; + color.green = (col & 0xff00) >> 8; + color.blue = col & 0xff; + } + *teststrp = end; + colour = runhookdef(GETCOLORATTR, &color); + } else if ((named = ialpha(**teststrp))) { colour = match_named_colour(teststrp); if (colour == 8) { /* default */ diff --git a/Src/zsh.h b/Src/zsh.h index 894158818..68731e226 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2707,6 +2707,12 @@ struct ttyinfo { #define COL_SEQ_BG (1) #define COL_SEQ_COUNT (2) +struct color_rgb { + unsigned int red, green, blue; +}; + +typedef struct color_rgb *Color_rgb; + /* * Flags to testcap() and set_colour_attribute (which currently only * handles TSC_PROMPT). @@ -3203,6 +3209,7 @@ enum { #define EXITHOOK (zshhooks + 0) #define BEFORETRAPHOOK (zshhooks + 1) #define AFTERTRAPHOOK (zshhooks + 2) +#define GETCOLORATTR (zshhooks + 3) #ifdef MULTIBYTE_SUPPORT /* Final argument to mb_niceformat() */