red-colors 0.1.0 → 0.3.0

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