red-colors 0.3.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41b270524325cebd81989dc494c23c3204517e6416d12c860c3bf678004fa0e6
4
- data.tar.gz: 605e7d7c3388fb95231e3fe24e3435799a631d8f8c2a4852741a449942ce7be7
3
+ metadata.gz: 5dba93ba16e08a29d6ffc1a28e03bcb63ddd4f4cb8060185917af95070468e43
4
+ data.tar.gz: 7f0a0d935d4994d7429768101a3d932221ae3796e75582e3fd2289cf5b919fcf
5
5
  SHA512:
6
- metadata.gz: 28bad73970d8198db20f0605b0766b3aba050a7487380784539a36143daa3b4bbe351776adf6497439648bc37336fce104eda9de40ab10be3d42dc720cfb63d7
7
- data.tar.gz: 6dbc678e7e3f8fa386bc1b5cd0adc941d3636b8a4c44f570f2eab919f41e966fafafb7cbdcbf347e379e7527fdd60923da0d7984764e48fff59df42f80d01d9b
6
+ metadata.gz: 71368b0caaf783ecfe72208e7a26c54448e4df7c743c99bde5e8a642aa1dcae47758bfbfcaf230e4ec3a1c7275ed249945173efabb42b2f96cd0ccd6eca6fe55
7
+ data.tar.gz: d2b262b65455e9f5dc77ea65cb7d38ec9d79c9a811df00229fa2de1d62e1987ed687503d16878f2b47e53e18a1a88653f7f04710f684d609783f0a086fa13f96
data/Rakefile CHANGED
@@ -17,3 +17,13 @@ task :test do
17
17
  end
18
18
 
19
19
  task default: :test
20
+
21
+ release_task = Rake.application["release"]
22
+ # We use Trusting Publishing
23
+ release_task.prerequisites.delete("build")
24
+ release_task.prerequisites.delete("release:rubygem_push")
25
+ release_task_comment = release_task.comment
26
+ if release_task_comment
27
+ release_task.clear_comments
28
+ release_task.comment = release_task_comment.gsub(/ and build.*$/, "")
29
+ end
data/doc/text/news.md ADDED
@@ -0,0 +1,29 @@
1
+ # News
2
+
3
+ ## 0.5.0 - 2026-09-09
4
+
5
+ ### Improvements
6
+
7
+ * Added support for json 3.0.0 or later.
8
+
9
+ ## 0.4.0 - 2024-02-18
10
+
11
+ ### Improvements
12
+
13
+ * Added support for CSS3 syntax.
14
+ * GH-5
15
+ * GH-12
16
+ * Patch by Ben Bowers.
17
+
18
+ * Suppressed warnings.
19
+ * GH-16
20
+ * Reported by rubyFeedback.
21
+
22
+ ### Fixes
23
+
24
+ * Fixed wrong `Colors::Xterm256#to_grey_level` conversion.
25
+
26
+ ### Thanks
27
+
28
+ * Ben Bowers
29
+ * rubyFeedback
@@ -956,7 +956,7 @@ module Colors
956
956
  a_data = data[a_key]
957
957
  b_data = data[b_key]
958
958
  a_listed = a_data.is_a?(Hash) && a_data.key?(:listed)
959
- b_listed = a_data.is_a?(Hash) && a_data.key?(:listed)
959
+ b_listed = b_data.is_a?(Hash) && b_data.key?(:listed)
960
960
  case
961
961
  when !a_listed && b_listed
962
962
  -1
@@ -12,7 +12,7 @@ module Colors
12
12
  def self.load_colormap_data(name)
13
13
  path = @colormaps_dir.join("#{name}.json")
14
14
  json = File.read(path)
15
- JSON.load(json, nil, symbolize_names: true, create_additions: false)
15
+ JSON.parse(json, symbolize_names: true)
16
16
  end
17
17
 
18
18
  def self.register_listed_colormap(name, data=nil)
@@ -30,7 +30,6 @@ module Colors
30
30
  end
31
31
 
32
32
  if @registry.key?(name)
33
- existing = @registry[name]
34
33
  if BUILTIN_COLORMAPS.key?(name)
35
34
  unless override_builtin
36
35
  raise ArgumentError,
@@ -150,6 +150,12 @@ module Colors
150
150
  dot_product(RGB2XYZ, srgb_to_linear_srgb(r, g, b))
151
151
  end
152
152
 
153
+ def rgb_to_greyscale(r, g, b)
154
+ r, g, b = srgb_to_linear_srgb(r, g, b)
155
+ a = RGB2XYZ[1]
156
+ (a[0]*r + a[1]*g + a[2]*b).clamp(0, 1)
157
+ end
158
+
153
159
  def rgb_to_xterm256(r, g, b)
154
160
  i = closest_xterm256_rgb_index(r)
155
161
  j = closest_xterm256_rgb_index(g)
data/lib/colors/css.rb ADDED
@@ -0,0 +1,63 @@
1
+ module Colors
2
+ module CSS
3
+ # Factory method for generating RGB/RGBA/HSL/HSLA Objects.
4
+ # Parsing based on spec https://www.w3.org/TR/css-color-3 ; section 4.2
5
+ def self.parse(css_string)
6
+ error_message = "must be a string of `rgb(rr,gg,bb)`, `rgba(rr,gg,bb,aa)`, `hsl(hh,ss,ll)`, or `hsla(hh,ss,ll,aa)` form"
7
+ unless css_string.respond_to?(:to_str)
8
+ raise ArgumentError, "#{error_message}: #{css_string.inspect}"
9
+ end
10
+
11
+ css_string = css_string.to_str.strip
12
+
13
+ matcher = /\A((?:rgb|hsl)a?)\(([^)]+)\)\z/.match(css_string)
14
+ unless matcher
15
+ raise ArgumentError, "#{error_message}: #{css_string.inspect}"
16
+ end
17
+
18
+ color_space, args_string = matcher[1..2]
19
+ args = args_string.strip.split(/\s*,\s*/)
20
+ has_alpha = color_space.end_with?("a") # rgba/hsla
21
+ if has_alpha && args.length != 4
22
+ raise ArgumentError, "Expecting 4 fields for #{color_space}(): #{css_string.inspect}"
23
+ elsif !has_alpha && args.length != 3
24
+ raise ArgumentError, "Expecting 3 fields for #{color_space}(): #{css_string.inspect}"
25
+ end
26
+
27
+ case color_space
28
+ when "rgb", "rgba"
29
+ rgb, alpha = args[0, 3], args[3]
30
+ num_percent_vals = rgb.count {|v| v.end_with?("%") }
31
+ # CSS3 allows RGB values to be specified with all 3 as a percentage "##%"
32
+ # or all as integer values without '%' sign.
33
+ if num_percent_vals.zero?
34
+ # interpret as integer values in range of 0..255
35
+ r, g, b = rgb.map {|strval| strval.to_i.clamp(0, 255) }
36
+ # Note, RGBA.new expects all values to be integers or floats.
37
+ # For this case, we also turn the alpha-value into an int range 0..255
38
+ # to match the r,g,b values.
39
+ a = has_alpha && (alpha.to_r.clamp(0r, 1r) * 255).to_i
40
+ elsif num_percent_vals == 3
41
+ r, g, b = rgb.map {|strval| (strval.to_r / 100).clamp(0r, 1r) }
42
+ a = has_alpha && alpha.to_r.clamp(0r, 1r)
43
+ else
44
+ raise ArgumentError, "Invalid mix of percent and integer values: #{css_string.inspect}"
45
+ end
46
+ return has_alpha ? RGBA.new(r, g, b, a) : RGB.new(r, g, b)
47
+ when "hsl", "hsla"
48
+ hue, sat, light, alpha = *args
49
+ # CSS3 Hue values are an angle, unclear if we should convert to Integer or Rational here.
50
+ h = hue.to_r
51
+ s, l = [sat, light].map {|strval| (strval.to_r / 100).clamp(0r, 1r) }
52
+ if has_alpha
53
+ a = alpha.to_r.clamp(0r, 1r)
54
+ return HSLA.new(h, s, l, a)
55
+ else
56
+ return HSL.new(h, s, l)
57
+ end
58
+ else
59
+ raise ArgumentError, "Unknown color space: #{css_string.inspect}"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -80,8 +80,6 @@ module Colors
80
80
  raise ArgumentError, "data array must consist of 3-length arrays"
81
81
  end
82
82
 
83
- shape = [ary.length, 3]
84
-
85
83
  x, y0, y1 = ary.transpose
86
84
 
87
85
  if x[0] != 0.0 || x[-1] != 1.0
@@ -1,3 +1,3 @@
1
1
  module Colors
2
- VERSION = "0.3.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -42,7 +42,8 @@ module Colors
42
42
  def to_grey_level
43
43
  if code < 232
44
44
  r, g, b = to_rgb_components
45
- x, y, z = Convet.rgb_to_xyz(r, g, b)
45
+ y = Convert.rgb_to_greyscale(r, g, b)
46
+ canonicalize_component_to_rational(y, :grey)
46
47
  else
47
48
  grey = 10*(code - 232) + 8
48
49
  canonicalize_component_from_integer(grey, :grey)
data/lib/colors.rb CHANGED
@@ -4,6 +4,7 @@ require_relative "colors/convert"
4
4
  require_relative "colors/helper"
5
5
 
6
6
  require_relative "colors/abstract_color"
7
+ require_relative "colors/css"
7
8
  require_relative "colors/hsl"
8
9
  require_relative "colors/hsla"
9
10
  require_relative "colors/husl"
data/red-colors.gemspec CHANGED
@@ -33,6 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.files += Dir.glob("doc/text/*")
34
34
  spec.test_files += Dir.glob("test/**/*")
35
35
 
36
+ spec.add_runtime_dependency("json")
36
37
  spec.add_runtime_dependency("matrix")
37
38
 
38
39
  spec.add_development_dependency("bundler")
data/test/run.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # TODO
4
- # $VERBOSE = true
3
+ $VERBOSE = true
5
4
 
6
5
  require "pathname"
7
6
 
data/test/test-css.rb ADDED
@@ -0,0 +1,174 @@
1
+ class ColorsCSSParserTest < Test::Unit::TestCase
2
+ include TestHelper
3
+
4
+ sub_test_case("rgb") do
5
+ test("simple in-range integer values") do
6
+ ref = Colors::RGB.new(1, 0, 255)
7
+ css = Colors::CSS.parse("rgb(1,0,255)")
8
+ assert_near(ref, css)
9
+ end
10
+
11
+ test("clamps out-of-range integer values") do
12
+ ref = Colors::RGB.new(255, 0, 0)
13
+ css = Colors::CSS.parse("rgb( 300 ,0,0)")
14
+ assert_near(ref, css)
15
+ css = Colors::CSS.parse("rgb( 255 , -10,0)")
16
+ assert_near(ref, css)
17
+ end
18
+
19
+ test("in-range percent values") do
20
+ ref = Colors::RGB.new(0, 0.55, 1)
21
+ css = Colors::CSS.parse("rgb(0%,55%,100%)")
22
+ assert_near(ref, css)
23
+ end
24
+
25
+ test("clamps out-of-range percent values") do
26
+ ref = Colors::RGB.new(0r, 0r, 1r)
27
+ css = Colors::CSS.parse("rgb(0%,0%,110%)")
28
+ assert_near(ref, css)
29
+ css = Colors::CSS.parse("rgb(-10%,0%,100%)")
30
+ assert_near(ref, css)
31
+ end
32
+
33
+ test("bad input") do
34
+ assert_raises ArgumentError, "mixed pct/int" do
35
+ Colors::CSS.parse("rgb(50%,0 ,0)")
36
+ end
37
+ assert_raises ArgumentError, "too few args" do
38
+ Colors::CSS.parse("rgb(50%,0%)")
39
+ end
40
+ assert_raises ArgumentError, "too many args" do
41
+ Colors::CSS.parse("rgb(50%,0%,6%,0)")
42
+ end
43
+ assert_raises ArgumentError, "missing comma" do
44
+ Colors::CSS.parse("rgb(50% 0% 6%)")
45
+ end
46
+ end
47
+ end
48
+
49
+ sub_test_case("rgba") do
50
+ test("integer values, int alpha") do
51
+ ref = Colors::RGBA.new(1r, 0r, 1r, 1r)
52
+ css = Colors::CSS.parse("rgba(255,0,255,1)")
53
+ assert_near(ref, css)
54
+ ref = Colors::RGBA.new(1r, 0r, 1r, 0r)
55
+ css = Colors::CSS.parse("rgba(255,0,255,0)")
56
+ assert_near(ref, css)
57
+ end
58
+
59
+ test("integer values, float alpha") do
60
+ ref = Colors::RGBA.new(255, 0, 255, 127)
61
+ css = Colors::CSS.parse("rgba(255,0,255 , 0.5)")
62
+ assert_near(ref, css)
63
+ end
64
+
65
+ test("clamp out of range alpha values") do
66
+ ref = Colors::RGBA.new(1r, 0, 1r, 1r)
67
+ css = Colors::CSS.parse("rgba(255,0,255,2.0)")
68
+ assert_near(ref, css)
69
+ ref = Colors::RGBA.new(1r, 127/255r, 123/255r, 0.0)
70
+ css = Colors::CSS.parse("rgba(255, 127, 123,-0.1)")
71
+ assert_near(ref, css)
72
+ end
73
+
74
+ test("percent values, int alpha") do
75
+ ref = Colors::RGBA.new(1r, 0r, 0.25r, 1r)
76
+ css = Colors::CSS.parse("rgba(100%,0%,25%,1)")
77
+ assert_near(ref, css)
78
+ ref = Colors::RGBA.new(1r, 0.33r, 0.02r, 0r)
79
+ css = Colors::CSS.parse("rgba(100% , 33%,2%,0)")
80
+ assert_near(ref, css)
81
+ end
82
+
83
+ test("percent values, float alpha") do
84
+ ref = Colors::RGBA.new(0.5r, 0, 0.06, 1/2r)
85
+ css = Colors::CSS.parse("rgba(50%,0%,6% ,0.5)")
86
+ assert_near(ref, css)
87
+ end
88
+
89
+ test("bad input") do
90
+ assert_raises ArgumentError, "mixed pct/int" do
91
+ Colors::CSS.parse("rgba(50%,0,6 ,0.5)")
92
+ end
93
+ assert_raises ArgumentError, "too few args" do
94
+ Colors::CSS.parse("rgba(50%,0%,6%)")
95
+ end
96
+ assert_raises ArgumentError, "too many args" do
97
+ Colors::CSS.parse("rgba(50%,0%,6%,0.5, 0.5)")
98
+ end
99
+ assert_raises ArgumentError, "missing comma" do
100
+ Colors::CSS.parse("rgba(50%,0%,6% 0.5)")
101
+ end
102
+ end
103
+ end
104
+
105
+ sub_test_case("hsl") do
106
+ test("in-range hsl") do
107
+ ref = Colors::HSL.new(180, 0, 0.5)
108
+ css = Colors::CSS.parse("hsl( 180, 0% , 50% )")
109
+ assert_near(ref, css)
110
+ end
111
+
112
+ test("clamps out-of-range integer values") do
113
+ ref = Colors::HSL.new(180, 0, 0.5)
114
+ css = Colors::CSS.parse("hsl(-180,0%,50%)")
115
+ assert_near(ref, css)
116
+ css = Colors::CSS.parse("hsl(180,-10%,50%)")
117
+ assert_near(ref, css)
118
+ css = Colors::CSS.parse("hsl(540,-10%,50%)")
119
+ assert_near(ref, css)
120
+ ref = Colors::HSL.new(0, 1.0, 1.0)
121
+ css = Colors::CSS.parse("hsl(0,100%,100%)")
122
+ assert_near(ref, css)
123
+ css = Colors::CSS.parse("hsl(360,100%,100%)")
124
+ assert_near(ref, css)
125
+ end
126
+
127
+ test("bad input") do
128
+ assert_raises ArgumentError, "too many args" do
129
+ css = Colors::CSS.parse("hsl( 180, 0% , 50%, 50% )")
130
+ end
131
+ assert_raises ArgumentError, "too few args" do
132
+ css = Colors::CSS.parse("hsl( 180, 0%)")
133
+ end
134
+ assert_raises ArgumentError, "missing comma" do
135
+ css = Colors::CSS.parse("hsl( 180, 0% 50% )")
136
+ end
137
+ end
138
+ end
139
+
140
+ sub_test_case("hsla") do
141
+ test("in-range hsla") do
142
+ ref = Colors::HSLA.new(180, 0, 0.5, 1.0)
143
+ css = Colors::CSS.parse("hsla( 180, 0% , 50%,1.0)")
144
+ assert_near(ref, css)
145
+ end
146
+
147
+ test("clamps out-of-range integer values") do
148
+ ref = Colors::HSLA.new(180, 0, 0.5, 0.1)
149
+ css = Colors::CSS.parse("hsla(-180,0%,50%,0.1)")
150
+ assert_near(ref, css)
151
+ css = Colors::CSS.parse("hsla(180,-10%,50%, 0.1)")
152
+ assert_near(ref, css)
153
+ css = Colors::CSS.parse("hsla(540,-10%,50%, 0.1)")
154
+ assert_near(ref, css)
155
+ ref = Colors::HSLA.new(0, 1.0, 1.0, 0.0)
156
+ css = Colors::CSS.parse("hsla(0,100%,100%,0.0)")
157
+ assert_near(ref, css)
158
+ css = Colors::CSS.parse("hsla(360,100%,100%, 0.0)")
159
+ assert_near(ref, css)
160
+ end
161
+
162
+ test("bad input") do
163
+ assert_raises ArgumentError, "too many args" do
164
+ css = Colors::CSS.parse("hsla( 180, 0% , 50%, 0.0, 0.0 )")
165
+ end
166
+ assert_raises ArgumentError, "too few args" do
167
+ css = Colors::CSS.parse("hsla( 180, 0%, 0.0)")
168
+ end
169
+ assert_raises ArgumentError, "missing comma" do
170
+ css = Colors::CSS.parse("hsla( 180, 0% 50% 0.0 )")
171
+ end
172
+ end
173
+ end
174
+ end
@@ -44,7 +44,9 @@ class ColorsXterm256Test < Test::Unit::TestCase
44
44
  assert { Colors::Xterm256.new(16) != Colors::Xterm256.new(17) }
45
45
  end
46
46
 
47
- data do
47
+ RGB2GREY = [0.21263900587151035754r, 0.71516867876775592746r, 0.07219231536073371500r]
48
+
49
+ data(keep: true) do
48
50
  data_set = {}
49
51
  # colors 16-231 are a 6x6x6 color cube
50
52
  (0...6).each do |r|
@@ -55,7 +57,10 @@ class ColorsXterm256Test < Test::Unit::TestCase
55
57
  green = (g > 0) ? 40*g + 55 : g
56
58
  blue = (b > 0) ? 40*b + 55 : b
57
59
  label = "Color #{code} is rgb(#{red}, #{green}, #{blue})"
58
- data_set[label] = [code, Colors::RGB.new(red, green, blue)]
60
+ rgb = Colors::RGB.new(red, green, blue)
61
+ red, green, blue = Colors::Convert.srgb_to_linear_srgb(*rgb.components)
62
+ grey = RGB2GREY[0]*red + RGB2GREY[1]*green + RGB2GREY[2]*blue
63
+ data_set[label] = [code, rgb, grey]
59
64
  end
60
65
  end
61
66
  end
@@ -63,14 +68,23 @@ class ColorsXterm256Test < Test::Unit::TestCase
63
68
  (0...24).each do |y|
64
69
  code = 232 + y
65
70
  level = 10*y + 8
71
+ grey = level/255r
66
72
  label = "Color #{code} is gray(#{level})"
67
- data_set[label] = [code, Colors::RGB.new(level, level, level)]
73
+ data_set[label] = [code, Colors::RGB.new(level, level, level), grey]
68
74
  end
69
75
  data_set
70
76
  end
77
+
71
78
  def test_to_rgb(data)
72
- code, rgb = data
79
+ code, rgb, _grey = data
73
80
  assert_equal(rgb,
74
81
  Colors::Xterm256.new(code).to_rgb)
75
82
  end
83
+
84
+ def test_to_grey_level(data)
85
+ code, _rgb, grey = data
86
+ assert_in_delta(grey,
87
+ Colors::Xterm256.new(code).to_grey_level,
88
+ 1e-12)
89
+ end
76
90
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red-colors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-06-14 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: json
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: matrix
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -124,6 +137,7 @@ files:
124
137
  - data/colormaps/twilight.json
125
138
  - data/colormaps/viridis.json
126
139
  - data/colormaps/vlag.json
140
+ - doc/text/news.md
127
141
  - lib/colors.rb
128
142
  - lib/colors/abstract_color.rb
129
143
  - lib/colors/alpha_component.rb
@@ -134,6 +148,7 @@ files:
134
148
  - lib/colors/colormap_data/seaborn_builtin.rb
135
149
  - lib/colors/colormap_registry.rb
136
150
  - lib/colors/convert.rb
151
+ - lib/colors/css.rb
137
152
  - lib/colors/helper.rb
138
153
  - lib/colors/hsl.rb
139
154
  - lib/colors/hsla.rb
@@ -151,6 +166,7 @@ files:
151
166
  - red-colors.gemspec
152
167
  - test/helper.rb
153
168
  - test/run.rb
169
+ - test/test-css.rb
154
170
  - test/test-hsl.rb
155
171
  - test/test-hsla.rb
156
172
  - test/test-husl.rb
@@ -165,7 +181,6 @@ homepage: https://github.com/red-data-tools/red-colors
165
181
  licenses:
166
182
  - MIT
167
183
  metadata: {}
168
- post_install_message:
169
184
  rdoc_options: []
170
185
  require_paths:
171
186
  - lib
@@ -180,14 +195,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
195
  - !ruby/object:Gem::Version
181
196
  version: '0'
182
197
  requirements: []
183
- rubygems_version: 3.2.3
184
- signing_key:
198
+ rubygems_version: 4.0.16
185
199
  specification_version: 4
186
200
  summary: Red Colors provides a wide array of features for dealing with colors. This
187
201
  includes conversion between colorspaces, desaturation, and parsing colors.
188
202
  test_files:
189
203
  - test/helper.rb
190
204
  - test/run.rb
205
+ - test/test-css.rb
191
206
  - test/test-hsl.rb
192
207
  - test/test-hsla.rb
193
208
  - test/test-husl.rb