red-colors 0.1.0 → 0.3.0

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -0
  3. data/data/colormaps/cividis.json +258 -0
  4. data/data/colormaps/coolwarm.json +107 -0
  5. data/data/colormaps/crest.json +258 -0
  6. data/data/colormaps/flare.json +258 -0
  7. data/data/colormaps/gist_earth.json +49 -0
  8. data/data/colormaps/gist_ncar.json +55 -0
  9. data/data/colormaps/icefire.json +258 -0
  10. data/data/colormaps/inferno.json +258 -0
  11. data/data/colormaps/magma.json +258 -0
  12. data/data/colormaps/mako.json +258 -0
  13. data/data/colormaps/nipy_spectral.json +71 -0
  14. data/data/colormaps/pink.json +200 -0
  15. data/data/colormaps/plasma.json +258 -0
  16. data/data/colormaps/rocket.json +258 -0
  17. data/data/colormaps/turbo.json +258 -0
  18. data/data/colormaps/twilight.json +512 -0
  19. data/data/colormaps/viridis.json +258 -0
  20. data/data/colormaps/vlag.json +258 -0
  21. data/lib/colors.rb +17 -5
  22. data/lib/colors/abstract_color.rb +4 -0
  23. data/lib/colors/colormap.rb +143 -0
  24. data/lib/colors/colormap_data.rb +44 -0
  25. data/lib/colors/colormap_data/matplotlib_builtin.rb +990 -0
  26. data/lib/colors/colormap_data/seaborn_builtin.rb +10 -0
  27. data/lib/colors/colormap_registry.rb +62 -0
  28. data/lib/colors/convert.rb +269 -0
  29. data/lib/colors/helper.rb +2 -1
  30. data/lib/colors/husl.rb +7 -100
  31. data/lib/colors/linear_segmented_colormap.rb +137 -0
  32. data/lib/colors/listed_colormap.rb +45 -0
  33. data/lib/colors/named_colors.rb +10 -20
  34. data/lib/colors/rgb.rb +20 -10
  35. data/lib/colors/rgba.rb +14 -8
  36. data/lib/colors/utils.rb +18 -0
  37. data/lib/colors/version.rb +1 -1
  38. data/lib/colors/xterm256.rb +56 -0
  39. data/lib/colors/xyy.rb +48 -0
  40. data/lib/colors/xyz.rb +2 -55
  41. data/red-colors.gemspec +3 -1
  42. data/test/test-husl.rb +45 -53
  43. data/test/test-linear-segmented-colormap.rb +138 -0
  44. data/test/test-listed-colormap.rb +134 -0
  45. data/test/test-rgb.rb +76 -1
  46. data/test/test-xterm256.rb +76 -0
  47. data/test/test-xyz.rb +1 -1
  48. metadata +50 -15
data/lib/colors/xyz.rb CHANGED
@@ -1,45 +1,10 @@
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
 
21
5
  EPSILON = (6/29r)**3
22
6
 
23
- KAPPA = (29/3)**3
24
-
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
7
+ KAPPA = (29/3r)**3
43
8
 
44
9
  def initialize(x, y, z)
45
10
  @x, @y, @z = canonicalize(x, y, z)
@@ -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),
data/red-colors.gemspec CHANGED
@@ -27,15 +27,17 @@ Gem::Specification.new do |spec|
27
27
  "#{spec.name}.gemspec",
28
28
  ]
29
29
  spec.files += [".yardopts"]
30
+ spec.files += Dir.glob("data/colormaps/*.json")
30
31
  spec.files += Dir.glob("lib/**/*.rb")
31
32
  spec.files += Dir.glob("image/*.*")
32
33
  spec.files += Dir.glob("doc/text/*")
33
34
  spec.test_files += Dir.glob("test/**/*")
34
35
 
36
+ spec.add_runtime_dependency("matrix")
37
+
35
38
  spec.add_development_dependency("bundler")
36
39
  spec.add_development_dependency("rake")
37
40
  spec.add_development_dependency("test-unit")
38
41
  spec.add_development_dependency("yard")
39
42
  spec.add_development_dependency("kramdown")
40
- spec.add_development_dependency("numo-narray")
41
43
  end
data/test/test-husl.rb CHANGED
@@ -175,67 +175,59 @@ 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
- assert_near(Colors::HUSL.new(85.87432021817473r, 0.9838589961976354r, 0.8850923805142681r), c)
180
+ assert_near(Colors::HUSL.new(85.87432021817473r, 0.9838589961976354r, 0.8850923805142681r), c, 0.01)
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))
188
+ sub_test_case("basic colors") do
189
+ colors = [
190
+ { rgb: "#ff0000", husl: [ 12.16, 1.0, 0.532], name: "red" },
191
+ { rgb: "#ffff00", husl: [ 85.90, 1.0, 0.971], name: "yellow" },
192
+ { rgb: "#00ff00", husl: [127.72, 1.0, 0.877], name: "green" },
193
+ { rgb: "#00ffff", husl: [192.20, 1.0, 0.911], name: "cyan" },
194
+ { rgb: "#0000ff", husl: [265.87, 1.0, 0.323], name: "blue" },
195
+ { rgb: "#ff00ff", husl: [307.71, 1.0, 0.603], name: "magenta" },
196
+ ]
197
+ data(colors.map {|r| [r[:name], r] }.to_h)
198
+ def test_husl_to_rgb(data)
199
+ husl, rgb = data.values_at(:husl, :rgb)
200
+ assert_equal(rgb,
201
+ Colors::HUSL.new(*husl).to_rgb.to_hex_string)
202
+ end
213
203
  end
214
204
 
215
- test("to_rgb") do
216
- # black
217
- assert_equal(Colors::RGB.new(0, 0, 0),
218
- Colors::HUSL.new(0, 0, 0).to_rgb)
219
- # red
220
- assert_near(Colors::RGB.new(1r, 0, 0),
221
- Colors::HUSL.new(12.177050630061776r, 1r, 0.5323711559542933r).to_rgb)
222
- # yellow
223
- assert_near(Colors::RGB.new(1r, 1r, 0),
224
- Colors::HUSL.new(85.87432021817473r, 1r, 0.9713855934179674r).to_rgb)
225
- # green
226
- assert_near(Colors::RGB.new(0r, 1r, 0),
227
- Colors::HUSL.new(127.71501294924047r, 1r, 0.8773551910965973r).to_rgb)
228
- # cyan
229
- assert_near(Colors::RGB.new(0r, 1r, 1r),
230
- Colors::HUSL.new(192.17705063006116r, 1r, 0.9111475231670507r).to_rgb)
231
- # blue
232
- assert_near(Colors::RGB.new(0r, 0r, 1r),
233
- Colors::HUSL.new(265.8743202181779r, 1r, 0.3230087290398002r).to_rgb)
234
- # magenta
235
- assert_near(Colors::RGB.new(1r, 0r, 1r),
236
- Colors::HUSL.new(307.7150129492436r, 1r, 0.60322731354551294r).to_rgb)
237
- # white
238
- assert_near(Colors::RGB.new(1r, 1r, 1r),
239
- Colors::HUSL.new(0r, 1r, 1r).to_rgb)
205
+ sub_test_case("reproducing color conversion in seaborn") do
206
+ s = 0.9 * 99/100r
207
+ l = 0.65 * 99/100r
208
+ palette = [
209
+ { rgb: "#f77189", husl: [ 3.590, s, l] },
210
+ { rgb: "#e68332", husl: [ 33.50666666666666, s, l] },
211
+ { rgb: "#bb9832", husl: [ 63.42333333333333, s, l] },
212
+ { rgb: "#97a431", husl: [ 93.3400, s, l] },
213
+ { rgb: "#50b131", husl: [123.25666666666666, s, l] },
214
+ { rgb: "#34af84", husl: [153.17333333333332, s, l] },
215
+ { rgb: "#36ada4", husl: [183.0900, s, l] },
216
+ { rgb: "#38aabf", husl: [213.00666666666663, s, l] },
217
+ { rgb: "#3ba3ec", husl: [242.92333333333332, s, l] },
218
+ { rgb: "#a48cf4", husl: [272.8400, s, l] },
219
+ { rgb: "#e867f4", husl: [302.75666666666666, s, l] }, # TODO: #e866f4 is the best
220
+ { rgb: "#f668c2", husl: [332.67333333333335, s, l] }
221
+ ]
222
+ data(palette.map {|r|
223
+ name = r[:name] || "husl(#{r[:husl]}) => #{r[:rgb]}"
224
+ [name, r]
225
+ }.to_h)
226
+ def test_husl_to_rgb(data)
227
+ husl_components, hex_string = data.values_at(:husl, :rgb)
228
+ husl = Colors::HUSL.new(*husl_components)
229
+ assert_equal(hex_string,
230
+ husl.to_rgb.to_hex_string)
231
+ end
240
232
  end
241
233
  end
@@ -0,0 +1,138 @@
1
+ class ColorsLinearSegmentedColormapTest < Test::Unit::TestCase
2
+ include TestHelper
3
+
4
+ sub_test_case("#[]") do
5
+ def setup
6
+ @cm = Colors::LinearSegmentedColormap.new_from_list(:triple_colors, [:red, :green, :blue], n_colors: 11)
7
+ @cm.under_color = :black
8
+ @cm.over_color = :white
9
+ end
10
+
11
+ sub_test_case("with an integer") do
12
+ data do
13
+ expected = [
14
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
15
+ Colors::RGBA.new(0r, 128/255r, 0r, 1r),
16
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
17
+ Colors::RGBA.new(1r, 1r, 1r, 1r),
18
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
19
+ ]
20
+ indices = [0, 5, 10, 11, -1]
21
+ indices.zip(expected).map { |i, c|
22
+ ["cmap[#{i}]", {i: i, expected_color: c}]
23
+ }.to_h
24
+ end
25
+ def test_aref(data)
26
+ i, expected = data.values_at(:i, :expected_color)
27
+ assert_near(expected, @cm[i])
28
+ end
29
+ end
30
+
31
+ sub_test_case("with an array of integers") do
32
+ def test_aref
33
+ indices = [0, 5, 10, 11, -1]
34
+ assert_equal(indices.map {|i| @cm[i] },
35
+ @cm[indices])
36
+ end
37
+ end
38
+
39
+ sub_test_case("with a float") do
40
+ data do
41
+ expected = [
42
+ Colors::RGBA.new(0.6r, 0.20078431372549022r, 0.0r, 1r),
43
+ Colors::RGBA.new(0.19999999999999996r, 0.40156862745098043r, 0.0r, 1r),
44
+ Colors::RGBA.new(0.0r, 0.4015686274509803r, 0.20000000000000018r, 1r),
45
+ Colors::RGBA.new(0.0r, 0.20078431372549022r, 0.6r, 1r),
46
+ Colors::RGBA.new(1r, 1r, 1r, 1r),
47
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
48
+ ]
49
+ indices = [0.2, 0.4, 0.6, 0.8, 1.2, -0.2]
50
+ indices.zip(expected).map { |i, c|
51
+ ["cmap[#{i}]", {i: i, expected_color: c}]
52
+ }.to_h
53
+ end
54
+ def test_aref(data)
55
+ i, expected = data.values_at(:i, :expected_color)
56
+ assert_near(expected, @cm[i])
57
+ end
58
+ end
59
+
60
+ sub_test_case("with an array of floats") do
61
+ def test_aref
62
+ indices = [0.2, 0.4, 0.6, 0.8, 1.2, -0.2]
63
+ assert_equal(indices.map {|i| @cm[i] },
64
+ @cm[indices])
65
+ end
66
+ end
67
+ end
68
+
69
+ def test_over_color
70
+ cm = Colors::LinearSegmentedColormap.new_from_list(:blue_and_red, [:blue, :red])
71
+
72
+ before = cm[[0, 255, 256]]
73
+ cm.over_color = :black
74
+ after = cm[[0, 255, 256]]
75
+
76
+ assert_equal([
77
+ [
78
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
79
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
80
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
81
+ ],
82
+ [
83
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
84
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
85
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
86
+ ]
87
+ ],
88
+ [
89
+ before,
90
+ after
91
+ ])
92
+ end
93
+
94
+ def test_under_color
95
+ cm = Colors::LinearSegmentedColormap.new_from_list(:blue_and_red, [:blue, :red])
96
+
97
+ before = cm[[0, 255, -1]]
98
+ cm.under_color = :black
99
+ after = cm[[0, 255, -1]]
100
+
101
+ assert_equal([
102
+ [
103
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
104
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
105
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
106
+ ],
107
+ [
108
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
109
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
110
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
111
+ ]
112
+ ],
113
+ [
114
+ before,
115
+ after
116
+ ])
117
+ end
118
+
119
+ def test_reversed
120
+ cm = Colors::LinearSegmentedColormap.new_from_list("four", [:red, :magenta, :green, :yellow, :blue], n_colors: 5)
121
+ cm.under_color = :black
122
+ cm.over_color = :white
123
+ rev = cm.reversed
124
+ assert_equal([
125
+ "four_r",
126
+ cm[[0, 1, 2, 3, 4]],
127
+ Colors[:black],
128
+ Colors[:white]
129
+ ],
130
+ [
131
+ rev.name,
132
+ rev[[4, 3, 2, 1, 0]],
133
+ rev.under_color,
134
+ rev.over_color
135
+ ])
136
+ end
137
+ end
138
+
@@ -0,0 +1,134 @@
1
+ class ColorsListedColormapTest < Test::Unit::TestCase
2
+ include TestHelper
3
+
4
+ sub_test_case("#[]") do
5
+ def setup
6
+ @cm = Colors::ListedColormap.new([:red, :green, :blue])
7
+ @cm.under_color = :black
8
+ @cm.over_color = :white
9
+ end
10
+
11
+ sub_test_case("with an integer") do
12
+ def test_aref
13
+ assert_equal([
14
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
15
+ Colors::RGBA.new(0r, 128/255r, 0r, 1r),
16
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
17
+ Colors::RGBA.new(1r, 1r, 1r, 1r),
18
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
19
+ ],
20
+ [
21
+ @cm[0],
22
+ @cm[1],
23
+ @cm[2],
24
+ @cm[3],
25
+ @cm[-1],
26
+ ])
27
+ end
28
+ end
29
+
30
+ sub_test_case("with an array of integers") do
31
+ def test_aref
32
+ indices = [0, 1, 2, 3, -1]
33
+ assert_equal(indices.map {|i| @cm[i] },
34
+ @cm[indices])
35
+ end
36
+ end
37
+
38
+ sub_test_case("with a float") do
39
+ def test_aref
40
+ assert_equal([
41
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
42
+ Colors::RGBA.new(0r, 128/255r, 0r, 1r),
43
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
44
+ Colors::RGBA.new(1r, 1r, 1r, 1r),
45
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
46
+ ],
47
+ [
48
+ @cm[0.1],
49
+ @cm[0.5],
50
+ @cm[0.8],
51
+ @cm[1.1],
52
+ @cm[-0.1]
53
+ ])
54
+ end
55
+ end
56
+
57
+ sub_test_case("with an array of floats") do
58
+ def test_aref
59
+ indices = [0.1, 0.5, 0.8, 1.1, -0.1]
60
+ assert_equal(indices.map {|i| @cm[i] },
61
+ @cm[indices])
62
+ end
63
+ end
64
+ end
65
+
66
+ def test_over_color
67
+ cm = Colors::ListedColormap.new([:blue, :red])
68
+
69
+ before = cm[[0, 1, 2]]
70
+ cm.over_color = :black
71
+ after = cm[[0, 1, 2]]
72
+
73
+ assert_equal([
74
+ [
75
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
76
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
77
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
78
+ ],
79
+ [
80
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
81
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
82
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
83
+ ]
84
+ ],
85
+ [
86
+ before,
87
+ after
88
+ ])
89
+ end
90
+
91
+ def test_under_color
92
+ cm = Colors::ListedColormap.new([:blue, :red])
93
+
94
+ before = cm[[0, 1, -1]]
95
+ cm.under_color = :black
96
+ after = cm[[0, 1, -1]]
97
+
98
+ assert_equal([
99
+ [
100
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
101
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
102
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
103
+ ],
104
+ [
105
+ Colors::RGBA.new(0r, 0r, 1r, 1r),
106
+ Colors::RGBA.new(1r, 0r, 0r, 1r),
107
+ Colors::RGBA.new(0r, 0r, 0r, 1r),
108
+ ]
109
+ ],
110
+ [
111
+ before,
112
+ after
113
+ ])
114
+ end
115
+
116
+ def test_reversed
117
+ cm = Colors::ListedColormap.new([:red, :green, :blue], name: "three")
118
+ cm.under_color = :orange
119
+ cm.over_color = :yellow
120
+ rev = cm.reversed
121
+ assert_equal([
122
+ "three_r",
123
+ cm[[0, 1, 2]],
124
+ Colors[:orange],
125
+ Colors[:yellow]
126
+ ],
127
+ [
128
+ rev.name,
129
+ rev[[2, 1, 0]],
130
+ rev.under_color,
131
+ rev.over_color
132
+ ])
133
+ end
134
+ end