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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bace2ec96d092df5552c1d8f422bb8c641965bb896d4255842f633d2f0e0238
4
- data.tar.gz: d19df3fab59429a3175e2cecb5243b7dce42f3bec54bce55e12a6c26cfebdc61
3
+ metadata.gz: 3587e5433ccb431efbc46112034d3b9f00d40ad58e8389cb4081bf0bdcb3304f
4
+ data.tar.gz: 2568105af67abd20c5133a7181194489006eef94cc3c4834d0f3509eaf01c5f9
5
5
  SHA512:
6
- metadata.gz: 0f1619704538a7cc04ffee56c3eba67c2b0d4bef96f059e253435e93dbf4c1ec52cf76708a1b82e6ac9d09f2d2838610535948f1d82137ca6fa49cafc6180dd6
7
- data.tar.gz: 3bd2f146e561b3583a4abc160af383f335b8d35e12d829dc467332eb2d1753ec6e8b7523ae49c8778d1a4bf594e709509d30f6d732582cbf864d1ac72816b13e
6
+ metadata.gz: d15ae1556244a040ff0822fc739e7dd561d2f29fc9cec04ba68bbc72a1bd6afe097c66fa88f5450bd7f7bce3e89622b34726bdc5e64320255cb6ba71a0c508f7
7
+ data.tar.gz: aa96cd5773128f79d48993319d920e7325b4db7520429eb211347abfb375688566b083d049e1e679aa81ca05f545682490d70f2b29beaedcbc6ea1c094c45c50
data/README.md CHANGED
@@ -20,6 +20,7 @@ $ gem install red-colors
20
20
  - HSL
21
21
  - HSLA
22
22
  - HUSL
23
+ - xyY
23
24
  - XYZ
24
25
 
25
26
  ## Usage
@@ -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::XYZ.from_xyY(0.3127r, 0.3290r, 1r)
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
@@ -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
- l, u, v = convert_lch_to_luv(*lch_components)
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 = self.class.max_chroma(l, h)
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
@@ -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
- if cache.has_key?(name)
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(color)
26
- orig_color = color
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
- color = color.to_s
24
+ name = name.to_s
34
25
  else
35
- color = color.to_str
26
+ name = name.to_str
36
27
  end
37
- color = @mapping.fetch(color, color)
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
@@ -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
- case hex_string.to_str.match(/\A#(\h+)\z/) { $1 }.length
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 = hex_string.scan(/\h/).map {|h| h.hex * 17 }
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 = hex_string.scan(/\h{2}/).map(&:hex)
18
+ r, g, b = hexes.scan(/\h{2}/).map(&:hex)
14
19
  new(r, g, b)
15
20
  else
16
- raise ArgumentError, "Invalid hex string: #{hex_string.inspect}"
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
- HUSL.from_rgb(r, g, b)
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.from_rgb(r, g, b)
125
+ XYZ.new(*Convert.rgb_to_xyz(r, g, b))
120
126
  end
121
127
 
122
128
  private def canonicalize(r, g, b)
@@ -1,24 +1,30 @@
1
1
  module Colors
2
2
  class RGBA < RGB
3
3
  def self.parse(hex_string)
4
- case hex_string.to_str.match(/\A#(\h+)\z/) { $1 }.length
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 = hex_string.scan(/\h/).map {|h| h.hex * 17 }
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 = hex_string.scan(/\h{2}/).map(&:hex)
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 = hex_string.scan(/\h/).map {|h| h.hex * 17 }
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 = hex_string.scan(/\h{2}/).map(&:hex)
23
+ r, g, b, a = hexes.scan(/\h{2}/).map(&:hex)
16
24
  new(r, g, b, a)
17
25
  else
18
- raise ArgumentError, "Invalid hex string: #{hex_string.inspect}"
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)
@@ -1,3 +1,3 @@
1
1
  module Colors
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -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
@@ -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
- c = XYZ2RGB.dot(Numo::DFloat[x, y, z])
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),
@@ -37,5 +37,4 @@ Gem::Specification.new do |spec|
37
37
  spec.add_development_dependency("test-unit")
38
38
  spec.add_development_dependency("yard")
39
39
  spec.add_development_dependency("kramdown")
40
- spec.add_development_dependency("numo-narray")
41
40
  end
@@ -175,44 +175,17 @@ class ColorsHUSLTest < Test::Unit::TestCase
175
175
  end
176
176
 
177
177
  test("#desaturate") do
178
- c = Colors::HUSL.from_rgb(1r, 1r, 0r).desaturate(0.8)
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(".from_rgb") do
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)
@@ -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
@@ -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::XYZ.from_rgb(0r, 0r, 0r).luv_components(Colors::WHITE_POINT_D65)
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.0
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-10-16 00:00:00.000000000 Z
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
- rubygems_version: 3.0.6
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