color_converters 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 +4 -4
- data/README.md +17 -2
- data/lib/color_converters/base_converter.rb +25 -9
- data/lib/color_converters/color.rb +1 -1
- data/lib/color_converters/converters/cielab_converter.rb +35 -31
- data/lib/color_converters/converters/cielch_converter.rb +21 -15
- 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 +58 -0
- 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
|
@@ -68,6 +70,12 @@ color = ColorConverters::Color.new(l: 16, a: 44, b: 32, space: :cie)
|
|
68
70
|
# from cielch
|
69
71
|
color = ColorConverters::Color.new(l: 16, c: 44, h: 32, space: :cie)
|
70
72
|
|
73
|
+
# from oklab
|
74
|
+
color = ColorConverters::Color.new(l: 16, a: 44, b: 32, space: :ok)
|
75
|
+
|
76
|
+
# from oklch
|
77
|
+
color = ColorConverters::Color.new(l: 16, c: 44, h: 32, space: :ok)
|
78
|
+
|
71
79
|
# from textual color
|
72
80
|
color = ColorConverters::Color.new("blue")
|
73
81
|
|
@@ -112,6 +120,12 @@ color.cielab
|
|
112
120
|
color.cielch
|
113
121
|
=> {:l=>52.47, :c=>32.45, :h=>262.78}
|
114
122
|
|
123
|
+
color.oklab # not always accurate
|
124
|
+
=> {:l=>52.47, :a=>-4.08, :b=>-32.19}
|
125
|
+
|
126
|
+
color.oklch # not always accurate
|
127
|
+
=> {:l=>52.47, :c=>32.45, :h=>262.78}
|
128
|
+
|
115
129
|
color.hex
|
116
130
|
=> "#4682b4"
|
117
131
|
|
@@ -128,6 +142,7 @@ The space parameter allows that, with examples in the usage code above
|
|
128
142
|
|
129
143
|
```ruby
|
130
144
|
ColorConverters::Color.new(l: 64, a: 28, b: -15, space: :cie)
|
145
|
+
ColorConverters::Color.new(l: 64, a: 28, b: -15, space: :ok)
|
131
146
|
```
|
132
147
|
|
133
148
|
### limit_override
|
@@ -141,7 +156,7 @@ ColorConverters::Color.new(r: 270, g: 1300, b: 380, a: 0.5, limit_override: true
|
|
141
156
|
|
142
157
|
## Development
|
143
158
|
|
144
|
-
[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).
|
145
160
|
|
146
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.
|
147
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
|
@@ -32,11 +34,13 @@ module ColorConverters
|
|
32
34
|
raise InvalidColorError # validation method is defined in each convertor
|
33
35
|
end
|
34
36
|
|
35
|
-
|
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) }
|
36
40
|
end
|
37
41
|
|
38
42
|
def rgb
|
39
|
-
{ 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) }
|
40
44
|
end
|
41
45
|
|
42
46
|
def hex
|
@@ -46,13 +50,13 @@ module ColorConverters
|
|
46
50
|
def hsl
|
47
51
|
@r, @g, @b = self.rgb_array_frac
|
48
52
|
|
49
|
-
{ 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) }
|
50
54
|
end
|
51
55
|
|
52
56
|
def hsv
|
53
57
|
@r, @g, @b = self.rgb_array
|
54
58
|
|
55
|
-
{ 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) }
|
56
60
|
end
|
57
61
|
|
58
62
|
def hsb
|
@@ -64,25 +68,37 @@ module ColorConverters
|
|
64
68
|
def cmyk
|
65
69
|
c, m, y, k = CmykConverter.rgb_to_cmyk(self.rgb_array_frac)
|
66
70
|
|
67
|
-
{ 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) }
|
68
72
|
end
|
69
73
|
|
70
74
|
def xyz
|
71
75
|
x, y, z = XyzConverter.rgb_to_xyz(self.rgb_array_frac)
|
72
76
|
|
73
|
-
{ 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) }
|
74
78
|
end
|
75
79
|
|
76
80
|
def cielab
|
77
81
|
l, a, b = CielabConverter.xyz_to_cielab(XyzConverter.rgb_to_xyz(self.rgb_array_frac))
|
78
82
|
|
79
|
-
{ l: l.round(OUTPUT_DP), a: a.round(OUTPUT_DP), b: b.round(OUTPUT_DP) }
|
83
|
+
{ l: l.to_f.round(OUTPUT_DP), a: a.to_f.round(OUTPUT_DP), b: b.to_f.round(OUTPUT_DP) }
|
80
84
|
end
|
81
85
|
|
82
86
|
def cielch
|
83
87
|
l, c, h = CielchConverter.cielab_to_cielch(CielabConverter.xyz_to_cielab(XyzConverter.rgb_to_xyz(self.rgb_array_frac)))
|
84
88
|
|
85
|
-
{ l: l.round(OUTPUT_DP), c: c.round(OUTPUT_DP), h: h.round(OUTPUT_DP) }
|
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))
|
94
|
+
|
95
|
+
{ l: l.to_f.round(OUTPUT_DP), a: a.to_f.round(OUTPUT_DP), b: b.to_f.round(OUTPUT_DP) }
|
96
|
+
end
|
97
|
+
|
98
|
+
def oklch
|
99
|
+
l, c, h = OklchConverter.oklab_to_oklch(OklabConverter.xyz_to_oklab(XyzConverter.rgb_to_xyz(self.rgb_array_frac)))
|
100
|
+
|
101
|
+
{ l: l.to_f.round(OUTPUT_DP), c: c.to_f.round(OUTPUT_DP), h: h.to_f.round(OUTPUT_DP) }
|
86
102
|
end
|
87
103
|
|
88
104
|
def alpha
|
@@ -90,7 +106,7 @@ module ColorConverters
|
|
90
106
|
end
|
91
107
|
|
92
108
|
def name
|
93
|
-
NameConverter.rgb_to_name(self.
|
109
|
+
NameConverter.rgb_to_name(self.rgb_array)
|
94
110
|
end
|
95
111
|
|
96
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, :cielch, :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)
|
@@ -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
27
|
def self.cielab_to_xyz(color_input)
|
26
|
-
l = color_input[:l].
|
27
|
-
a = color_input[:a].
|
28
|
-
b = color_input[:b].
|
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
53
|
def self.xyz_to_cielab(xyz_array)
|
52
|
-
x, y, z = 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
|
@@ -7,7 +7,7 @@ module ColorConverters
|
|
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, 150.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 = 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]
|
24
26
|
end
|
25
27
|
|
26
28
|
def self.cielch_to_cielab(color_input)
|
27
|
-
l = color_input[:l].
|
28
|
-
c = color_input[:c].
|
29
|
-
h = color_input[:h].
|
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
41
|
def self.cielab_to_cielch(lab_array)
|
40
|
-
l, aa, bb = 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]
|
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
|
@@ -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
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module ColorConverters
|
2
|
+
class OklchConverter < 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 == 'ok'
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.bounds
|
10
|
+
{ l: [0.0, 100.0], c: [0.0, 500.0], h: [0.0, 360.0] }
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def validate_input(color_input)
|
16
|
+
bounds = OklchConverter.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 = 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]
|
26
|
+
end
|
27
|
+
|
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
|
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.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
|
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
|
@@ -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
@@ -17,6 +17,8 @@ require 'color_converters/converters/cmyk_converter'
|
|
17
17
|
require 'color_converters/converters/xyz_converter'
|
18
18
|
require 'color_converters/converters/cielab_converter'
|
19
19
|
require 'color_converters/converters/cielch_converter'
|
20
|
+
require 'color_converters/converters/oklab_converter'
|
21
|
+
require 'color_converters/converters/oklch_converter'
|
20
22
|
|
21
23
|
require 'color_converters/converters/name_converter'
|
22
24
|
require 'color_converters/converters/null_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
|
@@ -44,6 +58,8 @@ files:
|
|
44
58
|
- lib/color_converters/converters/hsv_converter.rb
|
45
59
|
- lib/color_converters/converters/name_converter.rb
|
46
60
|
- lib/color_converters/converters/null_converter.rb
|
61
|
+
- lib/color_converters/converters/oklab_converter.rb
|
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
|
49
65
|
- lib/color_converters/converters/xyz_converter.rb
|