red-colors 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/lib/colors.rb +8 -5
- data/lib/colors/convert.rb +180 -0
- data/lib/colors/husl.rb +7 -100
- data/lib/colors/named_colors.rb +10 -20
- data/lib/colors/rgb.rb +16 -10
- data/lib/colors/rgba.rb +14 -8
- data/lib/colors/version.rb +1 -1
- data/lib/colors/xyy.rb +50 -0
- data/lib/colors/xyz.rb +1 -54
- data/red-colors.gemspec +0 -1
- data/test/test-husl.rb +3 -30
- data/test/test-rgb.rb +28 -1
- data/test/test-xyz.rb +1 -1
- metadata +11 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3587e5433ccb431efbc46112034d3b9f00d40ad58e8389cb4081bf0bdcb3304f
|
4
|
+
data.tar.gz: 2568105af67abd20c5133a7181194489006eef94cc3c4834d0f3509eaf01c5f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d15ae1556244a040ff0822fc739e7dd561d2f29fc9cec04ba68bbc72a1bd6afe097c66fa88f5450bd7f7bce3e89622b34726bdc5e64320255cb6ba71a0c508f7
|
7
|
+
data.tar.gz: aa96cd5773128f79d48993319d920e7325b4db7520429eb211347abfb375688566b083d049e1e679aa81ca05f545682490d70f2b29beaedcbc6ea1c094c45c50
|
data/README.md
CHANGED
data/lib/colors.rb
CHANGED
@@ -1,20 +1,23 @@
|
|
1
|
-
require_relative "colors/helper"
|
2
1
|
require_relative "colors/alpha_component"
|
2
|
+
require_relative "colors/convert"
|
3
|
+
require_relative "colors/helper"
|
3
4
|
|
4
5
|
require_relative "colors/abstract_color"
|
5
|
-
require_relative "colors/xyz"
|
6
|
-
require_relative "colors/rgb"
|
7
|
-
require_relative "colors/rgba"
|
8
6
|
require_relative "colors/hsl"
|
9
7
|
require_relative "colors/hsla"
|
10
8
|
require_relative "colors/husl"
|
9
|
+
require_relative "colors/rgb"
|
10
|
+
require_relative "colors/rgba"
|
11
|
+
require_relative "colors/xyy"
|
12
|
+
require_relative "colors/xyz"
|
11
13
|
|
14
|
+
require_relative "colors/color_data"
|
12
15
|
require_relative "colors/named_colors"
|
13
16
|
|
14
17
|
module Colors
|
15
18
|
# ITU-R BT.709 D65 white point
|
16
19
|
# See https://en.wikipedia.org/wiki/Rec._709 for details
|
17
|
-
WHITE_POINT_D65 = Colors::
|
20
|
+
WHITE_POINT_D65 = Colors::XYY.new(0.3127r, 0.3290r, 1r).to_xyz
|
18
21
|
|
19
22
|
def self.desaturate(c, factor)
|
20
23
|
case c
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module Colors
|
2
|
+
module Convert
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Sort alphabetically by FROM name such as degree, LCh, LUV and so on.
|
6
|
+
|
7
|
+
# Utilities
|
8
|
+
|
9
|
+
private def dot_product(matrix, vector)
|
10
|
+
matrix.map do |row|
|
11
|
+
product = 0.0
|
12
|
+
row.zip(vector) do |value1, value2|
|
13
|
+
product += value1 * value2
|
14
|
+
end
|
15
|
+
product
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def max_chroma(l, h)
|
20
|
+
h_rad = degree_to_radian(h)
|
21
|
+
sin_h = Math.sin(h_rad).to_r
|
22
|
+
cos_h = Math.cos(h_rad).to_r
|
23
|
+
|
24
|
+
max = Float::INFINITY
|
25
|
+
luminance_bounds(l).each do |line|
|
26
|
+
len = line[1] / (sin_h - line[0] * cos_h)
|
27
|
+
max = len if 0 <= len && len < max
|
28
|
+
end
|
29
|
+
max
|
30
|
+
end
|
31
|
+
|
32
|
+
private def luminance_bounds(l)
|
33
|
+
sub1 = (l + 16)**3 / 1560896r
|
34
|
+
sub2 = sub1 > XYZ::EPSILON ? sub1 : l/XYZ::KAPPA
|
35
|
+
|
36
|
+
bounds = Array.new(6) { [0r, 0r] }
|
37
|
+
0.upto(2) do |ch|
|
38
|
+
m1 = XYZ2RGB[ch][0].to_r
|
39
|
+
m2 = XYZ2RGB[ch][1].to_r
|
40
|
+
m3 = XYZ2RGB[ch][2].to_r
|
41
|
+
|
42
|
+
[0, 1].each do |t|
|
43
|
+
top1 = (284517r * m1 - 94839r * m3) * sub2
|
44
|
+
top2 = (838422r * m3 + 769860r * m2 + 731718r * m1) * l * sub2 - 769860r * t * l
|
45
|
+
bottom = (632260r * m3 - 126452r * m2) * sub2 + 126452r * t
|
46
|
+
|
47
|
+
bounds[ch*2 + t][0] = top1 / bottom
|
48
|
+
bounds[ch*2 + t][1] = top2 / bottom
|
49
|
+
end
|
50
|
+
end
|
51
|
+
bounds
|
52
|
+
end
|
53
|
+
|
54
|
+
# degree -> ???
|
55
|
+
|
56
|
+
DEG2RAD = 0.01745329251994329577r # 2 * pi / 360
|
57
|
+
def degree_to_radian(d)
|
58
|
+
d * DEG2RAD
|
59
|
+
end
|
60
|
+
|
61
|
+
# LCh -> ???
|
62
|
+
|
63
|
+
def lch_to_husl(l, c, h)
|
64
|
+
if l > 99.9999999 || l < 1e-8
|
65
|
+
s = 0r
|
66
|
+
else
|
67
|
+
mx = max_chroma(l, h)
|
68
|
+
s = c / mx * 100r
|
69
|
+
end
|
70
|
+
|
71
|
+
h = 0r if c < 1e-8
|
72
|
+
|
73
|
+
[h, s/100r, l/100r]
|
74
|
+
end
|
75
|
+
|
76
|
+
def lch_to_luv(l, c, h)
|
77
|
+
h_rad = degree_to_radian(h)
|
78
|
+
u = Math.cos(h_rad).to_r * c
|
79
|
+
v = Math.sin(h_rad).to_r * c
|
80
|
+
[l, u, v]
|
81
|
+
end
|
82
|
+
|
83
|
+
def lch_to_xyz(l, c, h)
|
84
|
+
luv_to_xyz(*lch_to_luv(l, c, h))
|
85
|
+
end
|
86
|
+
|
87
|
+
# linear-sRGB -> ???
|
88
|
+
|
89
|
+
def linear_srgb_to_srgb(r, g, b)
|
90
|
+
[r, g, b].map do |v|
|
91
|
+
# the following is an optimization technique for `1.055*v**(1/2.4) - 0.055`.
|
92
|
+
# x^y ~= exp(y*log(x)) ~= exp2(y*log2(y)); the middle form is faster
|
93
|
+
#
|
94
|
+
# See https://github.com/JuliaGraphics/Colors.jl/issues/351#issuecomment-532073196
|
95
|
+
# for more detail benchmark in Julia language.
|
96
|
+
if v <= 0.0031308
|
97
|
+
12.92*v
|
98
|
+
else
|
99
|
+
1.055 * Math.exp(1/2.4 * Math.log(v)) - 0.055
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Luv -> ???
|
105
|
+
|
106
|
+
def luv_to_husl(l, u, v)
|
107
|
+
lch_to_husl(*luv_to_lch(l, u, v))
|
108
|
+
end
|
109
|
+
|
110
|
+
def luv_to_lch(l, u, v)
|
111
|
+
c = Math.sqrt(u*u + v*v).to_r
|
112
|
+
|
113
|
+
if c < 1e-8
|
114
|
+
h = 0r
|
115
|
+
else
|
116
|
+
h = Math.atan2(v, u).to_r * 180/Math::PI.to_r
|
117
|
+
h += 360r if h < 0
|
118
|
+
end
|
119
|
+
|
120
|
+
[l, c, h]
|
121
|
+
end
|
122
|
+
|
123
|
+
def luv_to_xyz(l, u, v)
|
124
|
+
return [0r, 0r, 0r] if l <= 1e-8
|
125
|
+
|
126
|
+
wp_u, wp_v = WHITE_POINT_D65.uv_values
|
127
|
+
var_u = u / (13 * l) + wp_u
|
128
|
+
var_v = v / (13 * l) + wp_v
|
129
|
+
y = if l < 8
|
130
|
+
l / XYZ::KAPPA
|
131
|
+
else
|
132
|
+
((l + 16r) / 116r)**3
|
133
|
+
end
|
134
|
+
x = -(9 * y * var_u) / ((var_u - 4) * var_v - var_u * var_v)
|
135
|
+
z = (9 * y - (15 * var_v * y) - (var_v * x)) / (3 * var_v)
|
136
|
+
[x, y, z]
|
137
|
+
end
|
138
|
+
|
139
|
+
# RGB -> ???
|
140
|
+
|
141
|
+
RGB2XYZ = [
|
142
|
+
[ 0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751 ],
|
143
|
+
[ 0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500 ],
|
144
|
+
[ 0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086 ]
|
145
|
+
]
|
146
|
+
|
147
|
+
def rgb_to_xyz(r, g, b)
|
148
|
+
dot_product(RGB2XYZ, srgb_to_linear_srgb(r, g, b))
|
149
|
+
end
|
150
|
+
|
151
|
+
# sRGB -> ???
|
152
|
+
|
153
|
+
def srgb_to_linear_srgb(r, g, b)
|
154
|
+
[r, g, b].map do |v|
|
155
|
+
if v > 0.04045
|
156
|
+
((v + 0.055r) / 1.055r) ** 2.4r
|
157
|
+
else
|
158
|
+
v / 12.92r
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# XYZ -> ???
|
164
|
+
|
165
|
+
XYZ2RGB = [
|
166
|
+
[ 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 ],
|
167
|
+
[ -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 ],
|
168
|
+
[ 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 ]
|
169
|
+
]
|
170
|
+
def xyz_to_rgb(x, y, z)
|
171
|
+
r, g, b = dot_product(XYZ2RGB, [x, y, z])
|
172
|
+
r, g, b = srgb_to_linear_srgb(r, g, b)
|
173
|
+
[
|
174
|
+
r.clamp(0r, 1r),
|
175
|
+
g.clamp(0r, 1r),
|
176
|
+
b.clamp(0r, 1r)
|
177
|
+
]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
data/lib/colors/husl.rb
CHANGED
@@ -1,45 +1,7 @@
|
|
1
|
-
require "numo/narray"
|
2
|
-
|
3
1
|
module Colors
|
4
2
|
# Human-friendly alternative to HSL color space.
|
5
3
|
# The definition of HUSL is provided in <http://www.hsluv.org>.
|
6
4
|
class HUSL < HSL
|
7
|
-
DEG2RAD = 0.01745329251994329577r # 2 * pi / 360
|
8
|
-
|
9
|
-
def self.from_rgb(r, g, b)
|
10
|
-
c = XYZ.from_rgb(r, g, b)
|
11
|
-
l, u, v = c.luv_components(WHITE_POINT_D65)
|
12
|
-
l, c, h = convert_luv_to_lch(l, u, v)
|
13
|
-
h, s, l = convert_lch_to_husl(l, c, h)
|
14
|
-
new(h, s.to_r.clamp(0r, 1r), l.to_r.clamp(0r, 1r))
|
15
|
-
end
|
16
|
-
|
17
|
-
private_class_method def self.convert_luv_to_lch(l, u, v)
|
18
|
-
c = Math.sqrt(u*u + v*v).to_r
|
19
|
-
|
20
|
-
if c < 1e-8
|
21
|
-
h = 0r
|
22
|
-
else
|
23
|
-
h = Math.atan2(v, u).to_r * 180/Math::PI.to_r
|
24
|
-
h += 360r if h < 0
|
25
|
-
end
|
26
|
-
|
27
|
-
[l, c, h]
|
28
|
-
end
|
29
|
-
|
30
|
-
private_class_method def self.convert_lch_to_husl(l, c, h)
|
31
|
-
if l > 99.9999999 || l < 1e-8
|
32
|
-
s = 0r
|
33
|
-
else
|
34
|
-
mx = max_chroma(l, h)
|
35
|
-
s = c / mx * 100r
|
36
|
-
end
|
37
|
-
|
38
|
-
h = 0r if c < 1e-8
|
39
|
-
|
40
|
-
[h, s/100r, l/100r]
|
41
|
-
end
|
42
|
-
|
43
5
|
def ==(other)
|
44
6
|
case other
|
45
7
|
when HUSL
|
@@ -61,10 +23,13 @@ module Colors
|
|
61
23
|
RGB.new(*rgb_components)
|
62
24
|
end
|
63
25
|
|
26
|
+
def to_xyz
|
27
|
+
x, y, z = Convert.lch_to_xyz(*lch_components)
|
28
|
+
XYZ.new(x, y, z)
|
29
|
+
end
|
30
|
+
|
64
31
|
def rgb_components
|
65
|
-
|
66
|
-
x, y, z = convert_luv_to_xyz(l, u, v)
|
67
|
-
XYZ.new(x, y, z).rgb_components
|
32
|
+
to_xyz.rgb_components
|
68
33
|
end
|
69
34
|
|
70
35
|
def lch_components
|
@@ -74,7 +39,7 @@ module Colors
|
|
74
39
|
if l > 99.9999999 || l < 1e-8
|
75
40
|
c = 0r
|
76
41
|
else
|
77
|
-
mx =
|
42
|
+
mx = Convert.max_chroma(l, h)
|
78
43
|
c = mx / 100r * s
|
79
44
|
end
|
80
45
|
|
@@ -82,63 +47,5 @@ module Colors
|
|
82
47
|
|
83
48
|
[l, c, h]
|
84
49
|
end
|
85
|
-
|
86
|
-
private def convert_lch_to_luv(l, c, h)
|
87
|
-
h_rad = h * DEG2RAD
|
88
|
-
u = Math.cos(h_rad).to_r * c
|
89
|
-
v = Math.sin(h_rad).to_r * c
|
90
|
-
[l, u, v]
|
91
|
-
end
|
92
|
-
|
93
|
-
private def convert_luv_to_xyz(l, u, v)
|
94
|
-
return [0r, 0r, 0r] if l <= 1e-8
|
95
|
-
|
96
|
-
wp_u, wp_v = WHITE_POINT_D65.uv_values
|
97
|
-
var_u = u / (13 * l) + wp_u
|
98
|
-
var_v = v / (13 * l) + wp_v
|
99
|
-
y = if l < 8
|
100
|
-
l / XYZ::KAPPA
|
101
|
-
else
|
102
|
-
((l + 16r) / 116r)**3
|
103
|
-
end
|
104
|
-
x = -(9 * y * var_u) / ((var_u - 4) * var_v - var_u * var_v)
|
105
|
-
z = (9 * y - (15 * var_v * y) - (var_v * x)) / (3 * var_v)
|
106
|
-
[x, y, z]
|
107
|
-
end
|
108
|
-
|
109
|
-
def self.max_chroma(l, h)
|
110
|
-
h_rad = h * DEG2RAD
|
111
|
-
sin_h = Math.sin(h_rad).to_r
|
112
|
-
cos_h = Math.cos(h_rad).to_r
|
113
|
-
|
114
|
-
result = Float::INFINITY
|
115
|
-
get_bounds(l).each do |line|
|
116
|
-
len = line[1] / (sin_h - line[0] * cos_h)
|
117
|
-
result = len if 0 <= len && len < result
|
118
|
-
end
|
119
|
-
result
|
120
|
-
end
|
121
|
-
|
122
|
-
def self.get_bounds(l)
|
123
|
-
sub1 = (l + 16)**3 / 1560896r
|
124
|
-
sub2 = sub1 > XYZ::EPSILON ? sub1 : l/XYZ::KAPPA
|
125
|
-
|
126
|
-
bounds = Array.new(6) { [0r, 0r] }
|
127
|
-
0.upto(2) do |ch|
|
128
|
-
m1 = XYZ2RGB[ch, 0].to_r
|
129
|
-
m2 = XYZ2RGB[ch, 1].to_r
|
130
|
-
m3 = XYZ2RGB[ch, 2].to_r
|
131
|
-
|
132
|
-
[0, 1].each do |t|
|
133
|
-
top1 = (284517r * m1 - 94839r * m3) * sub2
|
134
|
-
top2 = (838422r * m3 + 769860r * m2 + 731718r * m1) * l * sub2 - 769860r * t * l
|
135
|
-
bottom = (632260r * m3 - 126452r * m2) * sub2 + 126452r * t
|
136
|
-
|
137
|
-
bounds[ch*2 + t][0] = top1 / bottom
|
138
|
-
bounds[ch*2 + t][1] = top2 / bottom
|
139
|
-
end
|
140
|
-
end
|
141
|
-
bounds
|
142
|
-
end
|
143
50
|
end
|
144
51
|
end
|
data/lib/colors/named_colors.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require_relative 'color_data'
|
2
|
-
|
3
1
|
module Colors
|
4
2
|
module NamedColors
|
5
3
|
class Mapping
|
@@ -8,33 +6,26 @@ module Colors
|
|
8
6
|
@cache = {}
|
9
7
|
end
|
10
8
|
|
11
|
-
attr_reader :cache
|
12
|
-
|
13
9
|
def [](name)
|
14
10
|
if NamedColors.nth_color?(name)
|
15
11
|
cycle = ColorData::DEFAULT_COLOR_CYCLE
|
16
12
|
name = cycle[name[1..-1].to_i % cycle.length]
|
17
13
|
end
|
18
|
-
|
19
|
-
cache[name]
|
20
|
-
else
|
21
|
-
cache[name] = lookup_no_color_cycle(name)
|
22
|
-
end
|
14
|
+
@cache[name] ||= lookup_no_color_cycle(name)
|
23
15
|
end
|
24
16
|
|
25
|
-
private def lookup_no_color_cycle(
|
26
|
-
|
27
|
-
case color
|
17
|
+
private def lookup_no_color_cycle(name)
|
18
|
+
case name
|
28
19
|
when /\Anone\z/i
|
29
20
|
return RGBA.new(0, 0, 0, 0)
|
30
21
|
when String
|
31
22
|
# nothing to do
|
32
23
|
when Symbol
|
33
|
-
|
24
|
+
name = name.to_s
|
34
25
|
else
|
35
|
-
|
26
|
+
name = name.to_str
|
36
27
|
end
|
37
|
-
color = @mapping.fetch(
|
28
|
+
color = @mapping.fetch(name, name)
|
38
29
|
case color
|
39
30
|
when /\A#\h+\z/
|
40
31
|
case color.length - 1
|
@@ -64,19 +55,19 @@ module Colors
|
|
64
55
|
def []=(name, value)
|
65
56
|
@mapping[name] = value
|
66
57
|
ensure
|
67
|
-
cache.clear
|
58
|
+
@cache.clear
|
68
59
|
end
|
69
60
|
|
70
61
|
def delete(name)
|
71
62
|
@mapping.delete(name)
|
72
63
|
ensure
|
73
|
-
cache.clear
|
64
|
+
@cache.clear
|
74
65
|
end
|
75
66
|
|
76
67
|
def update(other)
|
77
68
|
@mapping.update(other)
|
78
69
|
ensure
|
79
|
-
cache.clear
|
70
|
+
@cache.clear
|
80
71
|
end
|
81
72
|
end
|
82
73
|
|
@@ -104,11 +95,10 @@ module Colors
|
|
104
95
|
when Symbol
|
105
96
|
name = name.to_s
|
106
97
|
else
|
98
|
+
return false unless name.respond_to?(:to_str)
|
107
99
|
name = name.to_str
|
108
100
|
end
|
109
101
|
name.match?(/\AC\d+\z/)
|
110
|
-
rescue NoMethodError, TypeError
|
111
|
-
false
|
112
102
|
end
|
113
103
|
end
|
114
104
|
end
|
data/lib/colors/rgb.rb
CHANGED
@@ -1,22 +1,25 @@
|
|
1
|
-
require_relative 'helper'
|
2
|
-
|
3
1
|
module Colors
|
4
2
|
class RGB < AbstractColor
|
5
3
|
include Helper
|
6
4
|
|
7
5
|
def self.parse(hex_string)
|
8
|
-
|
6
|
+
error_message = "must be a hexadecimal string of `#rrggbb` or `#rgb` form"
|
7
|
+
unless hex_string.respond_to?(:to_str)
|
8
|
+
raise ArgumentError, "#{error_message}: #{hex_string.inspect}"
|
9
|
+
end
|
10
|
+
|
11
|
+
hex_string = hex_string.to_str
|
12
|
+
hexes = hex_string.match(/\A#(\h+)\z/) { $1 }
|
13
|
+
case hexes&.length
|
9
14
|
when 3 # rgb
|
10
|
-
r, g, b =
|
15
|
+
r, g, b = hexes.scan(/\h/).map {|h| h.hex * 17 }
|
11
16
|
new(r, g, b)
|
12
17
|
when 6 # rrggbb
|
13
|
-
r, g, b =
|
18
|
+
r, g, b = hexes.scan(/\h{2}/).map(&:hex)
|
14
19
|
new(r, g, b)
|
15
20
|
else
|
16
|
-
raise ArgumentError, "
|
21
|
+
raise ArgumentError, "#{error_message}: #{hex_string.inspect}"
|
17
22
|
end
|
18
|
-
rescue NoMethodError
|
19
|
-
raise ArgumentError, "hex_string must be a hexadecimal string of `#rrggbb` or `#rgb` form"
|
20
23
|
end
|
21
24
|
|
22
25
|
def initialize(r, g, b)
|
@@ -112,11 +115,14 @@ module Colors
|
|
112
115
|
end
|
113
116
|
|
114
117
|
def to_husl
|
115
|
-
|
118
|
+
c = RGB.new(r, g, b).to_xyz
|
119
|
+
l, u, v = c.luv_components(WHITE_POINT_D65)
|
120
|
+
h, s, l = Convert.luv_to_husl(l, u, v)
|
121
|
+
HUSL.new(h, s.to_r.clamp(0r, 1r), l.to_r.clamp(0r, 1r))
|
116
122
|
end
|
117
123
|
|
118
124
|
def to_xyz
|
119
|
-
XYZ.
|
125
|
+
XYZ.new(*Convert.rgb_to_xyz(r, g, b))
|
120
126
|
end
|
121
127
|
|
122
128
|
private def canonicalize(r, g, b)
|
data/lib/colors/rgba.rb
CHANGED
@@ -1,24 +1,30 @@
|
|
1
1
|
module Colors
|
2
2
|
class RGBA < RGB
|
3
3
|
def self.parse(hex_string)
|
4
|
-
|
4
|
+
error_message = "must be a hexadecimal string of " +
|
5
|
+
"`#rrggbbaa`, `#rgba`, `#rrggbb` or `#rgb` form"
|
6
|
+
unless hex_string.respond_to?(:to_str)
|
7
|
+
raise ArgumentError, "#{error_message}: #{hex_string.inspect}"
|
8
|
+
end
|
9
|
+
|
10
|
+
hex_string = hex_string.to_str
|
11
|
+
hexes = hex_string.match(/\A#(\h+)\z/) { $1 }
|
12
|
+
case hexes&.length
|
5
13
|
when 3 # rgb
|
6
|
-
r, g, b =
|
14
|
+
r, g, b = hexes.scan(/\h/).map {|h| h.hex * 17 }
|
7
15
|
new(r, g, b, 255)
|
8
16
|
when 6 # rrggbb
|
9
|
-
r, g, b =
|
17
|
+
r, g, b = hexes.scan(/\h{2}/).map(&:hex)
|
10
18
|
new(r, g, b, 255)
|
11
19
|
when 4 # rgba
|
12
|
-
r, g, b, a =
|
20
|
+
r, g, b, a = hexes.scan(/\h/).map {|h| h.hex * 17 }
|
13
21
|
new(r, g, b, a)
|
14
22
|
when 8 # rrggbbaa
|
15
|
-
r, g, b, a =
|
23
|
+
r, g, b, a = hexes.scan(/\h{2}/).map(&:hex)
|
16
24
|
new(r, g, b, a)
|
17
25
|
else
|
18
|
-
raise ArgumentError, "
|
26
|
+
raise ArgumentError, "#{error_message}: #{hex_string.inspect}"
|
19
27
|
end
|
20
|
-
rescue NoMethodError
|
21
|
-
raise ArgumentError, "hex_string must be a hexadecimal string of `#rrggbb` or `#rgb` form"
|
22
28
|
end
|
23
29
|
|
24
30
|
def initialize(r, g, b, a)
|
data/lib/colors/version.rb
CHANGED
data/lib/colors/xyy.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Colors
|
2
|
+
class XYY < AbstractColor
|
3
|
+
include Helper
|
4
|
+
|
5
|
+
def initialize(x, y, large_y)
|
6
|
+
@x, @y, @large_y = canonicalize(x, y, large_y)
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :x, :y, :large_y
|
10
|
+
|
11
|
+
def components
|
12
|
+
[x, y, large_y]
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
case other
|
17
|
+
when XYY
|
18
|
+
x == other.x && y == other.y && large_y == other.large_y
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_rgb
|
25
|
+
to_xyz.to_rgb
|
26
|
+
end
|
27
|
+
|
28
|
+
def rgb_components
|
29
|
+
to_xyz.rgb_components
|
30
|
+
end
|
31
|
+
|
32
|
+
def luv_components(wp)
|
33
|
+
to_xyz.luv_components(wp)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_xyz
|
37
|
+
large_x = large_y*x/y
|
38
|
+
large_z = large_y*(1 - x - y)/y
|
39
|
+
XYZ.new(large_x, large_y, large_z)
|
40
|
+
end
|
41
|
+
|
42
|
+
private def canonicalize(x, y, large_y)
|
43
|
+
[
|
44
|
+
Rational(x),
|
45
|
+
Rational(y),
|
46
|
+
Rational(large_y)
|
47
|
+
]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/colors/xyz.rb
CHANGED
@@ -1,20 +1,4 @@
|
|
1
|
-
require_relative "helper"
|
2
|
-
|
3
|
-
require "numo/narray"
|
4
|
-
|
5
1
|
module Colors
|
6
|
-
XYZ2RGB = Numo::DFloat[
|
7
|
-
[ 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 ],
|
8
|
-
[ -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 ],
|
9
|
-
[ 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 ]
|
10
|
-
]
|
11
|
-
|
12
|
-
RGB2XYZ = Numo::DFloat[
|
13
|
-
[ 0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751 ],
|
14
|
-
[ 0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500 ],
|
15
|
-
[ 0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086 ]
|
16
|
-
]
|
17
|
-
|
18
2
|
class XYZ < AbstractColor
|
19
3
|
include Helper
|
20
4
|
|
@@ -22,25 +6,6 @@ module Colors
|
|
22
6
|
|
23
7
|
KAPPA = (29/3)**3
|
24
8
|
|
25
|
-
def self.from_xyY(x, y, large_y)
|
26
|
-
large_x = large_y*x/y
|
27
|
-
large_z = large_y*(1 - x - y)/y
|
28
|
-
new(large_x, large_y, large_z)
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.from_rgb(r, g, b)
|
32
|
-
c = RGB2XYZ.dot(Numo::DFloat[to_linear(r), to_linear(g), to_linear(b)])
|
33
|
-
new(c[0], c[1], c[2])
|
34
|
-
end
|
35
|
-
|
36
|
-
private_class_method def self.to_linear(v)
|
37
|
-
if v > 0.04045
|
38
|
-
((v + 0.055r) / 1.055r) ** 2.4r
|
39
|
-
else
|
40
|
-
v / 12.92r
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
9
|
def initialize(x, y, z)
|
45
10
|
@x, @y, @z = canonicalize(x, y, z)
|
46
11
|
end
|
@@ -65,12 +30,7 @@ module Colors
|
|
65
30
|
end
|
66
31
|
|
67
32
|
def rgb_components
|
68
|
-
|
69
|
-
[
|
70
|
-
srgb_compand(c[0]).clamp(0r, 1r),
|
71
|
-
srgb_compand(c[1]).clamp(0r, 1r),
|
72
|
-
srgb_compand(c[2]).clamp(0r, 1r)
|
73
|
-
]
|
33
|
+
Convert.xyz_to_rgb(x, y, z)
|
74
34
|
end
|
75
35
|
|
76
36
|
def luv_components(wp)
|
@@ -99,19 +59,6 @@ module Colors
|
|
99
59
|
[u, v]
|
100
60
|
end
|
101
61
|
|
102
|
-
private def srgb_compand(v)
|
103
|
-
# the following is an optimization technique for `1.055*v**(1/2.4) - 0.055`.
|
104
|
-
# x^y ~= exp(y*log(x)) ~= exp2(y*log2(y)); the middle form is faster
|
105
|
-
#
|
106
|
-
# See https://github.com/JuliaGraphics/Colors.jl/issues/351#issuecomment-532073196
|
107
|
-
# for more detail benchmark in Julia language.
|
108
|
-
if v <= 0.0031308
|
109
|
-
12.92*v
|
110
|
-
else
|
111
|
-
1.055 * Math.exp(1/2.4 * Math.log(v)) - 0.055
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
62
|
private def canonicalize(x, y, z)
|
116
63
|
[
|
117
64
|
Rational(x),
|
data/red-colors.gemspec
CHANGED
data/test/test-husl.rb
CHANGED
@@ -175,44 +175,17 @@ class ColorsHUSLTest < Test::Unit::TestCase
|
|
175
175
|
end
|
176
176
|
|
177
177
|
test("#desaturate") do
|
178
|
-
c = Colors::
|
178
|
+
c = Colors::RGB.new(1r, 1r, 0r).to_husl.desaturate(0.8)
|
179
179
|
assert_instance_of(Colors::HUSL, c)
|
180
180
|
assert_near(Colors::HUSL.new(85.87432021817473r, 0.9838589961976354r, 0.8850923805142681r), c)
|
181
181
|
end
|
182
182
|
|
183
|
-
test("to_husl") do
|
183
|
+
test("#to_husl") do
|
184
184
|
black = Colors::HUSL.new(0, 0, 0)
|
185
185
|
assert_same(black, black.to_hsl)
|
186
186
|
end
|
187
187
|
|
188
|
-
test("
|
189
|
-
# black
|
190
|
-
assert_equal(Colors::HUSL.new(0, 0, 0),
|
191
|
-
Colors::HUSL.from_rgb(0, 0, 0))
|
192
|
-
# red
|
193
|
-
assert_near(Colors::HUSL.new(12.177050630061776r, 1r, 0.5323711559542933r),
|
194
|
-
Colors::HUSL.from_rgb(1r, 0, 0))
|
195
|
-
## yellow
|
196
|
-
assert_near(Colors::HUSL.new(85.87432021817473r, 1r, 0.9713855934179674r),
|
197
|
-
Colors::HUSL.from_rgb(1r, 1r, 0))
|
198
|
-
## green
|
199
|
-
assert_near(Colors::HUSL.new(127.71501294924047r, 1r, 0.8773551910965973r),
|
200
|
-
Colors::HUSL.from_rgb(0r, 1r, 0))
|
201
|
-
## cyan
|
202
|
-
assert_near(Colors::HUSL.new(192.17705063006116r, 1r, 0.9111475231670507r),
|
203
|
-
Colors::HUSL.from_rgb(0r, 1r, 1r))
|
204
|
-
## blue
|
205
|
-
assert_near(Colors::HUSL.new(265.8743202181779r, 1r, 0.3230087290398002r),
|
206
|
-
Colors::HUSL.from_rgb(0r, 0r, 1r))
|
207
|
-
## magenta
|
208
|
-
assert_near(Colors::HUSL.new(307.7150129492436r, 1r, 0.60322731354551294r),
|
209
|
-
Colors::HUSL.from_rgb(1r, 0r, 1r))
|
210
|
-
## white
|
211
|
-
assert_near(Colors::HUSL.new(0r, 0r, 1r),
|
212
|
-
Colors::HUSL.from_rgb(1r, 1r, 1r))
|
213
|
-
end
|
214
|
-
|
215
|
-
test("to_rgb") do
|
188
|
+
test("#to_rgb") do
|
216
189
|
# black
|
217
190
|
assert_equal(Colors::RGB.new(0, 0, 0),
|
218
191
|
Colors::HUSL.new(0, 0, 0).to_rgb)
|
data/test/test-rgb.rb
CHANGED
@@ -234,7 +234,7 @@ class ColorsRGBTest < Test::Unit::TestCase
|
|
234
234
|
Colors::RGB.new(0x33, 0x33, 0x33).to_hex_string)
|
235
235
|
end
|
236
236
|
|
237
|
-
test("to_rgb") do
|
237
|
+
test("#to_rgb") do
|
238
238
|
black = Colors::RGB.new(0, 0, 0)
|
239
239
|
assert_same(black, black.to_rgb)
|
240
240
|
end
|
@@ -291,4 +291,31 @@ class ColorsRGBTest < Test::Unit::TestCase
|
|
291
291
|
assert_equal(Colors::HSL.new(0r, 0r, 1r),
|
292
292
|
Colors::RGB.new(1r, 1r, 1r).to_hsl)
|
293
293
|
end
|
294
|
+
|
295
|
+
test("#to_husl") do
|
296
|
+
# black
|
297
|
+
assert_equal(Colors::HUSL.new(0, 0, 0),
|
298
|
+
Colors::RGB.new(0, 0, 0).to_husl)
|
299
|
+
# red
|
300
|
+
assert_near(Colors::HUSL.new(12.177050630061776r, 1r, 0.5323711559542933r),
|
301
|
+
Colors::RGB.new(1r, 0, 0).to_husl)
|
302
|
+
# yellow
|
303
|
+
assert_near(Colors::HUSL.new(85.87432021817473r, 1r, 0.9713855934179674r),
|
304
|
+
Colors::RGB.new(1r, 1r, 0).to_husl)
|
305
|
+
# green
|
306
|
+
assert_near(Colors::HUSL.new(127.71501294924047r, 1r, 0.8773551910965973r),
|
307
|
+
Colors::RGB.new(0r, 1r, 0).to_husl)
|
308
|
+
# cyan
|
309
|
+
assert_near(Colors::HUSL.new(192.17705063006116r, 1r, 0.9111475231670507r),
|
310
|
+
Colors::RGB.new(0r, 1r, 1r).to_husl)
|
311
|
+
# blue
|
312
|
+
assert_near(Colors::HUSL.new(265.8743202181779r, 1r, 0.3230087290398002r),
|
313
|
+
Colors::RGB.new(0r, 0r, 1r).to_husl)
|
314
|
+
# magenta
|
315
|
+
assert_near(Colors::HUSL.new(307.7150129492436r, 1r, 0.60322731354551294r),
|
316
|
+
Colors::RGB.new(1r, 0r, 1r).to_husl)
|
317
|
+
# white
|
318
|
+
assert_near(Colors::HUSL.new(0r, 0r, 1r),
|
319
|
+
Colors::RGB.new(1r, 1r, 1r).to_husl)
|
320
|
+
end
|
294
321
|
end
|
data/test/test-xyz.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class ColorsXYZTest < Test::Unit::TestCase
|
2
2
|
sub_test_case("#luv_components") do
|
3
3
|
test("on ITU-R BT.709 D65 white point") do
|
4
|
-
l, u, v = Colors::
|
4
|
+
l, u, v = Colors::RGB.new(0r, 0r, 0r).to_xyz.luv_components(Colors::WHITE_POINT_D65)
|
5
5
|
assert_in_delta(0, l)
|
6
6
|
assert_in_delta(0, u)
|
7
7
|
assert_in_delta(0, v)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: red-colors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenta Murata
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: numo-narray
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
83
|
description: ''
|
98
84
|
email:
|
99
85
|
- mrkn@mrkn.jp
|
@@ -110,6 +96,7 @@ files:
|
|
110
96
|
- lib/colors/abstract_color.rb
|
111
97
|
- lib/colors/alpha_component.rb
|
112
98
|
- lib/colors/color_data.rb
|
99
|
+
- lib/colors/convert.rb
|
113
100
|
- lib/colors/helper.rb
|
114
101
|
- lib/colors/hsl.rb
|
115
102
|
- lib/colors/hsla.rb
|
@@ -118,6 +105,7 @@ files:
|
|
118
105
|
- lib/colors/rgb.rb
|
119
106
|
- lib/colors/rgba.rb
|
120
107
|
- lib/colors/version.rb
|
108
|
+
- lib/colors/xyy.rb
|
121
109
|
- lib/colors/xyz.rb
|
122
110
|
- red-colors.gemspec
|
123
111
|
- test/helper.rb
|
@@ -148,18 +136,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
136
|
- !ruby/object:Gem::Version
|
149
137
|
version: '0'
|
150
138
|
requirements: []
|
151
|
-
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.7.6.2
|
152
141
|
signing_key:
|
153
142
|
specification_version: 4
|
154
143
|
summary: Red Colors provides a wide array of features for dealing with colors. This
|
155
144
|
includes conversion between colorspaces, desaturation, and parsing colors.
|
156
145
|
test_files:
|
157
|
-
- test/test-named-color.rb
|
158
|
-
- test/test-hsl.rb
|
159
|
-
- test/test-husl.rb
|
160
|
-
- test/test-xyz.rb
|
161
146
|
- test/test-hsla.rb
|
147
|
+
- test/test-named-color.rb
|
148
|
+
- test/test-rgba.rb
|
162
149
|
- test/helper.rb
|
163
150
|
- test/test-rgb.rb
|
164
|
-
- test/test-rgba.rb
|
165
151
|
- test/run.rb
|
152
|
+
- test/test-hsl.rb
|
153
|
+
- test/test-xyz.rb
|
154
|
+
- test/test-husl.rb
|