Merge branch 'feature/xep-0392-0.5' into 'master'
Bring XEP-0392 implementation to XEP version 0.5 See merge request poezio/poezio!13
This commit is contained in:
commit
4e231185f5
3 changed files with 373 additions and 32 deletions
|
@ -3,6 +3,8 @@ import curses
|
|||
import hashlib
|
||||
import math
|
||||
|
||||
from . import hsluv
|
||||
|
||||
Palette = Dict[float, int]
|
||||
|
||||
# BT.601 (YCbCr) constants, see XEP-0392
|
||||
|
@ -33,13 +35,6 @@ def ncurses_color_to_rgb(color: int) -> Tuple[float, float, float]:
|
|||
return r / 5, g / 5, b / 5
|
||||
|
||||
|
||||
def rgb_to_ycbcr(r: float, g: float, b: float) -> Tuple[float, float, float]:
|
||||
y = K_R * r + K_G * g + K_B * b
|
||||
cr = (r - y) / (1 - K_R) / 2
|
||||
cb = (b - y) / (1 - K_B) / 2
|
||||
return y, cb, cr
|
||||
|
||||
|
||||
def generate_ccg_palette(curses_palette: List[int],
|
||||
reference_y: float) -> Palette:
|
||||
cbcr_palette = {} # type: Dict[float, Tuple[float, int]]
|
||||
|
@ -48,8 +43,10 @@ def generate_ccg_palette(curses_palette: List[int],
|
|||
# drop grayscale
|
||||
if r == g == b:
|
||||
continue
|
||||
y, cb, cr = rgb_to_ycbcr(r, g, b)
|
||||
key = round(cbcr_to_angle(cb, cr), 2)
|
||||
h, _, y = hsluv.rgb_to_hsluv((r, g, b))
|
||||
# this is to keep the code compatible with earlier versions of XEP-0392
|
||||
y = y / 100
|
||||
key = round(h)
|
||||
try:
|
||||
existing_y, *_ = cbcr_palette[key]
|
||||
except KeyError:
|
||||
|
@ -68,35 +65,15 @@ def text_to_angle(text: str) -> float:
|
|||
hf = hashlib.sha1()
|
||||
hf.update(text.encode("utf-8"))
|
||||
hue = int.from_bytes(hf.digest()[:2], "little")
|
||||
return hue / 65535 * math.pi * 2
|
||||
|
||||
|
||||
def angle_to_cbcr_edge(angle: float) -> Tuple[float, float]:
|
||||
cr = math.sin(angle)
|
||||
cb = math.cos(angle)
|
||||
if abs(cr) > abs(cb):
|
||||
factor = 0.5 / abs(cr)
|
||||
else:
|
||||
factor = 0.5 / abs(cb)
|
||||
return cb * factor, cr * factor
|
||||
|
||||
|
||||
def cbcr_to_angle(cb: float, cr: float) -> float:
|
||||
magn = math.sqrt(cb**2 + cr**2)
|
||||
if magn > 0:
|
||||
cr /= magn
|
||||
cb /= magn
|
||||
return math.atan2(cr, cb) % (2 * math.pi)
|
||||
return hue / 65535 * 360
|
||||
|
||||
|
||||
def ccg_palette_lookup(palette: Palette, angle: float) -> int:
|
||||
# try quick lookup first
|
||||
try:
|
||||
color = palette[round(angle, 2)]
|
||||
return palette[round(angle)]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return color
|
||||
|
||||
best_metric = float("inf")
|
||||
best = None
|
||||
|
|
360
poezio/hsluv.py
Normal file
360
poezio/hsluv.py
Normal file
|
@ -0,0 +1,360 @@
|
|||
# This file was taken from https://github.com/hsluv/hsluv-python
|
||||
#
|
||||
# Copyright (c) 2015 Alexei Boronine
|
||||
#
|
||||
# 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.
|
||||
""" This module is generated by transpiling Haxe into Python and cleaning
|
||||
the resulting code by hand, e.g. removing unused Haxe classes. To try it
|
||||
yourself, clone https://github.com/hsluv/hsluv and run:
|
||||
|
||||
haxe -cp haxe/src hsluv.Hsluv -python hsluv.py
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
|
||||
|
||||
__version__ = '0.0.2'
|
||||
|
||||
m = [[3.240969941904521, -1.537383177570093, -0.498610760293],
|
||||
[-0.96924363628087, 1.87596750150772, 0.041555057407175],
|
||||
[0.055630079696993, -0.20397695888897, 1.056971514242878]]
|
||||
minv = [[0.41239079926595, 0.35758433938387, 0.18048078840183],
|
||||
[0.21263900587151, 0.71516867876775, 0.072192315360733],
|
||||
[0.019330818715591, 0.11919477979462, 0.95053215224966]]
|
||||
refY = 1.0
|
||||
refU = 0.19783000664283
|
||||
refV = 0.46831999493879
|
||||
kappa = 903.2962962
|
||||
epsilon = 0.0088564516
|
||||
hex_chars = "0123456789abcdef"
|
||||
|
||||
|
||||
def _distance_line_from_origin(line):
|
||||
v = math.pow(line['slope'], 2) + 1
|
||||
return math.fabs(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 = math.pow(l + 16, 3) / 1560896
|
||||
if sub1 > epsilon:
|
||||
sub2 = sub1
|
||||
else:
|
||||
sub2 = l / kappa
|
||||
_g = 0
|
||||
while _g < 3:
|
||||
c = _g
|
||||
_g = _g + 1
|
||||
m1 = m[c][0]
|
||||
m2 = m[c][1]
|
||||
m3 = m[c][2]
|
||||
_g1 = 0
|
||||
while _g1 < 2:
|
||||
t = _g1
|
||||
_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):
|
||||
bounds = _get_bounds(l)
|
||||
_hx_min = 1.7976931348623157e+308
|
||||
_g = 0
|
||||
while _g < 2:
|
||||
i = _g
|
||||
_g = _g + 1
|
||||
length = _distance_line_from_origin(bounds[i])
|
||||
if math.isnan(_hx_min):
|
||||
_hx_min = _hx_min
|
||||
elif math.isnan(length):
|
||||
_hx_min = length
|
||||
else:
|
||||
_hx_min = min(_hx_min, length)
|
||||
return _hx_min
|
||||
|
||||
|
||||
def _max_chroma_for_lh(l, h):
|
||||
hrad = h / 360 * math.pi * 2
|
||||
bounds = _get_bounds(l)
|
||||
_hx_min = 1.7976931348623157e+308
|
||||
_g = 0
|
||||
while _g < len(bounds):
|
||||
bound = bounds[_g]
|
||||
_g = (_g + 1)
|
||||
length = _length_of_ray_until_intersect(hrad, bound)
|
||||
if length >= 0:
|
||||
if math.isnan(_hx_min):
|
||||
_hx_min = _hx_min
|
||||
elif math.isnan(length):
|
||||
_hx_min = length
|
||||
else:
|
||||
_hx_min = min(_hx_min, length)
|
||||
return _hx_min
|
||||
|
||||
|
||||
def _dot_product(a, b):
|
||||
sum = 0
|
||||
_g1 = 0
|
||||
_g = len(a)
|
||||
while _g1 < _g:
|
||||
i = _g1
|
||||
_g1 = _g1 + 1
|
||||
sum += a[i] * b[i]
|
||||
return sum
|
||||
|
||||
|
||||
def _from_linear(c):
|
||||
if c <= 0.0031308:
|
||||
return 12.92 * c
|
||||
else:
|
||||
return 1.055 * math.pow(c, 0.416666666666666685) - 0.055
|
||||
|
||||
|
||||
def _to_linear(c):
|
||||
if c > 0.04045:
|
||||
return math.pow((c + 0.055) / 1.055, 2.4)
|
||||
else:
|
||||
return c / 12.92
|
||||
|
||||
|
||||
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(minv[0], rgbl),
|
||||
_dot_product(minv[1], rgbl),
|
||||
_dot_product(minv[2], rgbl)]
|
||||
|
||||
|
||||
def _y_to_l(y):
|
||||
if y <= epsilon:
|
||||
return y / refY * kappa
|
||||
else:
|
||||
return 116 * math.pow(y / refY, 0.333333333333333315) - 16
|
||||
|
||||
|
||||
def _l_to_y(l):
|
||||
if l <= 8:
|
||||
return refY * l / kappa
|
||||
else:
|
||||
return refY * math.pow((l + 16) / 116, 3)
|
||||
|
||||
|
||||
def xyz_to_luv(_hx_tuple):
|
||||
x = float(_hx_tuple[0])
|
||||
y = float(_hx_tuple[1])
|
||||
z = float(_hx_tuple[2])
|
||||
divider = x + 15 * y + 3 * z
|
||||
var_u = 4 * x
|
||||
var_v = 9 * y
|
||||
if divider != 0:
|
||||
var_u = var_u / divider
|
||||
var_v = var_v / divider
|
||||
else:
|
||||
var_u = float("nan")
|
||||
var_v = float("nan")
|
||||
l = _y_to_l(y)
|
||||
if l == 0:
|
||||
return [0, 0, 0]
|
||||
u = 13 * l * (var_u - refU)
|
||||
v = 13 * l * (var_v - refV)
|
||||
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) + refU
|
||||
var_v = v / (13 * l) + refV
|
||||
y = _l_to_y(l)
|
||||
x = 0 - ((9 * y * var_u) / (((var_u - 4) * var_v) - var_u * var_v))
|
||||
z = (((9 * y) - (15 * var_v * y)) - (var_v * x)) / (3 * 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])
|
||||
_v = (u * u) + (v * v)
|
||||
if _v < 0:
|
||||
c = float("nan")
|
||||
else:
|
||||
c = math.sqrt(_v)
|
||||
if c < 0.00000001:
|
||||
h = 0
|
||||
else:
|
||||
hrad = math.atan2(v, u)
|
||||
h = hrad * 180.0 / 3.1415926535897932
|
||||
if h < 0:
|
||||
h = 360 + h
|
||||
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 = h / 360.0 * 2 * math.pi
|
||||
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 > 99.9999999:
|
||||
return [100, 0, h]
|
||||
if l < 0.00000001:
|
||||
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 > 99.9999999:
|
||||
return [h, 0, 100]
|
||||
if l < 0.00000001:
|
||||
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 > 99.9999999:
|
||||
return [100, 0, h]
|
||||
if l < 0.00000001:
|
||||
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 > 99.9999999:
|
||||
return [h, 0, 100]
|
||||
if l < 0.00000001:
|
||||
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):
|
||||
h = "#"
|
||||
_g = 0
|
||||
while _g < 3:
|
||||
i = _g
|
||||
_g = _g + 1
|
||||
chan = float(_hx_tuple[i])
|
||||
c = math.floor(chan * 255 + 0.5)
|
||||
digit2 = int(c % 16)
|
||||
digit1 = int((c - digit2) / 16)
|
||||
|
||||
h += hex_chars[digit1] + hex_chars[digit2]
|
||||
return h
|
||||
|
||||
|
||||
def hex_to_rgb(hex):
|
||||
hex = hex.lower()
|
||||
ret = []
|
||||
_g = 0
|
||||
while _g < 3:
|
||||
i = _g
|
||||
_g = _g + 1
|
||||
index = i * 2 + 1
|
||||
_hx_str = hex[index]
|
||||
digit1 = hex_chars.find(_hx_str)
|
||||
index1 = i * 2 + 2
|
||||
str1 = hex[index1]
|
||||
digit2 = hex_chars.find(str1)
|
||||
n = digit1 * 16 + digit2
|
||||
ret.append(n / 255.0)
|
||||
return ret
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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))
|
||||
|
||||
|
||||
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))
|
|
@ -59,7 +59,11 @@ class User:
|
|||
theme = get_theme()
|
||||
if theme.ccg_palette:
|
||||
# use XEP-0392 CCG
|
||||
fg_color = colors.ccg_text_to_color(theme.ccg_palette, self.nick)
|
||||
if self.jid and self.jid.domain:
|
||||
input_ = self.jid.bare
|
||||
else:
|
||||
input_ = self.nick
|
||||
fg_color = colors.ccg_text_to_color(theme.ccg_palette, input_)
|
||||
self.color = fg_color, -1
|
||||
else:
|
||||
mod = len(theme.LIST_COLOR_NICKNAMES)
|
||||
|
|
Loading…
Reference in a new issue