Skip to content

Commit dcdfc91

Browse files
committed
faster linear_gradient
1 parent 6d215d4 commit dcdfc91

File tree

2 files changed

+39
-36
lines changed

2 files changed

+39
-36
lines changed

branca/utilities.py

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -66,33 +66,20 @@ def linear_gradient(hexList: List[str], nColors: int) -> List[str]:
6666
nColors where the colors are linearly interpolated between the
6767
(r, g, b) tuples that are given.
6868
"""
69-
70-
def _scale(start, finish, length, i):
71-
"""
72-
Return the value correct value of a number that is in between start
73-
and finish, for use in a loop of length *length*.
74-
75-
"""
76-
base = 16
77-
78-
fraction = float(i) / (length - 1)
79-
raynge = int(finish, base) - int(start, base)
80-
thex = hex(int(int(start, base) + fraction * raynge)).split("x")[-1]
81-
if len(thex) != 2:
82-
thex = "0" + thex
83-
return thex
84-
8569
allColors: List[str] = []
8670
# Separate (R, G, B) pairs.
8771
for start, end in zip(hexList[:-1], hexList[1:]):
8872
# Linearly interpolate between pair of hex ###### values and
8973
# add to list.
90-
nInterpolate = 765
91-
for index in range(nInterpolate):
92-
r = _scale(start[1:3], end[1:3], nInterpolate, index)
93-
g = _scale(start[3:5], end[3:5], nInterpolate, index)
94-
b = _scale(start[5:7], end[5:7], nInterpolate, index)
95-
allColors.append("".join(["#", r, g, b]))
74+
start = [int(start[i:i+2], 16) for i in (1, 3, 5)]
75+
end = [int(end[i:i+2], 16) for i in (1, 3, 5)]
76+
77+
n_interpolate = 765
78+
for i in range(n_interpolate):
79+
frac = i / (n_interpolate - 1)
80+
byte_ints = [int(x + (y - x) * frac) for x, y in zip(start, end)]
81+
hexs = [hex(x)[2:].zfill(2) for x in byte_ints]
82+
allColors.append("#" + "".join(hexs))
9683

9784
# Pick only nColors colors from the total list.
9885
result: List[str] = []

tests/test_utilities.py

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import os
33
from pathlib import Path
4+
from typing import List
45

56
import pytest
67

@@ -42,7 +43,8 @@ def test_color_brewer_reverse():
4243
assert scheme[::-1] == scheme_r
4344

4445

45-
def test_color_brewer_extendability():
46+
@pytest.mark.parametrize("sname", core_schemes)
47+
def test_color_brewer_extendability(sname):
4648
"""
4749
The non-qualitative schemes should be extendable.
4850
@@ -54,21 +56,20 @@ def test_color_brewer_extendability():
5456
Indeed, in color_brewer, the key searched in the scheme database was not found,
5557
thus, it was passing `None` instead of a real scheme vector to linear_gradient.
5658
"""
57-
for sname in core_schemes:
58-
for n in range(color_brewer_minimum_n, color_brewer_maximum_n + 1):
59-
try:
60-
scheme = ut.color_brewer(sname, n=n)
61-
except Exception as e:
62-
if scheme_info[sname] == "Qualitative" and isinstance(e, ValueError):
63-
continue
64-
raise
59+
for n in range(color_brewer_minimum_n, color_brewer_maximum_n + 1):
60+
try:
61+
scheme = ut.color_brewer(sname, n=n)
62+
except Exception as e:
63+
if scheme_info[sname] == "Qualitative" and isinstance(e, ValueError):
64+
continue
65+
raise
6566

66-
assert len(scheme) == n
67+
assert len(scheme) == n
6768

68-
# When we try to extend a scheme,
69-
# the reverse is not always the exact reverse vector of the original one.
70-
# Thus, we do not test this property!
71-
_ = ut.color_brewer(sname + "_r", n=n)
69+
# When we try to extend a scheme,
70+
# the reverse is not always the exact reverse vector of the original one.
71+
# Thus, we do not test this property!
72+
_ = ut.color_brewer(sname + "_r", n=n)
7273

7374

7475
def test_color_avoid_unexpected_error():
@@ -169,3 +170,18 @@ def test_write_png_rgb():
169170
]
170171
png = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x04\x00\x00\x00\x02\x08\x06\x00\x00\x00\x7f\xa8}c\x00\x00\x00-IDATx\xda\x01"\x00\xdd\xff\x00\xff\xa7G\xffp\xff+\xff\x9e\x1cH\xff9\x90$\xff\x00\x93\xe9\xb8\xff\x0cz\xe2\xff\xc6\xca\xff\xff\xd4W\xd0\xffYw\x15\x95\xcf\xb9@D\x00\x00\x00\x00IEND\xaeB`\x82' # noqa E501
171172
assert ut.write_png(image_rgb) == png
173+
174+
175+
@pytest.mark.parametrize(
176+
"hex_list, n_colors, expected_output",
177+
[
178+
(["#000000", "#FFFFFF"], 2, ["#000000", "#ffffff"]),
179+
(["#FF0000", "#00FF00", "#0000FF"], 3, ["#ff0000", "#00ff00", "#0000ff"]),
180+
(["#000000", "#0000FF"], 5, ['#000000', '#00003f', '#00007f', '#0000bf', '#0000ff']),
181+
(["#FFFFFF", "#000000"], 5, ['#ffffff', '#bfbfbf', '#7f7f7f', '#3f3f3f', '#000000']),
182+
(["#FF0000", "#00FF00", "#0000FF"], 7, ['#ff0000', '#aa5400', '#55a900', '#00ff00', '#00aa54', '#0055a9', '#0000ff']),
183+
]
184+
)
185+
def test_linear_gradient(hex_list: List[str], n_colors: int, expected_output: List[str]):
186+
result = ut.linear_gradient(hex_list, n_colors)
187+
assert result == expected_output

0 commit comments

Comments
 (0)