red-colors 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|