💾 Archived View for bacaliu.de › color-converter.html captured on 2023-07-10 at 13:47:16.

View Raw

More Information

-=-=-=-=-=-=-

<!DOCTYPE html>
<html lang="en">
<head>
<!-- 2023-06-26 Mo 07:33 -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Color-converter</title>
<meta name="generator" content="Org Mode" />

<meta property="og:type" content="article">
<meta property="og:site_name" content="Bacaliu.de">
<link rel="stylesheet" type="text/css" href="/css/modus.css">
<link rel="shortcut icon" href="/favicon.ico" sizes="65x65">
<script src=/static/js/htmx.min.js></script>
<link rel="stylesheet" type="text/css" href="/css/colors/auto.css"
      id="ef-theme"
      hx-swap="outerHTML" hx-get="/data/colorscheme"
      hx-trigger="load">
<script defer src="/static/js/pyscript.js"></script>
</head>
<body>
<header id="preamble" class="status">
<a id='top'></a>
<pre style='text-align: left; font-size: min(2vw, 1rem); display: inline-block;'>
 ___               _ _           _
| _ ) __ _ __ __ _| (_)_  _   __| |___
| _ \/ _` / _/ _` | | | || |_/ _` / -_)
|___/\__,_\__\__,_|_|_|\_,_(_)__,_\___|

</pre><br>
<a href='#top'>↑</a>
- <a href='/index.html'>Home</a>
- <a href='/search.html'>Search</a>
- <a href='#bottom'>↓</a>
<br>
<div hx-get="/data/colorlist" hx-trigger="load"></div>
<hr>
</header>
<main id="content" class="content">
<header>
<h1 class="title">Color-converter</h1>
<p class="subtitle" role="doc-subtitle">Converting colors</p>
</header><nav id="table-of-contents" role="doc-toc">
<h2>Table of Contents</h2>
<div id="text-table-of-contents" role="doc-toc">
<ul>
<li><a href="#what-hsluv">1. What is HSLuv?</a></li>
<li><a href="#hsluv-hex-rgb">2. HSLuv to hex and rgb</a></li>
<li><a href="#hex-hsluv-rgb">3. hex to HSLuv and rgb</a></li>
<li><a href="#bibliography">Bibliography</a></li>
<li><a href="#nav">Nav</a></li>
</ul>
</div>
</nav>
<py-script>
from functools import wraps as _wraps, partial as _partial  # unexport, see #17
import math as _math  # unexport, see #17

__version__ = '5.0.3'

_m = [[3.240969941904521, -1.537383177570093, -0.498610760293],
      [-0.96924363628087, 1.87596750150772, 0.041555057407175],
      [0.055630079696993, -0.20397695888897, 1.056971514242878]]
_min_v = [[0.41239079926595, 0.35758433938387, 0.18048078840183],
          [0.21263900587151, 0.71516867876775, 0.072192315360733],
          [0.019330818715591, 0.11919477979462, 0.95053215224966]]
_ref_y = 1.0
_ref_u = 0.19783000664283
_ref_v = 0.46831999493879
_kappa = 903.2962962
_epsilon = 0.0088564516


def _normalize_output(conversion):
    # as in snapshot rev 4, the tolerance should be 1e-11
    normalize = _partial(round, ndigits=11-1)

    @_wraps(conversion)
    def normalized(*args, **kwargs):
        color = conversion(*args, **kwargs)
        return tuple(normalize(c) for c in color)
    return normalized


def _distance_line_from_origin(line):
    v = line['slope'] ** 2 + 1
    return abs(line['intercept']) / _math.sqrt(v)


def _length_of_ray_until_intersect(theta, line):
    return line['intercept']\
         / (_math.sin(theta) - line['slope'] * _math.cos(theta))


def _get_bounds(l):
    result = []
    sub1 = ((l + 16) ** 3) / 1560896
    if sub1 > _epsilon:
        sub2 = sub1
    else:
        sub2 = l / _kappa
    _g = 0
    while _g < 3:
        c = _g
        _g += 1
        m1 = _m[c][0]
        m2 = _m[c][1]
        m3 = _m[c][2]
        _g1 = 0
        while _g1 < 2:
            t = _g1
            _g1 += 1
            top1 = (284517 * m1 - 94839 * m3) * sub2
            top2 = (838422 * m3 + 769860 * m2 + 731718 * m1)\
                * l * sub2 - (769860 * t) * l
            bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t
            result.append({'slope': top1 / bottom, 'intercept': top2 / bottom})
    return result


def _max_safe_chroma_for_l(l):
    return min(_distance_line_from_origin(bound)
            for bound in _get_bounds(l))


def _max_chroma_for_lh(l, h):
    hrad = _math.radians(h)
    lengths = [_length_of_ray_until_intersect(hrad, bound) for bound in _get_bounds(l)]
    return min(length for length in lengths if length >= 0)


def _dot_product(a, b):
    return sum(i * j for i, j in zip(a, b))


def _from_linear(c):
    if c <= 0.0031308:
        return 12.92 * c

    return 1.055 * _math.pow(c, 5 / 12) - 0.055


def _to_linear(c):
    if c > 0.04045:
        return _math.pow((c + 0.055) / 1.055, 2.4)

    return c / 12.92


def _y_to_l(y):
    if y <= _epsilon:
        return y / _ref_y * _kappa

    return 116 * _math.pow(y / _ref_y, 1 / 3) - 16


def _l_to_y(l):
    if l <= 8:
        return _ref_y * l / _kappa

    return _ref_y * (((l + 16) / 116) ** 3)


def xyz_to_rgb(_hx_tuple):
    return (
        _from_linear(_dot_product(_m[0], _hx_tuple)),
        _from_linear(_dot_product(_m[1], _hx_tuple)),
        _from_linear(_dot_product(_m[2], _hx_tuple)))


def rgb_to_xyz(_hx_tuple):
    rgbl = (_to_linear(_hx_tuple[0]),
            _to_linear(_hx_tuple[1]),
            _to_linear(_hx_tuple[2]))
    return (_dot_product(_min_v[0], rgbl),
            _dot_product(_min_v[1], rgbl),
            _dot_product(_min_v[2], rgbl))


def xyz_to_luv(_hx_tuple):
    x = float(_hx_tuple[0])
    y = float(_hx_tuple[1])
    z = float(_hx_tuple[2])
    l = _y_to_l(y)
    if l == 0:
        return (0, 0, 0)
    divider = x + 15 * y + 3 * z
    if divider == 0:
        u = v = float("nan")
        return (l, u, v)
    var_u = 4 * x / divider
    var_v = 9 * y / divider
    u = 13 * l * (var_u - _ref_u)
    v = 13 * l * (var_v - _ref_v)
    return (l, u, v)


def luv_to_xyz(_hx_tuple):
    l = float(_hx_tuple[0])
    u = float(_hx_tuple[1])
    v = float(_hx_tuple[2])
    if l == 0:
        return (0, 0, 0)
    var_u = u / (13 * l) + _ref_u
    var_v = v / (13 * l) + _ref_v
    y = _l_to_y(l)
    x = y * 9 * var_u / (4 * var_v)
    z = y * (12 - 3 * var_u - 20 * var_v) / (4 * var_v)
    return (x, y, z)


def luv_to_lch(_hx_tuple):
    l = float(_hx_tuple[0])
    u = float(_hx_tuple[1])
    v = float(_hx_tuple[2])
    c = _math.hypot(u, v)
    if c < 1e-08:
        h = 0
    else:
        hrad = _math.atan2(v, u)
        h = _math.degrees(hrad)
        if h < 0:
            h += 360
    return (l, c, h)


def lch_to_luv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    hrad = _math.radians(h)
    u = _math.cos(hrad) * c
    v = _math.sin(hrad) * c
    return (l, u, v)


def hsluv_to_lch(_hx_tuple):
    h = float(_hx_tuple[0])
    s = float(_hx_tuple[1])
    l = float(_hx_tuple[2])
    if l > 100-1e-7:
        return (100, 0, h)
    if l < 1e-08:
        return (0, 0, h)
    _hx_max = _max_chroma_for_lh(l, h)
    c = _hx_max / 100 * s
    return (l, c, h)


def lch_to_hsluv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    if l > 100-1e-7:
        return (h, 0, 100)
    if l < 1e-08:
        return (h, 0, 0)
    _hx_max = _max_chroma_for_lh(l, h)
    s = c / _hx_max * 100
    return (h, s, l)


def hpluv_to_lch(_hx_tuple):
    h = float(_hx_tuple[0])
    s = float(_hx_tuple[1])
    l = float(_hx_tuple[2])
    if l > 100-1e-7:
        return (100, 0, h)
    if l < 1e-08:
        return (0, 0, h)
    _hx_max = _max_safe_chroma_for_l(l)
    c = _hx_max / 100 * s
    return (l, c, h)


def lch_to_hpluv(_hx_tuple):
    l = float(_hx_tuple[0])
    c = float(_hx_tuple[1])
    h = float(_hx_tuple[2])
    if l > 100-1e-7:
        return (h, 0, 100)
    if l < 1e-08:
        return (h, 0, 0)
    _hx_max = _max_safe_chroma_for_l(l)
    s = c / _hx_max * 100
    return (h, s, l)


def rgb_to_hex(_hx_tuple):
    return '#{:02x}{:02x}{:02x}'.format(
            int(_math.floor(_hx_tuple[0] * 255 + 0.5)),
            int(_math.floor(_hx_tuple[1] * 255 + 0.5)),
            int(_math.floor(_hx_tuple[2] * 255 + 0.5)))


def hex_to_rgb(_hex):
    # skip leading '#'
    r = int(_hex[1:3], base=16) / 255.0
    g = int(_hex[3:5], base=16) / 255.0
    b = int(_hex[5:7], base=16) / 255.0
    return (r, g, b)


def lch_to_rgb(_hx_tuple):
    return xyz_to_rgb(luv_to_xyz(lch_to_luv(_hx_tuple)))


def rgb_to_lch(_hx_tuple):
    return luv_to_lch(xyz_to_luv(rgb_to_xyz(_hx_tuple)))


def _hsluv_to_rgb(_hx_tuple):
    return lch_to_rgb(hsluv_to_lch(_hx_tuple))


hsluv_to_rgb = _normalize_output(_hsluv_to_rgb)


def rgb_to_hsluv(_hx_tuple):
    return lch_to_hsluv(rgb_to_lch(_hx_tuple))


def _hpluv_to_rgb(_hx_tuple):
    return lch_to_rgb(hpluv_to_lch(_hx_tuple))


hpluv_to_rgb = _normalize_output(_hpluv_to_rgb)


def rgb_to_hpluv(_hx_tuple):
    return lch_to_hpluv(rgb_to_lch(_hx_tuple))


def hsluv_to_hex(_hx_tuple):
    return rgb_to_hex(hsluv_to_rgb(_hx_tuple))


def hpluv_to_hex(_hx_tuple):
    return rgb_to_hex(hpluv_to_rgb(_hx_tuple))


def hex_to_hsluv(s):
    return rgb_to_hsluv(hex_to_rgb(s))


def hex_to_hpluv(s):
    return rgb_to_hpluv(hex_to_rgb(s))
def function_1(*args, **kwargs):
    h = Element('hue').element.value
    s = Element('saturation').element.value
    l = Element('luv').element.value

    target = document.getElementById("hsluv-hex-output")
    target.style.backgroundColor = hsluv_to_hex((h, s, l))
    target.style.color = "black" if int(l) > 50 else "white"
    target.innerText = hsluv_to_hex((h, s, l))

    target = document.getElementById("hsluv-rgb-output")
    target.style.backgroundColor = hsluv_to_hex((h, s, l))
    target.style.color = "black" if int(l) > 50 else "white"
    target.innerText = ", ".join([f"{256*n:.1f}" for n in hsluv_to_rgb((h, s, l))])

def function_2(*args, **kwargs):
    hex = Element("hex-input").element.value
    hex = "#" + hex.replace("#", "")
    h, s, l = hex_to_hsluv(hex)

    target = document.getElementById("hex-hsluv-output")
    target.style.backgroundColor = hex
    target.style.color = "black" if int(l) > 50 else "white"
    target.innerText = ", ".join([f"{n:.1f}" for n in [h, s, l]])

    target = document.getElementById("hex-rgb-output")
    target.style.backgroundColor = hex
    target.style.color = "black" if int(l) > 50 else "white"
    target.innerText = ", ".join([f"{256*n:.1f}" for n in hex_to_rgb(hex)])
</py-script>

<div id="outline-container-what-hsluv" class="outline-2">
<h2 id="what-hsluv"><span class="section-number-2">1.</span> <a href="#what-hsluv">What is HSLuv?</a></h2>
<div class="outline-text-2" id="text-1">
<p>
HSLuv (<a href="#citeproc_bib_item_1">Boronine 2022</a>) is a color model similar to hsl, but it is tuned by the human perception. Because I like it, use it a lot, and wanted to try out Pyscript (<a href="#citeproc_bib_item_2">Inc. 2022</a>), I created this calculator to convert Colors with it.<br>
</p>


<figure id="hsluv-Farbkreis">
<img src="images/hsluv.png" alt="hsluv.png" class="invertable"><br>

<figcaption><span class="figure-number">Figure 1: </span>hsluv-Farbkreis</figcaption>
</figure>
</div>
</div>


<div id="outline-container-hsluv-hex-rgb" class="outline-2">
<h2 id="hsluv-hex-rgb"><span class="section-number-2">2.</span> <a href="#hsluv-hex-rgb">HSLuv to hex and rgb</a></h2>
<div class="outline-text-2" id="text-2">
<form onsubmit="return false" style="font-family: var(--fonts-typewriter);">
  Hue (0-360): <input type="number" id="hue" min="0" max="360" placeholder="hue" size="7ch" step="0.1"/><br>
  Sat (0-100): <input type="number" id="saturation" min="0" max="100" placeholder="saturation" size="7ch" step="0.1"/><br>
  Luv (0-100): <input type="number" id="luv" min="0" max="100" placeholder="luv" size="7ch" step="0.1"/><br>
  <button id="submit-button-hsluv" type="submit" pys-onClick="function_1" size="5ch">Calculate</button>
</form>

<p>
<code>hex (00-FF):</code> <span id="hsluv-hex-output" style="font-family: var(--fonts-typewriter);"></span> <br><br>
<code>rgb (0-256):</code> <span id="hsluv-rgb-output" style="font-family: var(--fonts-typewriter);"></span><br>
</p>
</div>
</div>

<div id="outline-container-hex-hsluv-rgb" class="outline-2">
<h2 id="hex-hsluv-rgb"><span class="section-number-2">3.</span> <a href="#hex-hsluv-rgb">hex to HSLuv and rgb</a></h2>
<div class="outline-text-2" id="text-3">
<form onsubmit="return false" style="font-family: var(--fonts-typewriter);">
  Hex (00-FF)^3: <input id="hex-input" placeholder="hex" size="7ch"/><br>
  <button id="submit-button-hex" type="submit" pys-onClick="function_2" size="5ch">Calculate</button>
</form>

<p>
<code>hsluv (0-360/0-100):</code> <span id="hex-hsluv-output" style="font-family: var(--fonts-typewriter);"></span> <br><br>
<code>rgb (0-256):</code> <span id="hex-rgb-output" style="font-family: var(--fonts-typewriter);"></span><br>
</p>
</div>
</div>

<div id="outline-container-bibliography" class="outline-2">
<h2 id="bibliography"><a href="#bibliography">Bibliography</a></h2>
<div class="outline-text-2" id="text-bibliography">
<style>.csl-entry{text-indent: -0; margin-left: 0;}</style><div class="csl-bib-body">
  <div class="csl-entry"><a id="citeproc_bib_item_1"></a>Boronine, A. 2022. “HSLuv - Human-friendly HSL,” September 15, 2022, URL: <a href="https://www.hsluv.org">https://www.hsluv.org</a>.</div>
  <div class="csl-entry"><a id="citeproc_bib_item_2"></a>Inc., A. 2022. “Pyscript.net,” September 1, 2022, URL: <a href="https://pyscript.net">https://pyscript.net</a>, retrieved on September 3, 2022.</div>
</div>
</div>
</div>

<div id="outline-container-nav" class="outline-2">
<h2 id="nav"><a href="#nav">Nav</a></h2>
<div class="outline-text-2" id="text-nav">
<!-- Begin insert Tags (but there are no) -->

<!-- BEGIN insert Backlinks (but there are no) -->
<ul class="org-ul">
<li>Formats: <a href="./color-converter.md">md</a> - <a href="./color-converter.txt">txt</a> - <a href="./color-converter.html">html</a> - <a href="./color-converter.gmi">gmi</a></li>
</ul>
</div>
</div>
</main>
<footer id="postamble" class="status">

<hr>
 
<br>
<img alt="CC BY-4.0" style="border-width:0; vertical-align: middle; height: 1em;" src="/icons/cc-by-4.0-80x15.svg">
<a rel='license' href='http://creativecommons.org/licenses/by/4.0/'>
        CC BY-4.0.
</a>
<br>
running at <a href='https://www.hosting.de/'>hosting.de</a>
<br>
Creator: <a href="https://www.gnu.org/software/emacs/">Emacs</a> 28.2 (<a href="https://orgmode.org">Org</a> mode 9.5.5);
<br>
<a href='/impressum-datenschutz.html' id='impressum-dings'>
    Impressum und Datenschutzerklärung
</a>
<a id='bottom'></a>
</footer>
</body>
</html>