color_converters 0.1.1 → 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 +4 -4
- data/README.md +29 -5
- data/lib/color_converters/base_converter.rb +29 -11
- data/lib/color_converters/color.rb +1 -1
- data/lib/color_converters/converters/cielab_converter.rb +38 -34
- data/lib/color_converters/converters/cielch_converter.rb +58 -0
- data/lib/color_converters/converters/cmyk_converter.rb +4 -4
- data/lib/color_converters/converters/hex_converter.rb +2 -2
- data/lib/color_converters/converters/hsl_converter.rb +4 -3
- data/lib/color_converters/converters/hsl_string_converter.rb +4 -2
- data/lib/color_converters/converters/hsv_converter.rb +6 -6
- data/lib/color_converters/converters/name_converter.rb +7 -5
- data/lib/color_converters/converters/oklab_converter.rb +98 -0
- data/lib/color_converters/converters/oklch_converter.rb +24 -18
- data/lib/color_converters/converters/rgb_converter.rb +78 -1
- data/lib/color_converters/converters/rgb_string_converter.rb +6 -6
- data/lib/color_converters/converters/xyz_converter.rb +67 -87
- data/lib/color_converters/version.rb +1 -1
- data/lib/color_converters.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5cff79bc695fab4e8041206a798421948d03eb04933b77274a802f90a0010999
|
4
|
+
data.tar.gz: 1edc8b8811b32ca6ac4d7b9f8347151ab772fa58d52d90ba4cd8f48ab60d889e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5fe55d89679198c41e0e166dbdae882b11f9396d58710b004707358652c6187e53889d88dd21efbeeee99b7738484a7cd3d71f7cc35496445881a9558fa9543
|
7
|
+
data.tar.gz: 507da4fe02639e97285b5df4ed0e6e5e69fd78f793aa8bc772b2702d72e029f227ce50abf2987ac087fe1b050a06562262ed9660ca8395d6833e528412900852
|
data/README.md
CHANGED
@@ -18,7 +18,9 @@ Colors can be converted between the following spaces:
|
|
18
18
|
- cmyk
|
19
19
|
- xyz
|
20
20
|
- cielab
|
21
|
-
-
|
21
|
+
- cielch
|
22
|
+
- oklab (not always reliable)
|
23
|
+
- oklch (not always reliable)
|
22
24
|
- name
|
23
25
|
|
24
26
|
## Installation
|
@@ -63,10 +65,16 @@ color = ColorConverters::Color.new(c: 74, m: 58, y: 22, k: 3)
|
|
63
65
|
color = ColorConverters::Color.new(x: 16, y: 44, z: 32)
|
64
66
|
|
65
67
|
# from cielab
|
66
|
-
color = ColorConverters::Color.new(l: 16, a: 44, b: 32)
|
68
|
+
color = ColorConverters::Color.new(l: 16, a: 44, b: 32, space: :cie)
|
69
|
+
|
70
|
+
# from cielch
|
71
|
+
color = ColorConverters::Color.new(l: 16, c: 44, h: 32, space: :cie)
|
72
|
+
|
73
|
+
# from oklab
|
74
|
+
color = ColorConverters::Color.new(l: 16, a: 44, b: 32, space: :ok)
|
67
75
|
|
68
76
|
# from oklch
|
69
|
-
color = ColorConverters::Color.new(l: 16, c: 44, h: 32)
|
77
|
+
color = ColorConverters::Color.new(l: 16, c: 44, h: 32, space: :ok)
|
70
78
|
|
71
79
|
# from textual color
|
72
80
|
color = ColorConverters::Color.new("blue")
|
@@ -109,7 +117,13 @@ color.xyz
|
|
109
117
|
color.cielab
|
110
118
|
=> {:l=>52.47, :a=>-4.08, :b=>-32.19}
|
111
119
|
|
112
|
-
color.
|
120
|
+
color.cielch
|
121
|
+
=> {:l=>52.47, :c=>32.45, :h=>262.78}
|
122
|
+
|
123
|
+
color.oklab # not always accurate
|
124
|
+
=> {:l=>52.47, :a=>-4.08, :b=>-32.19}
|
125
|
+
|
126
|
+
color.oklch # not always accurate
|
113
127
|
=> {:l=>52.47, :c=>32.45, :h=>262.78}
|
114
128
|
|
115
129
|
color.hex
|
@@ -121,6 +135,16 @@ color.name
|
|
121
135
|
|
122
136
|
## Options
|
123
137
|
|
138
|
+
### space
|
139
|
+
|
140
|
+
As there are certain color spaces that use the same letter keys, there needed to be a way to different between those space.
|
141
|
+
The space parameter allows that, with examples in the usage code above
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
ColorConverters::Color.new(l: 64, a: 28, b: -15, space: :cie)
|
145
|
+
ColorConverters::Color.new(l: 64, a: 28, b: -15, space: :ok)
|
146
|
+
```
|
147
|
+
|
124
148
|
### limit_override
|
125
149
|
|
126
150
|
By default all values are checked to be within the expected number ranges, i.e.; rgb between 0-255 each.
|
@@ -132,7 +156,7 @@ ColorConverters::Color.new(r: 270, g: 1300, b: 380, a: 0.5, limit_override: true
|
|
132
156
|
|
133
157
|
## Development
|
134
158
|
|
135
|
-
[Converting Colors](https://convertingcolors.com/) can be usef to help verify results. Different calculators use different exponents and standards so there can be discrepency across them (like this calculator for LCH).
|
159
|
+
[Converting Colors](https://convertingcolors.com/) and [Colorffy](https://colorffy.com/) can be usef to help verify results. Different calculators use different exponents and standards so there can be discrepency across them (like this calculator for LCH).
|
136
160
|
|
137
161
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
138
162
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'active_support/core_ext/object/blank'
|
2
|
+
require 'bigdecimal'
|
3
|
+
require 'bigdecimal/util'
|
2
4
|
|
3
5
|
module ColorConverters
|
4
6
|
class BaseConverter
|
@@ -26,15 +28,19 @@ module ColorConverters
|
|
26
28
|
def initialize(color_input, limit_override = false)
|
27
29
|
@original_value = color_input
|
28
30
|
|
31
|
+
# self.clamp_input(color_input) if limit_clamp == true
|
32
|
+
|
29
33
|
if limit_override == false && !self.validate_input(color_input)
|
30
34
|
raise InvalidColorError # validation method is defined in each convertor
|
31
35
|
end
|
32
36
|
|
33
|
-
|
37
|
+
r, g, b, a = self.input_to_rgba(color_input) # conversion method is defined in each convertor
|
38
|
+
|
39
|
+
@rgba = { r: r.to_f.round(IMPORT_DP), g: g.to_f.round(IMPORT_DP), b: b.to_f.round(IMPORT_DP), a: a.to_f.round(IMPORT_DP) }
|
34
40
|
end
|
35
41
|
|
36
42
|
def rgb
|
37
|
-
{ r: @rgba[:r].round(OUTPUT_DP), g: @rgba[:g].round(OUTPUT_DP), b: @rgba[:b].round(OUTPUT_DP) }
|
43
|
+
{ r: @rgba[:r].to_f.round(OUTPUT_DP), g: @rgba[:g].to_f.round(OUTPUT_DP), b: @rgba[:b].to_f.round(OUTPUT_DP) }
|
38
44
|
end
|
39
45
|
|
40
46
|
def hex
|
@@ -44,13 +50,13 @@ module ColorConverters
|
|
44
50
|
def hsl
|
45
51
|
@r, @g, @b = self.rgb_array_frac
|
46
52
|
|
47
|
-
{ h: self.hue.round(OUTPUT_DP), s: self.hsl_saturation.round(OUTPUT_DP), l: self.hsl_lightness.round(OUTPUT_DP) }
|
53
|
+
{ h: self.hue.to_f.round(OUTPUT_DP), s: self.hsl_saturation.to_f.round(OUTPUT_DP), l: self.hsl_lightness.to_f.round(OUTPUT_DP) }
|
48
54
|
end
|
49
55
|
|
50
56
|
def hsv
|
51
57
|
@r, @g, @b = self.rgb_array
|
52
58
|
|
53
|
-
{ h: self.hue.round(OUTPUT_DP), s: self.hsv_saturation.round(OUTPUT_DP), v: self.hsv_value.round(OUTPUT_DP) }
|
59
|
+
{ h: self.hue.to_f.round(OUTPUT_DP), s: self.hsv_saturation.to_f.round(OUTPUT_DP), v: self.hsv_value.to_f.round(OUTPUT_DP) }
|
54
60
|
end
|
55
61
|
|
56
62
|
def hsb
|
@@ -62,25 +68,37 @@ module ColorConverters
|
|
62
68
|
def cmyk
|
63
69
|
c, m, y, k = CmykConverter.rgb_to_cmyk(self.rgb_array_frac)
|
64
70
|
|
65
|
-
{ c: c.round(OUTPUT_DP), m: m.round(OUTPUT_DP), y: y.round(OUTPUT_DP), k: k.round(OUTPUT_DP) }
|
71
|
+
{ c: c.to_f.round(OUTPUT_DP), m: m.to_f.round(OUTPUT_DP), y: y.to_f.round(OUTPUT_DP), k: k.to_f.round(OUTPUT_DP) }
|
66
72
|
end
|
67
73
|
|
68
74
|
def xyz
|
69
75
|
x, y, z = XyzConverter.rgb_to_xyz(self.rgb_array_frac)
|
70
76
|
|
71
|
-
{ x: x.round(OUTPUT_DP), y: y.round(OUTPUT_DP), z: z.round(OUTPUT_DP) }
|
77
|
+
{ x: x.to_f.round(OUTPUT_DP), y: y.to_f.round(OUTPUT_DP), z: z.to_f.round(OUTPUT_DP) }
|
72
78
|
end
|
73
79
|
|
74
80
|
def cielab
|
75
|
-
l, a, b = CielabConverter.
|
81
|
+
l, a, b = CielabConverter.xyz_to_cielab(XyzConverter.rgb_to_xyz(self.rgb_array_frac))
|
82
|
+
|
83
|
+
{ l: l.to_f.round(OUTPUT_DP), a: a.to_f.round(OUTPUT_DP), b: b.to_f.round(OUTPUT_DP) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def cielch
|
87
|
+
l, c, h = CielchConverter.cielab_to_cielch(CielabConverter.xyz_to_cielab(XyzConverter.rgb_to_xyz(self.rgb_array_frac)))
|
88
|
+
|
89
|
+
{ l: l.to_f.round(OUTPUT_DP), c: c.to_f.round(OUTPUT_DP), h: h.to_f.round(OUTPUT_DP) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def oklab
|
93
|
+
l, a, b = OklabConverter.xyz_to_oklab(XyzConverter.rgb_to_xyz(self.rgb_array_frac))
|
76
94
|
|
77
|
-
{ l: l.round(OUTPUT_DP), a: a.round(OUTPUT_DP), b: b.round(OUTPUT_DP) }
|
95
|
+
{ l: l.to_f.round(OUTPUT_DP), a: a.to_f.round(OUTPUT_DP), b: b.to_f.round(OUTPUT_DP) }
|
78
96
|
end
|
79
97
|
|
80
98
|
def oklch
|
81
|
-
l, c, h = OklchConverter.
|
99
|
+
l, c, h = OklchConverter.oklab_to_oklch(OklabConverter.xyz_to_oklab(XyzConverter.rgb_to_xyz(self.rgb_array_frac)))
|
82
100
|
|
83
|
-
{ l: l.round(OUTPUT_DP), c: c.round(OUTPUT_DP), h: h.round(OUTPUT_DP) }
|
101
|
+
{ l: l.to_f.round(OUTPUT_DP), c: c.to_f.round(OUTPUT_DP), h: h.to_f.round(OUTPUT_DP) }
|
84
102
|
end
|
85
103
|
|
86
104
|
def alpha
|
@@ -88,7 +106,7 @@ module ColorConverters
|
|
88
106
|
end
|
89
107
|
|
90
108
|
def name
|
91
|
-
NameConverter.rgb_to_name(self.
|
109
|
+
NameConverter.rgb_to_name(self.rgb_array)
|
92
110
|
end
|
93
111
|
|
94
112
|
protected
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ColorConverters
|
2
2
|
class Color
|
3
3
|
extend Forwardable
|
4
|
-
def_delegators :@converter, :rgb, :hex, :hsl, :hsv, :hsb, :cmyk, :xyz, :cielab, :oklch, :name, :alpha
|
4
|
+
def_delegators :@converter, :rgb, :hex, :hsl, :hsv, :hsb, :cmyk, :xyz, :cielab, :cielch, :oklab, :oklch, :name, :alpha
|
5
5
|
|
6
6
|
def initialize(color)
|
7
7
|
@converter = BaseConverter.factory(color)
|
@@ -3,7 +3,7 @@ module ColorConverters
|
|
3
3
|
def self.matches?(color_input)
|
4
4
|
return false unless color_input.is_a?(Hash)
|
5
5
|
|
6
|
-
color_input.keys - [:l, :a, :b] == []
|
6
|
+
color_input.keys - [:l, :a, :b, :space] == [] && color_input[:space].to_s == 'cie'
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.bounds
|
@@ -18,69 +18,73 @@ module ColorConverters
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def input_to_rgba(color_input)
|
21
|
-
|
22
|
-
XyzConverter.
|
21
|
+
x, y, z = CielabConverter.cielab_to_xyz(color_input)
|
22
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
23
|
+
|
24
|
+
[r, g, b, 1.0]
|
23
25
|
end
|
24
26
|
|
25
|
-
def self.
|
26
|
-
l = color_input[:l].
|
27
|
-
a = color_input[:a].
|
28
|
-
b = color_input[:b].
|
27
|
+
def self.cielab_to_xyz(color_input)
|
28
|
+
l = color_input[:l].to_d
|
29
|
+
a = color_input[:a].to_d
|
30
|
+
b = color_input[:b].to_d
|
29
31
|
|
30
|
-
yy = (l + 16.0) / 116.0
|
31
|
-
xx = (a / 500.0) + yy
|
32
|
-
zz = yy - (b / 200.0)
|
32
|
+
yy = (l + 16.0.to_d) / 116.0.to_d
|
33
|
+
xx = (a / 500.0.to_d) + yy
|
34
|
+
zz = yy - (b / 200.0.to_d)
|
33
35
|
|
34
|
-
|
36
|
+
e = 216.0.to_d / 24_389.0.to_d
|
35
37
|
|
36
38
|
x, y, z = [xx, yy, zz].map do
|
37
|
-
if _1 <=
|
38
|
-
(3.0 * (6.0 / 29.0) * (6.0 / 29.0) * (_1 - (4.0 / 29.0)))
|
39
|
+
if _1**3.to_d <= e
|
40
|
+
(3.0.to_d * (6.0.to_d / 29.0.to_d) * (6.0.to_d / 29.0.to_d) * (_1 - (4.0.to_d / 29.0.to_d)))
|
39
41
|
else
|
40
|
-
_1**3
|
42
|
+
_1**3.to_d
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
x *= 95.047
|
45
|
-
y *= 100.0
|
46
|
-
z *= 108.883
|
46
|
+
x *= 95.047.to_d
|
47
|
+
y *= 100.0.to_d
|
48
|
+
z *= 108.883.to_d
|
47
49
|
|
48
|
-
|
50
|
+
[x, y, z]
|
49
51
|
end
|
50
52
|
|
51
|
-
def self.
|
52
|
-
x, y, z = xyz_array
|
53
|
+
def self.xyz_to_cielab(xyz_array)
|
54
|
+
x, y, z = xyz_array.map(&:to_d)
|
55
|
+
|
56
|
+
# https://www.w3.org/TR/css-color-4/#color-conversion-code
|
57
|
+
# # The D50 & D65 standard illuminant white point
|
58
|
+
# wp_rel = [0.3457 / 0.3585, 1.0, (1.0 - 0.3457 - 0.3585) / 0.3585]
|
59
|
+
wp_rel = [0.3127.to_d / 0.3290.to_d, 1.0.to_d, (1.0.to_d - 0.3127.to_d - 0.3290.to_d) / 0.3290.to_d].map { _1 * 100.0.to_d }
|
53
60
|
|
54
|
-
|
55
|
-
xr = 95.047
|
56
|
-
yr = 100.0
|
57
|
-
zr = 108.883
|
61
|
+
xr, yr, zr = wp_rel
|
58
62
|
|
59
|
-
# Calculate the ratio of the XYZ values to the reference white.
|
60
|
-
# http://www.brucelindbloom.com/index.html?Equations.html
|
63
|
+
# # Calculate the ratio of the XYZ values to the reference white.
|
64
|
+
# # http://www.brucelindbloom.com/index.html?Equations.html
|
61
65
|
rel = [x / xr, y / yr, z / zr]
|
62
66
|
|
63
|
-
e = 216.0 / 24_389.0
|
64
|
-
k = 841.0 / 108.0
|
67
|
+
e = 216.0.to_d / 24_389.0.to_d
|
68
|
+
k = 841.0.to_d / 108.0.to_d
|
65
69
|
|
66
70
|
# And now transform
|
67
|
-
# http
|
71
|
+
# http:#en.wikipedia.org/wiki/Lab_color_space#Forward_transformation
|
68
72
|
# There is a brief explanation there as far as the nature of the calculations,
|
69
73
|
# as well as a much nicer looking modeling of the algebra.
|
70
74
|
xx, yy, zz = rel.map do
|
71
75
|
if _1 > e
|
72
|
-
_1**(1.0 / 3.0)
|
76
|
+
_1**(1.0.to_d / 3.0.to_d)
|
73
77
|
else
|
74
|
-
(k * _1) + (4.0 / 29.0)
|
78
|
+
(k * _1) + (4.0.to_d / 29.0.to_d)
|
75
79
|
# The 4/29 here is for when t = 0 (black). 4/29 * 116 = 16, and 16 -
|
76
80
|
# 16 = 0, which is the correct value for L* with black.
|
77
81
|
# ((1.0/3)*((29.0/6)**2) * t) + (4.0/29)
|
78
82
|
end
|
79
83
|
end
|
80
84
|
|
81
|
-
l = ((116.0 * yy) - 16.0)
|
82
|
-
a = (500.0 * (xx - yy))
|
83
|
-
b = (200.0 * (yy - zz))
|
85
|
+
l = ((116.0.to_d * yy) - 16.0.to_d)
|
86
|
+
a = (500.0.to_d * (xx - yy))
|
87
|
+
b = (200.0.to_d * (yy - zz))
|
84
88
|
|
85
89
|
[l, a, b]
|
86
90
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ColorConverters
|
2
|
+
class CielchConverter < BaseConverter
|
3
|
+
def self.matches?(color_input)
|
4
|
+
return false unless color_input.is_a?(Hash)
|
5
|
+
|
6
|
+
color_input.keys - [:l, :c, :h, :space] == [] && color_input[:space].to_s == 'cie'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.bounds
|
10
|
+
{ l: [0.0, 100.0], c: [0.0, 150.0], h: [0.0, 360.0] }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate_input(color_input)
|
16
|
+
bounds = CielchConverter.bounds
|
17
|
+
color_input[:l].to_f.between?(*bounds[:l]) && color_input[:c].to_f.between?(*bounds[:c]) && color_input[:h].to_f.between?(*bounds[:h])
|
18
|
+
end
|
19
|
+
|
20
|
+
def input_to_rgba(color_input)
|
21
|
+
l, a, b = CielchConverter.cielch_to_cielab(color_input)
|
22
|
+
x, y, z = CielabConverter.cielab_to_xyz({ l: l, a: a, b: b })
|
23
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
24
|
+
|
25
|
+
[r, g, b, 1.0]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.cielch_to_cielab(color_input)
|
29
|
+
l = color_input[:l].to_d
|
30
|
+
c = color_input[:c].to_d
|
31
|
+
h = color_input[:h].to_d
|
32
|
+
|
33
|
+
h_rad = h * (Math::PI.to_d / 180.0.to_d)
|
34
|
+
|
35
|
+
a = c * Math.cos(h_rad).to_d
|
36
|
+
b = c * Math.sin(h_rad).to_d
|
37
|
+
|
38
|
+
[l, a, b]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.cielab_to_cielch(lab_array)
|
42
|
+
l, aa, bb = lab_array.map(&:to_d)
|
43
|
+
|
44
|
+
e = 0.0015.to_d; # if chroma is smaller than this, set hue to 0 [https://www.w3.org/TR/css-color-4/#color-conversion-code]
|
45
|
+
|
46
|
+
c = ((aa**2.to_d) + (bb**2.to_d))**0.5.to_d
|
47
|
+
|
48
|
+
h_rad = Math.atan2(bb, aa).to_d
|
49
|
+
h = h_rad * (180.0.to_d / Math::PI.to_d)
|
50
|
+
|
51
|
+
h %= 360
|
52
|
+
|
53
|
+
h = 0 if c < e
|
54
|
+
|
55
|
+
[l, c, h]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -28,11 +28,11 @@ module ColorConverters
|
|
28
28
|
y /= 100.0
|
29
29
|
k /= 100.0
|
30
30
|
|
31
|
-
r = (255 * (1.0 - [1.0, c * (1.0 - k) + k].min))
|
32
|
-
g = (255 * (1.0 - [1.0, m * (1.0 - k) + k].min))
|
33
|
-
b = (255 * (1.0 - [1.0, y * (1.0 - k) + k].min))
|
31
|
+
r = (255 * (1.0 - [1.0, c * (1.0 - k) + k].min))
|
32
|
+
g = (255 * (1.0 - [1.0, m * (1.0 - k) + k].min))
|
33
|
+
b = (255 * (1.0 - [1.0, y * (1.0 - k) + k].min))
|
34
34
|
|
35
|
-
|
35
|
+
[r, g, b, 1.0]
|
36
36
|
end
|
37
37
|
|
38
38
|
def self.rgb_to_cmyk(rgb_array_frac)
|
@@ -8,7 +8,7 @@ module ColorConverters
|
|
8
8
|
|
9
9
|
private
|
10
10
|
|
11
|
-
def validate_input(
|
11
|
+
def validate_input(_color_input)
|
12
12
|
# TODO
|
13
13
|
true
|
14
14
|
end
|
@@ -21,7 +21,7 @@ module ColorConverters
|
|
21
21
|
b = color_input[4, 2].hex
|
22
22
|
a = color_input.length == 8 ? hex[6, 2].hex : 1.0
|
23
23
|
|
24
|
-
|
24
|
+
[r, g, b, a]
|
25
25
|
end
|
26
26
|
|
27
27
|
def normalize_hex(hex_input)
|
@@ -50,15 +50,16 @@ module ColorConverters
|
|
50
50
|
t1
|
51
51
|
end
|
52
52
|
|
53
|
-
rgb[i] = (val * 255)
|
53
|
+
rgb[i] = (val * 255)
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
[rgb[0], rgb[1], rgb[2], a]
|
57
57
|
end
|
58
58
|
|
59
59
|
def greyscale(luminosity, alpha)
|
60
60
|
rgb_equal_value = (luminosity * 255).round(IMPORT_DP)
|
61
|
-
|
61
|
+
|
62
|
+
[rgb_equal_value, rgb_equal_value, rgb_equal_value, alpha]
|
62
63
|
end
|
63
64
|
end
|
64
65
|
end
|
@@ -8,7 +8,7 @@ module ColorConverters
|
|
8
8
|
|
9
9
|
private
|
10
10
|
|
11
|
-
def validate_input(
|
11
|
+
def validate_input(_color_input)
|
12
12
|
true
|
13
13
|
end
|
14
14
|
|
@@ -19,7 +19,9 @@ module ColorConverters
|
|
19
19
|
h, s, l, a = matches[1].split(',').map(&:strip)
|
20
20
|
raise InvalidColorError unless h.present? && s.present? && l.present?
|
21
21
|
|
22
|
-
HslConverter.new(h: h, s: s, l: l, a: a).rgba
|
22
|
+
rgba = HslConverter.new(h: h, s: s, l: l, a: a).rgba
|
23
|
+
|
24
|
+
[rgba[:r], rgba[:g], rgba[:b], rgba[:a]]
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -41,17 +41,17 @@ module ColorConverters
|
|
41
41
|
|
42
42
|
case i % 6
|
43
43
|
when 0
|
44
|
-
|
44
|
+
[v, t, p, 1.0]
|
45
45
|
when 1
|
46
|
-
|
46
|
+
[q, v, p, 1.0]
|
47
47
|
when 2
|
48
|
-
|
48
|
+
[p, v, t, 1.0]
|
49
49
|
when 3
|
50
|
-
|
50
|
+
[p, q, v, 1.0]
|
51
51
|
when 4
|
52
|
-
|
52
|
+
[t, p, v, 1.0]
|
53
53
|
when 5
|
54
|
-
|
54
|
+
[v, p, q, 1.0]
|
55
55
|
end
|
56
56
|
end
|
57
57
|
end
|
@@ -6,16 +6,16 @@ module ColorConverters
|
|
6
6
|
self.color_names.include?(color_input.downcase.to_sym)
|
7
7
|
end
|
8
8
|
|
9
|
-
def self.rgb_to_name(
|
10
|
-
r, g, b =
|
9
|
+
def self.rgb_to_name(rgb_array)
|
10
|
+
r, g, b = rgb_array
|
11
11
|
|
12
|
-
name = self.color_names.find { |_k, v| v == [r, g, b] }
|
12
|
+
name = self.color_names.find { |_k, v| v == [r.round, g.round, b.round] }
|
13
13
|
name.present? ? name[0].to_s : nil
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def validate_input(
|
18
|
+
def validate_input(_color_input)
|
19
19
|
# TODO: validate against list of keys?
|
20
20
|
true
|
21
21
|
end
|
@@ -25,9 +25,11 @@ module ColorConverters
|
|
25
25
|
raise InvalidColorError unless found_colour.present?
|
26
26
|
|
27
27
|
r, g, b = found_colour
|
28
|
-
|
28
|
+
|
29
|
+
[r, g, b, 1.0]
|
29
30
|
end
|
30
31
|
|
32
|
+
# TODO: use color_namer_ruby gem instead, but that gem also uses this gem
|
31
33
|
def self.color_names
|
32
34
|
{
|
33
35
|
aliceblue: [240, 248, 255],
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module ColorConverters
|
2
|
+
class OklabConverter < BaseConverter
|
3
|
+
def self.matches?(color_input)
|
4
|
+
return false unless color_input.is_a?(Hash)
|
5
|
+
|
6
|
+
color_input.keys - [:l, :a, :b, :space] == [] && color_input[:space].to_s == 'ok'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.bounds
|
10
|
+
{ l: [0.0, 100.0], a: [-0.5, 0.5], b: [-0.5, 0.5] }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate_input(color_input)
|
16
|
+
bounds = OklabConverter.bounds
|
17
|
+
color_input[:l].to_f.between?(*bounds[:l]) && color_input[:a].to_f.between?(*bounds[:a]) && color_input[:b].to_f.between?(*bounds[:b])
|
18
|
+
end
|
19
|
+
|
20
|
+
def input_to_rgba(color_input)
|
21
|
+
x, y, z = OklabConverter.oklab_to_xyz(color_input)
|
22
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
23
|
+
|
24
|
+
[r, g, b, 1.0]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.oklab_to_xyz(color_input)
|
28
|
+
l = color_input[:l].to_d
|
29
|
+
a = color_input[:a].to_d
|
30
|
+
b = color_input[:b].to_d
|
31
|
+
|
32
|
+
# Now, scale l to a decimal
|
33
|
+
l /= 100.0.to_d
|
34
|
+
|
35
|
+
# Convert Oklab (L*a*b*) to LMS'
|
36
|
+
lab_to_lms_matrix = ::Matrix[
|
37
|
+
[1.0000000000000000.to_d, 0.3963377773761749.to_d, 0.2158037573099136.to_d],
|
38
|
+
[1.0000000000000000.to_d, -0.1055613458156586.to_d, -0.0638541728258133.to_d],
|
39
|
+
[1.0000000000000000.to_d, -0.0894841775298119.to_d, -1.2914855480194092.to_d]
|
40
|
+
]
|
41
|
+
|
42
|
+
l_lms, m_lms, s_lms = (lab_to_lms_matrix * ::Matrix.column_vector([l, a, b])).to_a.flatten
|
43
|
+
|
44
|
+
l_lms **= 3.to_d
|
45
|
+
m_lms **= 3.to_d
|
46
|
+
s_lms **= 3.to_d
|
47
|
+
|
48
|
+
lms_to_xyz_matrix = ::Matrix[
|
49
|
+
[1.2268798758459243.to_d, -0.5578149944602171.to_d, 0.2813910456659647.to_d],
|
50
|
+
[-0.0405757452148008.to_d, 1.1122868032803170.to_d, -0.0717110580655164.to_d],
|
51
|
+
[-0.0763729366746601.to_d, -0.4214933324022432.to_d, 1.5869240198367816.to_d]
|
52
|
+
]
|
53
|
+
|
54
|
+
x, y, z = (lms_to_xyz_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
|
55
|
+
|
56
|
+
x *= 100.0.to_d
|
57
|
+
y *= 100.0.to_d
|
58
|
+
z *= 100.0.to_d
|
59
|
+
|
60
|
+
[x, y, z]
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.xyz_to_oklab(xyz_array)
|
64
|
+
x, y, z = xyz_array.map(&:to_d)
|
65
|
+
|
66
|
+
# The transformation matrix expects normalised X, Y, Z values.
|
67
|
+
x /= 100.0.to_d
|
68
|
+
y /= 100.0.to_d
|
69
|
+
z /= 100.0.to_d
|
70
|
+
|
71
|
+
# Given XYZ relative to D65, convert to OKLab
|
72
|
+
xyz_to_lms_matrix = ::Matrix[
|
73
|
+
[0.8190224379967030.to_d, 0.3619062600528904.to_d, -0.1288737815209879.to_d],
|
74
|
+
[0.0329836539323885.to_d, 0.9292868615863434.to_d, 0.0361446663506424.to_d],
|
75
|
+
[0.0481771893596242.to_d, 0.2642395317527308.to_d, 0.6335478284694309.to_d]
|
76
|
+
]
|
77
|
+
|
78
|
+
l_lms, m_lms, s_lms = (xyz_to_lms_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
|
79
|
+
|
80
|
+
l_lms **= (1.0.to_d / 3.0.to_d)
|
81
|
+
m_lms **= (1.0.to_d / 3.0.to_d)
|
82
|
+
s_lms **= (1.0.to_d / 3.0.to_d)
|
83
|
+
|
84
|
+
lms_to_lab_matrix = ::Matrix[
|
85
|
+
[0.2104542683093140.to_d, 0.7936177747023054.to_d, -0.0040720430116193.to_d],
|
86
|
+
[1.9779985324311684.to_d, -2.4285922420485799.to_d, 0.4505937096174110.to_d],
|
87
|
+
[0.0259040424655478.to_d, 0.7827717124575296.to_d, -0.8086757549230774.to_d]
|
88
|
+
]
|
89
|
+
|
90
|
+
l_lab, a_lab, b_lab = (lms_to_lab_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
|
91
|
+
|
92
|
+
# Now, scale l to a percentage
|
93
|
+
l_lab *= 100.0.to_d
|
94
|
+
|
95
|
+
[l_lab, a_lab, b_lab]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -3,11 +3,11 @@ module ColorConverters
|
|
3
3
|
def self.matches?(color_input)
|
4
4
|
return false unless color_input.is_a?(Hash)
|
5
5
|
|
6
|
-
color_input.keys - [:l, :c, :h] == []
|
6
|
+
color_input.keys - [:l, :c, :h, :space] == [] && color_input[:space].to_s == 'ok'
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.bounds
|
10
|
-
{ l: [0.0, 100.0], c: [0.0,
|
10
|
+
{ l: [0.0, 100.0], c: [0.0, 500.0], h: [0.0, 360.0] }
|
11
11
|
end
|
12
12
|
|
13
13
|
private
|
@@ -18,34 +18,40 @@ module ColorConverters
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def input_to_rgba(color_input)
|
21
|
-
|
22
|
-
|
23
|
-
XyzConverter.
|
21
|
+
l, a, b = OklchConverter.oklch_to_oklab(color_input)
|
22
|
+
x, y, z = OklabConverter.oklab_to_xyz({ l: l, a: a, b: b })
|
23
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
24
|
+
|
25
|
+
[r, g, b, 1.0]
|
24
26
|
end
|
25
27
|
|
26
|
-
def self.
|
27
|
-
l = color_input[:l].
|
28
|
-
c = color_input[:c].
|
29
|
-
h = color_input[:h].
|
28
|
+
def self.oklch_to_oklab(color_input)
|
29
|
+
l = color_input[:l].to_d
|
30
|
+
c = color_input[:c].to_d
|
31
|
+
h = color_input[:h].to_d
|
30
32
|
|
31
|
-
h_rad = h * (Math::PI / 180.0)
|
33
|
+
h_rad = h * (Math::PI.to_d / 180.0.to_d)
|
32
34
|
|
33
|
-
a = c * Math.cos(h_rad)
|
34
|
-
b = c * Math.sin(h_rad)
|
35
|
+
a = c * Math.cos(h_rad).to_d
|
36
|
+
b = c * Math.sin(h_rad).to_d
|
35
37
|
|
36
|
-
|
38
|
+
[l, a, b]
|
37
39
|
end
|
38
40
|
|
39
|
-
def self.
|
40
|
-
l, aa, bb = lab_array
|
41
|
+
def self.oklab_to_oklch(lab_array)
|
42
|
+
l, aa, bb = lab_array.map(&:to_d)
|
43
|
+
|
44
|
+
e = 0.000015.to_d; # if chroma is smaller than this, set hue to 0 similar to CIELch
|
41
45
|
|
42
|
-
c = ((aa**2) + (bb**2))**0.5
|
46
|
+
c = ((aa**2.to_d) + (bb**2.to_d))**0.5.to_d
|
43
47
|
|
44
|
-
h_rad = Math.atan2(bb, aa)
|
45
|
-
h = h_rad * (180.0 / Math::PI)
|
48
|
+
h_rad = Math.atan2(bb, aa).to_d
|
49
|
+
h = h_rad * (180.0.to_d / Math::PI.to_d)
|
46
50
|
|
47
51
|
h %= 360
|
48
52
|
|
53
|
+
h = 0 if c < e
|
54
|
+
|
49
55
|
[l, c, h]
|
50
56
|
end
|
51
57
|
end
|
@@ -23,7 +23,84 @@ module ColorConverters
|
|
23
23
|
b = color_input[:b].to_f
|
24
24
|
a = (color_input[:a] || 1.0).to_f
|
25
25
|
|
26
|
-
|
26
|
+
[r, g, b, a]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.rgb_to_lrgb(rgb_array_frac)
|
30
|
+
# [0, 1]
|
31
|
+
r, g, b = rgb_array_frac
|
32
|
+
|
33
|
+
# Inverse sRGB companding. Linearizes RGB channels with respect to energy.
|
34
|
+
# Assumption that r, g, b are always positive
|
35
|
+
rr, gg, bb = [r, g, b].map do
|
36
|
+
if _1.to_d <= 0.04045.to_d
|
37
|
+
_1.to_d / 12.92.to_d
|
38
|
+
else
|
39
|
+
# sRGB Inverse Companding (Non-linear to Linear RGB)
|
40
|
+
# The sRGB specification (IEC 61966-2-1) defines the exponent as 2.4.
|
41
|
+
#
|
42
|
+
(((_1.to_d + 0.055.to_d) / 1.055.to_d)**2.4.to_d)
|
43
|
+
|
44
|
+
# IMPORTANT NUMERICAL NOTE:
|
45
|
+
# On this specific system (and confirmed by Wolfram Alpha for direct calculation),
|
46
|
+
# the power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
|
47
|
+
#
|
48
|
+
# To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
|
49
|
+
# an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
|
50
|
+
#
|
51
|
+
# Choose 2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
|
52
|
+
# or choose 2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
|
53
|
+
#
|
54
|
+
# ((_1 + 0.055) / 1.055)**2.5
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# [0, 1]
|
59
|
+
[rr, gg, bb]
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.lrgb_to_rgb(lrgb_array)
|
63
|
+
rr, gg, bb = lrgb_array
|
64
|
+
|
65
|
+
# Apply sRGB Companding (gamma correction) to convert from Linear RGB to non-linear sRGB.
|
66
|
+
# This is defined by the sRGB specification (IEC 61966-2-1).
|
67
|
+
# The exponent for the non-linear segment is 1/2.4 (approximately 0.41666...).
|
68
|
+
# Assumption that rr, gg, bb are always positive
|
69
|
+
r, g, b = [rr, gg, bb].map do
|
70
|
+
if _1.to_d <= 0.0031308.to_d
|
71
|
+
# Linear portion of the sRGB curve
|
72
|
+
_1.to_d * 12.92.to_d
|
73
|
+
else
|
74
|
+
# Non-linear (gamma-corrected) portion of the sRGB curve
|
75
|
+
# The sRGB specification uses an exponent of 1/2.4.
|
76
|
+
#
|
77
|
+
(1.055.to_d * (_1.to_d**(1.0.to_d / 2.4.to_d))) - 0.055.to_d
|
78
|
+
|
79
|
+
# IMPORTANT NUMERICAL NOTE:
|
80
|
+
# On this specific system (and confirmed by Wolfram Alpha for direct calculation),
|
81
|
+
# the inverse power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
|
82
|
+
#
|
83
|
+
# To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
|
84
|
+
# an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
|
85
|
+
#
|
86
|
+
# Choose 1/2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
|
87
|
+
# or choose 1/2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
|
88
|
+
#
|
89
|
+
# (1.055 * (_1**(1.0 / 2.5))) - 0.055
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Scale the 0-1 sRGB value to the 0-255 range for 8-bit color components.
|
94
|
+
r *= 255.0.to_d
|
95
|
+
g *= 255.0.to_d
|
96
|
+
b *= 255.0.to_d
|
97
|
+
|
98
|
+
# Clamping RGB values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
|
99
|
+
r = r.clamp(0.0..255.0)
|
100
|
+
g = g.clamp(0.0..255.0)
|
101
|
+
b = b.clamp(0.0..255.0)
|
102
|
+
|
103
|
+
[r, g, b]
|
27
104
|
end
|
28
105
|
end
|
29
106
|
end
|
@@ -8,7 +8,7 @@ module ColorConverters
|
|
8
8
|
|
9
9
|
private
|
10
10
|
|
11
|
-
def validate_input(
|
11
|
+
def validate_input(_color_input)
|
12
12
|
true
|
13
13
|
end
|
14
14
|
|
@@ -21,12 +21,12 @@ module ColorConverters
|
|
21
21
|
|
22
22
|
a ||= 1.0
|
23
23
|
|
24
|
-
r = r.to_f
|
25
|
-
g = g.to_f
|
26
|
-
b = b.to_f
|
27
|
-
a = a.to_f
|
24
|
+
r = r.to_f
|
25
|
+
g = g.to_f
|
26
|
+
b = b.to_f
|
27
|
+
a = a.to_f
|
28
28
|
|
29
|
-
|
29
|
+
[r, g, b, a]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -6,8 +6,12 @@ module ColorConverters
|
|
6
6
|
color_input.keys - [:x, :y, :z] == []
|
7
7
|
end
|
8
8
|
|
9
|
+
def self.d65
|
10
|
+
{ x: 95.047, y: 100.0, z: 108.883 }
|
11
|
+
end
|
12
|
+
|
9
13
|
def self.bounds
|
10
|
-
{ x: [0.0,
|
14
|
+
{ x: [0.0, 100.0], y: [0.0, 100.0], z: [0.0, 110.0] }
|
11
15
|
end
|
12
16
|
|
13
17
|
private
|
@@ -18,98 +22,50 @@ module ColorConverters
|
|
18
22
|
end
|
19
23
|
|
20
24
|
def input_to_rgba(color_input)
|
21
|
-
r, g, b = xyz_to_rgb(color_input)
|
25
|
+
r, g, b = XyzConverter.xyz_to_rgb(color_input)
|
22
26
|
|
23
|
-
|
27
|
+
[r, g, b, 1.0]
|
24
28
|
end
|
25
29
|
|
26
|
-
def xyz_to_rgb(xyz_hash)
|
30
|
+
def self.xyz_to_rgb(xyz_hash)
|
31
|
+
# [0, 100]
|
32
|
+
x = xyz_hash[:x].to_d
|
33
|
+
y = xyz_hash[:y].to_d
|
34
|
+
z = xyz_hash[:z].to_d
|
35
|
+
|
27
36
|
# Convert XYZ (typically with Y=100 for white) to normalized XYZ (Y=1 for white).
|
28
37
|
# The transformation matrix expects X, Y, Z values in the 0-1 range.
|
29
|
-
x
|
30
|
-
y
|
31
|
-
z
|
32
|
-
|
33
|
-
# Convert normalized XYZ to Linear sRGB values
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
# Linear portion of the sRGB curve
|
46
|
-
_1 * 12.92
|
47
|
-
else
|
48
|
-
# Non-linear (gamma-corrected) portion of the sRGB curve
|
49
|
-
# The sRGB specification uses an exponent of 1/2.4.
|
50
|
-
#
|
51
|
-
(1.055 * (_1**(1.0 / 2.4))) - 0.055
|
52
|
-
|
53
|
-
# IMPORTANT NUMERICAL NOTE:
|
54
|
-
# On this specific system (and confirmed by Wolfram Alpha for direct calculation),
|
55
|
-
# the inverse power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
|
56
|
-
#
|
57
|
-
# To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
|
58
|
-
# an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
|
59
|
-
#
|
60
|
-
# Choose 1/2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
|
61
|
-
# or choose 1/2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
|
62
|
-
#
|
63
|
-
# (1.055 * (_1**(1.0 / 2.5))) - 0.055
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
# Scale the 0-1 sRGB value to the 0-255 range for 8-bit color components.
|
68
|
-
r *= 255.0
|
69
|
-
g *= 255.0
|
70
|
-
b *= 255.0
|
71
|
-
|
72
|
-
# Clamping RGB values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
|
73
|
-
r = r.clamp(0.0..255.0)
|
74
|
-
g = g.clamp(0.0..255.0)
|
75
|
-
b = b.clamp(0.0..255.0)
|
76
|
-
|
77
|
-
[r, g, b]
|
38
|
+
x /= 100.0.to_d
|
39
|
+
y /= 100.0.to_d
|
40
|
+
z /= 100.0.to_d
|
41
|
+
|
42
|
+
# Convert normalized XYZ to Linear sRGB values using sRGB's own white, D65 (no chromatic adaptation)
|
43
|
+
# https://www.w3.org/TR/css-color-4/#color-conversion-code
|
44
|
+
conversion_matrix = ::Matrix[
|
45
|
+
[BigDecimal('3.2409699419045213'), BigDecimal('-1.5373831775700935'), BigDecimal('-0.4986107602930033')],
|
46
|
+
[BigDecimal('-0.9692436362808798'), BigDecimal('1.8759675015077206'), BigDecimal('0.04155505740717561')],
|
47
|
+
[BigDecimal('0.05563007969699361'), BigDecimal('-0.20397695888897657'), BigDecimal('1.0569715142428786')]
|
48
|
+
]
|
49
|
+
|
50
|
+
rr, gg, bb = (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
|
51
|
+
|
52
|
+
# [0, 1]
|
53
|
+
RgbConverter.lrgb_to_rgb([rr, gg, bb])
|
78
54
|
end
|
79
55
|
|
80
56
|
# http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
|
81
57
|
def self.rgb_to_xyz(rgb_array_frac)
|
82
|
-
|
83
|
-
|
84
|
-
#
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
# IMPORTANT NUMERICAL NOTE:
|
95
|
-
# On this specific system (and confirmed by Wolfram Alpha for direct calculation),
|
96
|
-
# the power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
|
97
|
-
#
|
98
|
-
# To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
|
99
|
-
# an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
|
100
|
-
#
|
101
|
-
# Choose 2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
|
102
|
-
# or choose 2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
|
103
|
-
#
|
104
|
-
# ((_1 + 0.055) / 1.055)**2.5
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# Convert using the RGB/XYZ matrix at:
|
109
|
-
# http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices
|
110
|
-
x = (rr * 0.4124564) + (gg * 0.3575761) + (bb * 0.1804375)
|
111
|
-
y = (rr * 0.2126729) + (gg * 0.7151522) + (bb * 0.0721750)
|
112
|
-
z = (rr * 0.0193339) + (gg * 0.1191920) + (bb * 0.9503041)
|
58
|
+
rr, gg, bb = RgbConverter.rgb_to_lrgb(rgb_array_frac)
|
59
|
+
|
60
|
+
# Convert using the RGB/XYZ matrix and sRGB's own white, D65 (no chromatic adaptation)
|
61
|
+
# https://www.w3.org/TR/css-color-4/#color-conversion-code
|
62
|
+
conversion_matrix = ::Matrix[
|
63
|
+
[BigDecimal('0.4123907992659595'), BigDecimal('0.35758433938387796'), BigDecimal('0.1804807884018343')],
|
64
|
+
[BigDecimal('0.21263900587151036'), BigDecimal('0.7151686787677559'), BigDecimal('0.07219231536073371')],
|
65
|
+
[BigDecimal('0.01933081871559185'), BigDecimal('0.11919477979462599'), BigDecimal('0.9505321522496606')]
|
66
|
+
]
|
67
|
+
|
68
|
+
x, y, z = (conversion_matrix * ::Matrix.column_vector([rr, gg, bb])).to_a.flatten
|
113
69
|
|
114
70
|
# Now, scale X, Y, Z so that Y for D65 white would be 100.
|
115
71
|
x *= 100.0
|
@@ -117,11 +73,35 @@ module ColorConverters
|
|
117
73
|
z *= 100.0
|
118
74
|
|
119
75
|
# Clamping XYZ values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
|
120
|
-
x = x.clamp(0.0..95.047)
|
121
|
-
y = y.clamp(0.0..100.0)
|
122
|
-
z = z.clamp(0.0..108.883)
|
76
|
+
# x = x.clamp(0.0..95.047)
|
77
|
+
# y = y.clamp(0.0..100.0)
|
78
|
+
# z = z.clamp(0.0..108.883)
|
123
79
|
|
124
80
|
[x, y, z]
|
125
81
|
end
|
82
|
+
|
83
|
+
def self.d50_to_d65(xyz_array)
|
84
|
+
x, y, z = xyz_array
|
85
|
+
|
86
|
+
conversion_matrix = ::Matrix[
|
87
|
+
[BigDecimal('0.955473421488075'), BigDecimal('-0.02309845494876471'), BigDecimal('0.06325924320057072')],
|
88
|
+
[BigDecimal('-0.0283697093338637'), BigDecimal('1.0099953980813041'), BigDecimal('0.021041441191917323')],
|
89
|
+
[BigDecimal('0.012314014864481998'), BigDecimal('-0.020507649298898964'), BigDecimal('1.330365926242124')]
|
90
|
+
]
|
91
|
+
|
92
|
+
(conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.d65_to_d50(xyz_array)
|
96
|
+
x, y, z = xyz_array
|
97
|
+
|
98
|
+
conversion_matrix = ::Matrix[
|
99
|
+
[BigDecimal('1.0479297925449969'), BigDecimal('0.022946870601609652'), BigDecimal('-0.05019226628920524')],
|
100
|
+
[BigDecimal('0.02962780877005599'), BigDecimal('0.9904344267538799'), BigDecimal('-0.017073799063418826')],
|
101
|
+
[BigDecimal('-0.009243040646204504'), BigDecimal('0.015055191490298152'), BigDecimal('0.7518742814281371')]
|
102
|
+
]
|
103
|
+
|
104
|
+
(conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
|
105
|
+
end
|
126
106
|
end
|
127
107
|
end
|
data/lib/color_converters.rb
CHANGED
@@ -16,6 +16,8 @@ require 'color_converters/converters/hsv_converter'
|
|
16
16
|
require 'color_converters/converters/cmyk_converter'
|
17
17
|
require 'color_converters/converters/xyz_converter'
|
18
18
|
require 'color_converters/converters/cielab_converter'
|
19
|
+
require 'color_converters/converters/cielch_converter'
|
20
|
+
require 'color_converters/converters/oklab_converter'
|
19
21
|
require 'color_converters/converters/oklch_converter'
|
20
22
|
|
21
23
|
require 'color_converters/converters/name_converter'
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: color_converters
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Louis Davis
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-08-18 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: activesupport
|
@@ -23,6 +23,20 @@ dependencies:
|
|
23
23
|
- - "~>"
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: 8.0.2
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: matrix
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
26
40
|
description: Convert colors to hex/rgb/hsv/cmyk/hsl/xyz/cielab/oklch
|
27
41
|
email:
|
28
42
|
- LouisWilliamDavis@gmail.com
|
@@ -36,6 +50,7 @@ files:
|
|
36
50
|
- lib/color_converters/base_converter.rb
|
37
51
|
- lib/color_converters/color.rb
|
38
52
|
- lib/color_converters/converters/cielab_converter.rb
|
53
|
+
- lib/color_converters/converters/cielch_converter.rb
|
39
54
|
- lib/color_converters/converters/cmyk_converter.rb
|
40
55
|
- lib/color_converters/converters/hex_converter.rb
|
41
56
|
- lib/color_converters/converters/hsl_converter.rb
|
@@ -43,6 +58,7 @@ files:
|
|
43
58
|
- lib/color_converters/converters/hsv_converter.rb
|
44
59
|
- lib/color_converters/converters/name_converter.rb
|
45
60
|
- lib/color_converters/converters/null_converter.rb
|
61
|
+
- lib/color_converters/converters/oklab_converter.rb
|
46
62
|
- lib/color_converters/converters/oklch_converter.rb
|
47
63
|
- lib/color_converters/converters/rgb_converter.rb
|
48
64
|
- lib/color_converters/converters/rgb_string_converter.rb
|