red-colors 0.1.2 → 0.1.3

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: c144d5d89c917428d99401b92513852896b2ff109228d85b2ddac5dd40b89fb4
4
- data.tar.gz: 8e99f5fadc2068211a0ec92d12ad754ae777efedd23048867c0ad91824fe7a35
3
+ metadata.gz: 4f5bd8e883bdf940ae77ece7135e3667a5d953534fda4780e344fd9413b59fa7
4
+ data.tar.gz: c275f21b6223f399f50b4265b32d652753d9a6d5297d1bdff1597aa9c2956371
5
5
  SHA512:
6
- metadata.gz: 53491ee2e2c97a99c624d5e4d2fc73d9939f68ee9b27f24f54bcf1f3831f87f8cf2d115d4c8b0499e73e2623ad6e045c714f254a4dc33da55dfdf84213b45903
7
- data.tar.gz: 1d783343d7536ca570bdbd049d83d92a107450341e03a8f4a390f88abc891d92d67404e198b87ee7555b6abea05610e13f4afcd9421d417c20f1ef3e38be002c
6
+ metadata.gz: a1dbc251a4dda958ada6163601b876ab7572f6aa2a71bb84e728f6b1bd180ef90a6937ef5db3ca3724951be8a329f2abc655f162d8849a4f2868be9d78eb4c73
7
+ data.tar.gz: b0d5952ff67aa28147f85105ea7d89f59a526cfa856d5ab1b6826eec35f50d60dde5a69d1f28b1c73720b4edcd8eff948c656880feda2e1e943f7decbc9c0859
@@ -1,3 +1,5 @@
1
+ require "matrix"
2
+
1
3
  module Colors
2
4
  module Convert
3
5
  module_function
@@ -16,6 +18,11 @@ module Colors
16
18
  end
17
19
  end
18
20
 
21
+ private def matrix_inv(matrix)
22
+ matrix = Matrix[*matrix]
23
+ matrix.inv.to_a
24
+ end
25
+
19
26
  def max_chroma(l, h)
20
27
  h_rad = degree_to_radian(h)
21
28
  sin_h = Math.sin(h_rad).to_r
@@ -35,9 +42,9 @@ module Colors
35
42
 
36
43
  bounds = Array.new(6) { [0r, 0r] }
37
44
  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
45
+ m1 = M_XYZ_RGB[ch][0].to_r
46
+ m2 = M_XYZ_RGB[ch][1].to_r
47
+ m3 = M_XYZ_RGB[ch][2].to_r
41
48
 
42
49
  [0, 1].each do |t|
43
50
  top1 = (284517r * m1 - 94839r * m3) * sub2
@@ -109,14 +116,9 @@ module Colors
109
116
 
110
117
  def luv_to_lch(l, u, v)
111
118
  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
-
119
+ hard = Math.atan2(v, u).to_r
120
+ h = hard * 180 / Math::PI.to_r
121
+ h += 360r if h < 0
120
122
  [l, c, h]
121
123
  end
122
124
 
@@ -195,26 +197,68 @@ module Colors
195
197
 
196
198
  # sRGB -> ???
197
199
 
200
+ def srgb_from_linear_srgb(r, g, b)
201
+ a = 0.055r
202
+ [r, g, b].map do |v|
203
+ if v < 0.0031308
204
+ 12.92r * v
205
+ else
206
+ (1 + a) * v**(1/2.4r) - a
207
+ end
208
+ end
209
+ end
210
+
198
211
  def srgb_to_linear_srgb(r, g, b)
212
+ a = 0.055r
199
213
  [r, g, b].map do |v|
200
214
  if v > 0.04045
201
- ((v + 0.055r) / 1.055r) ** 2.4r
215
+ ((v + a) / (1 + a)) ** 2.4r
202
216
  else
203
217
  v / 12.92r
204
218
  end
205
219
  end
206
220
  end
207
221
 
222
+ # xyY -> ???
223
+
224
+ def xyy_to_xyz(x, y, large_y)
225
+ large_x = large_y*x/y
226
+ large_z = large_y*(1 - x - y)/y
227
+ [large_x, large_y, large_z]
228
+ end
229
+
208
230
  # XYZ -> ???
209
231
 
210
- XYZ2RGB = [
211
- [ 3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366 ],
212
- [ -0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247 ],
213
- [ 0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072 ]
232
+ # sRGB reference points
233
+ R_xyY = [0.64r, 0.33r, 1r]
234
+ G_xyY = [0.30r, 0.60r, 1r]
235
+ B_xyY = [0.15r, 0.06r, 1r]
236
+ D65_xyY = [0.3127r, 0.3290r, 1r]
237
+
238
+ R_XYZ = xyy_to_xyz(*R_xyY)
239
+ G_XYZ = xyy_to_xyz(*G_xyY)
240
+ B_XYZ = xyy_to_xyz(*B_xyY)
241
+ D65_XYZ = xyy_to_xyz(*D65_xyY)
242
+
243
+ M_P = [
244
+ [R_XYZ[0], G_XYZ[0], B_XYZ[0]],
245
+ [R_XYZ[1], G_XYZ[1], B_XYZ[1]],
246
+ [R_XYZ[2], G_XYZ[2], B_XYZ[2]]
214
247
  ]
248
+
249
+ M_S = dot_product(matrix_inv(M_P), D65_XYZ)
250
+
251
+ M_RGB_XYZ = (0 ... 3).map do |i|
252
+ (0 ... 3).map {|j| (M_S[j] * M_P[i][j]).round(4) }
253
+ end
254
+
255
+ M_XYZ_RGB = matrix_inv(M_RGB_XYZ).map do |row|
256
+ row.map {|v| v.round(4) }
257
+ end
258
+
215
259
  def xyz_to_rgb(x, y, z)
216
- r, g, b = dot_product(XYZ2RGB, [x, y, z])
217
- r, g, b = srgb_to_linear_srgb(r, g, b)
260
+ r, g, b = dot_product(M_XYZ_RGB, [x, y, z])
261
+ r, g, b = srgb_from_linear_srgb(r, g, b)
218
262
  [
219
263
  r.clamp(0r, 1r),
220
264
  g.clamp(0r, 1r),
data/lib/colors/rgb.rb CHANGED
@@ -118,7 +118,7 @@ module Colors
118
118
  c = RGB.new(r, g, b).to_xyz
119
119
  l, u, v = c.luv_components(WHITE_POINT_D65)
120
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))
121
+ HUSL.new(h, s.clamp(0r, 1r).to_r, l.clamp(0r, 1r).to_r)
122
122
  end
123
123
 
124
124
  def to_xyz
@@ -1,3 +1,3 @@
1
1
  module Colors
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/colors/xyy.rb CHANGED
@@ -34,9 +34,7 @@ module Colors
34
34
  end
35
35
 
36
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)
37
+ XYZ.new(*Convert.xyy_to_xyz(*components))
40
38
  end
41
39
 
42
40
  private def canonicalize(x, y, large_y)
data/red-colors.gemspec CHANGED
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
32
32
  spec.files += Dir.glob("doc/text/*")
33
33
  spec.test_files += Dir.glob("test/**/*")
34
34
 
35
+ spec.add_runtime_dependency("matrix")
36
+
35
37
  spec.add_development_dependency("bundler")
36
38
  spec.add_development_dependency("rake")
37
39
  spec.add_development_dependency("test-unit")
data/test/test-husl.rb CHANGED
@@ -177,7 +177,7 @@ class ColorsHUSLTest < Test::Unit::TestCase
177
177
  test("#desaturate") do
178
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
183
  test("#to_husl") do
@@ -185,30 +185,49 @@ class ColorsHUSLTest < Test::Unit::TestCase
185
185
  assert_same(black, black.to_hsl)
186
186
  end
187
187
 
188
- test("#to_rgb") do
189
- # black
190
- assert_equal(Colors::RGB.new(0, 0, 0),
191
- Colors::HUSL.new(0, 0, 0).to_rgb)
192
- # red
193
- assert_near(Colors::RGB.new(1r, 0, 0),
194
- Colors::HUSL.new(12.177050630061776r, 1r, 0.5323711559542933r).to_rgb)
195
- # yellow
196
- assert_near(Colors::RGB.new(1r, 1r, 0),
197
- Colors::HUSL.new(85.87432021817473r, 1r, 0.9713855934179674r).to_rgb)
198
- # green
199
- assert_near(Colors::RGB.new(0r, 1r, 0),
200
- Colors::HUSL.new(127.71501294924047r, 1r, 0.8773551910965973r).to_rgb)
201
- # cyan
202
- assert_near(Colors::RGB.new(0r, 1r, 1r),
203
- Colors::HUSL.new(192.17705063006116r, 1r, 0.9111475231670507r).to_rgb)
204
- # blue
205
- assert_near(Colors::RGB.new(0r, 0r, 1r),
206
- Colors::HUSL.new(265.8743202181779r, 1r, 0.3230087290398002r).to_rgb)
207
- # magenta
208
- assert_near(Colors::RGB.new(1r, 0r, 1r),
209
- Colors::HUSL.new(307.7150129492436r, 1r, 0.60322731354551294r).to_rgb)
210
- # white
211
- assert_near(Colors::RGB.new(1r, 1r, 1r),
212
- Colors::HUSL.new(0r, 1r, 1r).to_rgb)
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
203
+ end
204
+
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
213
232
  end
214
233
  end
data/test/test-rgb.rb CHANGED
@@ -292,31 +292,19 @@ class ColorsRGBTest < Test::Unit::TestCase
292
292
  Colors::RGB.new(1r, 1r, 1r).to_hsl)
293
293
  end
294
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)
295
+ data(
296
+ "red" => { rgb: "#ff0000", husl: [ 12.16, 1.0, 0.532] },
297
+ "yellow" => { rgb: "#ffff00", husl: [ 85.90, 1.0, 0.971] },
298
+ "green" => { rgb: "#00ff00", husl: [127.72, 1.0, 0.877] },
299
+ "cyan" => { rgb: "#00ffff", husl: [192.20, 1.0, 0.911] },
300
+ "blue" => { rgb: "#0000ff", husl: [265.87, 1.0, 0.323] },
301
+ "magenta" => { rgb: "#ff00ff", husl: [307.71, 1.0, 0.603] }
302
+ )
303
+ def test_to_husl(data)
304
+ husl_components, hex_string = data.values_at(:husl, :rgb)
305
+ husl = Colors::HUSL.new(*husl_components)
306
+ rgb = Colors::RGB.parse(hex_string)
307
+ assert_near(husl, rgb.to_husl, 0.03)
320
308
  end
321
309
 
322
310
  data do
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: red-colors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenta Murata
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-19 00:00:00.000000000 Z
11
+ date: 2021-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: matrix
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement