Skip to content

Commit

Permalink
Rename to HSLuv, upgrade to rev4
Browse files Browse the repository at this point in the history
  • Loading branch information
boronine committed Dec 16, 2016
1 parent bb150e1 commit 7e30a7f
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 403 deletions.
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
*.pyc
*.pyo
/test/snapshot-current.png
/test/snapshot-current.json
/husl.egg-info
*.egg-info
/venv
/dist

# IntelliJ IDEA / PyCharm
/.idea
*.iml
23 changes: 12 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
[![Build Status](https://travis-ci.org/husl-colors/husl-python.svg?branch=master)](http://travis-ci.org/husl-colors/husl-python)
[![Package Version](https://img.shields.io/pypi/v/husl.svg)](https://pypi.python.org/pypi/husl/)
[![Build Status](https://travis-ci.org/hsluv/hsluv-python.svg?branch=master)](http://travis-ci.org/hsluv/hsluv-python)
[![Package Version](https://img.shields.io/pypi/v/hsluv.svg)](https://pypi.python.org/pypi/hsluv/)

A Python implementation of [HUSL](http://www.husl-colors.org) (revision 3).
A Python implementation of [HSLuv](http://www.hsluv.org) (revision 3).

## Installation

`pip install husl`
`pip install hsluv`

## Usage

**husl_to_hex(hue, saturation, lightness)**
**hsluv_to_hex([hue, saturation, lightness])**

`hue` is a float between 0 and 360, `saturation` and `lightness` are floats between 0 and 100. This function returns the resulting color as a hex string.
`hue` is a float between 0 and 360, `saturation` and `lightness` are floats between 0 and 100. This
function returns the resulting color as a hex string.

**husl_to_rgb(hue, saturation, lightness)**
**hsluv_to_rgb([hue, saturation, lightness])**

Like above, but returns a list of 3 floats between 0 and 1, for each RGB channel.

**hex_to_husl(hex)**
**hex_to_hsluv(hex)**

Takes a hex string and returns the HUSL color as a list of floats as defined above.
Takes a hex string and returns the HSLuv color as a list of floats as defined above.

**rgb_to_husl(red, green, blue)**
**rgb_to_hsluv(red, green, blue)**

Like above, but `red`, `green` and `blue` are passed as floats between 0 and 1.

For HUSLp (the pastel variant), use `huslp_to_hex`, `huslp_to_rgb`, `hex_to_huslp` and `rgb_to_huslp`.
For HPLuv (the pastel variant), use `hpluv_to_hex`, `hpluv_to_rgb`, `hex_to_hpluv` and `rgb_to_hpluv`.

## Testing

Expand Down
330 changes: 330 additions & 0 deletions hsluv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
import math

__version__ = '0.0.1'

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))
Loading

0 comments on commit 7e30a7f

Please sign in to comment.