color_converters 0.1.3 → 0.1.4

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.
@@ -1,185 +1,67 @@
1
- module ColorConverters
2
- class NameConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(String)
5
-
6
- self.color_names.include?(color_input.downcase.to_sym)
7
- end
8
-
9
- def self.rgb_to_name(rgb_array)
10
- r, g, b = rgb_array
11
-
12
- name = self.color_names.find { |_k, v| v == [r.round, g.round, b.round] }
13
- name.present? ? name[0].to_s : nil
14
- end
15
-
16
- private
17
-
18
- def validate_input(_color_input)
19
- # TODO: validate against list of keys?
20
- true
21
- end
22
-
23
- def input_to_rgba(color_input)
24
- found_colour = self.class.color_names[color_input.downcase.to_sym]
25
- raise InvalidColorError unless found_colour.present?
26
-
27
- r, g, b = found_colour
28
-
29
- [r, g, b, 1.0]
30
- end
31
-
32
- # TODO: use color_namer_ruby gem instead, but that gem also uses this gem
33
- def self.color_names
34
- {
35
- aliceblue: [240, 248, 255],
36
- antiquewhite: [250, 235, 215],
37
- aqua: [0, 255, 255],
38
- aquamarine: [127, 255, 212],
39
- azure: [240, 255, 255],
40
- beige: [245, 245, 220],
41
- bisque: [255, 228, 196],
42
- black: [0, 0, 0],
43
- blanchedalmond: [255, 235, 205],
44
- blue: [0, 0, 255],
45
- blueviolet: [138, 43, 226],
46
- brown: [165, 42, 42],
47
- burlywood: [222, 184, 135],
48
- cadetblue: [95, 158, 160],
49
- chartreuse: [127, 255, 0],
50
- chocolate: [210, 105, 30],
51
- coral: [255, 127, 80],
52
- cornflowerblue: [100, 149, 237],
53
- cornsilk: [255, 248, 220],
54
- crimson: [220, 20, 60],
55
- cyan: [0, 255, 255],
56
- darkblue: [0, 0, 139],
57
- darkcyan: [0, 139, 139],
58
- darkgoldenrod: [184, 134, 11],
59
- darkgray: [169, 169, 169],
60
- darkgreen: [0, 100, 0],
61
- darkgrey: [169, 169, 169],
62
- darkkhaki: [189, 183, 107],
63
- darkmagenta: [139, 0, 139],
64
- darkolivegreen: [85, 107, 47],
65
- darkorange: [255, 140, 0],
66
- darkorchid: [153, 50, 204],
67
- darkred: [139, 0, 0],
68
- darksalmon: [233, 150, 122],
69
- darkseagreen: [143, 188, 143],
70
- darkslateblue: [72, 61, 139],
71
- darkslategray: [47, 79, 79],
72
- darkslategrey: [47, 79, 79],
73
- darkturquoise: [0, 206, 209],
74
- darkviolet: [148, 0, 211],
75
- deeppink: [255, 20, 147],
76
- deepskyblue: [0, 191, 255],
77
- dimgray: [105, 105, 105],
78
- dimgrey: [105, 105, 105],
79
- dodgerblue: [30, 144, 255],
80
- firebrick: [178, 34, 34],
81
- floralwhite: [255, 250, 240],
82
- forestgreen: [34, 139, 34],
83
- fuchsia: [255, 0, 255],
84
- gainsboro: [220, 220, 220],
85
- ghostwhite: [248, 248, 255],
86
- gold: [255, 215, 0],
87
- goldenrod: [218, 165, 32],
88
- gray: [128, 128, 128],
89
- green: [0, 128, 0],
90
- greenyellow: [173, 255, 47],
91
- grey: [128, 128, 128],
92
- honeydew: [240, 255, 240],
93
- hotpink: [255, 105, 180],
94
- indianred: [205, 92, 92],
95
- indigo: [75, 0, 130],
96
- ivory: [255, 255, 240],
97
- khaki: [240, 230, 140],
98
- lavender: [230, 230, 250],
99
- lavenderblush: [255, 240, 245],
100
- lawngreen: [124, 252, 0],
101
- lemonchiffon: [255, 250, 205],
102
- lightblue: [173, 216, 230],
103
- lightcoral: [240, 128, 128],
104
- lightcyan: [224, 255, 255],
105
- lightgoldenrodyellow: [250, 250, 210],
106
- lightgray: [211, 211, 211],
107
- lightgreen: [144, 238, 144],
108
- lightgrey: [211, 211, 211],
109
- lightpink: [255, 182, 193],
110
- lightsalmon: [255, 160, 122],
111
- lightseagreen: [32, 178, 170],
112
- lightskyblue: [135, 206, 250],
113
- lightslategray: [119, 136, 153],
114
- lightslategrey: [119, 136, 153],
115
- lightsteelblue: [176, 196, 222],
116
- lightyellow: [255, 255, 224],
117
- lime: [0, 255, 0],
118
- limegreen: [50, 205, 50],
119
- linen: [250, 240, 230],
120
- magenta: [255, 0, 255],
121
- maroon: [128, 0, 0],
122
- mediumaquamarine: [102, 205, 170],
123
- mediumblue: [0, 0, 205],
124
- mediumorchid: [186, 85, 211],
125
- mediumpurple: [147, 112, 219],
126
- mediumseagreen: [60, 179, 113],
127
- mediumslateblue: [123, 104, 238],
128
- mediumspringgreen: [0, 250, 154],
129
- mediumturquoise: [72, 209, 204],
130
- mediumvioletred: [199, 21, 133],
131
- midnightblue: [25, 25, 112],
132
- mintcream: [245, 255, 250],
133
- mistyrose: [255, 228, 225],
134
- moccasin: [255, 228, 181],
135
- navajowhite: [255, 222, 173],
136
- navy: [0, 0, 128],
137
- oldlace: [253, 245, 230],
138
- olive: [128, 128, 0],
139
- olivedrab: [107, 142, 35],
140
- orange: [255, 165, 0],
141
- orangered: [255, 69, 0],
142
- orchid: [218, 112, 214],
143
- palegoldenrod: [238, 232, 170],
144
- palegreen: [152, 251, 152],
145
- paleturquoise: [175, 238, 238],
146
- palevioletred: [219, 112, 147],
147
- papayawhip: [255, 239, 213],
148
- peachpuff: [255, 218, 185],
149
- peru: [205, 133, 63],
150
- pink: [255, 192, 203],
151
- plum: [221, 160, 221],
152
- powderblue: [176, 224, 230],
153
- purple: [128, 0, 128],
154
- red: [255, 0, 0],
155
- rosybrown: [188, 143, 143],
156
- royalblue: [65, 105, 225],
157
- saddlebrown: [139, 69, 19],
158
- salmon: [250, 128, 114],
159
- sandybrown: [244, 164, 96],
160
- seagreen: [46, 139, 87],
161
- seashell: [255, 245, 238],
162
- sienna: [160, 82, 45],
163
- silver: [192, 192, 192],
164
- skyblue: [135, 206, 235],
165
- slateblue: [106, 90, 205],
166
- slategray: [112, 128, 144],
167
- slategrey: [112, 128, 144],
168
- snow: [255, 250, 250],
169
- springgreen: [0, 255, 127],
170
- steelblue: [70, 130, 180],
171
- tan: [210, 180, 140],
172
- teal: [0, 128, 128],
173
- thistle: [216, 191, 216],
174
- tomato: [255, 99, 71],
175
- turquoise: [64, 224, 208],
176
- violet: [238, 130, 238],
177
- wheat: [245, 222, 179],
178
- white: [255, 255, 255],
179
- whitesmoke: [245, 245, 245],
180
- yellow: [255, 255, 0],
181
- yellowgreen: [154, 205, 50]
182
- }
183
- end
184
- end
185
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'color_swatch_collection'
4
+
5
+ module ColorConverters
6
+ class NameConverter < BaseConverter
7
+ def self.matches?(colour_input)
8
+ return false unless colour_input.is_a?(String)
9
+
10
+ !colour_input.include?('#') && !colour_input.include?('rgb') && !colour_input.include?('hsl')
11
+ end
12
+
13
+ def self.rgb_to_name(rgb_array, fuzzy = false)
14
+ if fuzzy
15
+ source_colour = ColorConverters::Color.new({ r: rgb_array[0], g: rgb_array[1], b: rgb_array[2] })
16
+
17
+ collection_colours = []
18
+
19
+ ::ColorSwatchCollection.list_collections.each do |collection_name|
20
+ collection_colours += self.add_colour_distances_to_collection(Object.const_get("::ColorSwatchCollection::#{collection_name.capitalize}").colours, source_colour)
21
+ end
22
+
23
+ collection_colours.min_by { |swatch| swatch[:distance] }.dig(:name)
24
+ else
25
+ ::ColorSwatchCollection.get_from_hex(HexConverter.rgb_to_hex(rgb_array)).dig(:name)
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def validate_input(colour_input)
32
+ self.class.match_name_from_palettes(colour_input).present? ? [] : ['name could not be found across colour collections']
33
+ end
34
+
35
+ def input_to_rgba(colour_input)
36
+ found_colour = self.class.match_name_from_palettes(colour_input)
37
+
38
+ raise InvalidColorError unless found_colour.present?
39
+
40
+ HexConverter.hex_to_rgba(found_colour)
41
+ end
42
+
43
+ # this is a checking for a direct naming match against the ColorSwatchCollection
44
+ def self.match_name_from_palettes(colour_name)
45
+ ::ColorSwatchCollection.get_from_name(colour_name).dig(:hex)
46
+ end
47
+
48
+ def self.add_colour_distances_to_collection(collection_colours, source_colour)
49
+ collection_colours.map do |swatch|
50
+ swatch[:distance] = self.distance_between_colours(ColorConverters::Color.new(swatch.dig(:hex)), source_colour)
51
+ end
52
+
53
+ collection_colours
54
+ end
55
+
56
+ def self.distance_between_colours(comparison_colour, source_colour)
57
+ # https://en.wikipedia.org/wiki/Euclidean_distance#Higher_dimensions
58
+ # https://www.baeldung.com/cs/compute-similarity-of-colours
59
+ # TODO: allow the type of matching to be set via config. Use HSL for now as it's far faster than CIELab
60
+ conversion_1 = comparison_colour.hsl
61
+ conversion_2 = source_colour.hsl
62
+
63
+ keys = conversion_1.keys
64
+ Math.sqrt((conversion_1[keys[0]] - conversion_2[keys[0]])**2 + (conversion_1[keys[1]] - conversion_2[keys[1]])**2 + (conversion_1[keys[2]] - conversion_2[keys[2]])**2)
65
+ end
66
+ end
67
+ end
@@ -1,17 +1,19 @@
1
- module ColorConverters
2
- class NullConverter < BaseConverter
3
- def self.matches?(_color_input)
4
- true
5
- end
6
-
7
- private
8
-
9
- def validate_input(_color_input)
10
- false
11
- end
12
-
13
- def input_to_rgba(_color_input)
14
- raise InvalidColorError
15
- end
16
- end
17
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class NullConverter < BaseConverter
5
+ def self.matches?(_colour_input)
6
+ true
7
+ end
8
+
9
+ private
10
+
11
+ def validate_input(_colour_input)
12
+ ['did not recognise colour input']
13
+ end
14
+
15
+ def input_to_rgba(_colour_input)
16
+ raise InvalidColorError
17
+ end
18
+ end
19
+ end
@@ -1,98 +1,102 @@
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
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class OklabConverter < BaseConverter
5
+ def self.matches?(colour_input)
6
+ return false unless colour_input.is_a?(Hash)
7
+
8
+ colour_input.keys - [:l, :a, :b, :space] == [] && colour_input[:space].to_s == 'ok'
9
+ end
10
+
11
+ def self.bounds
12
+ { l: [0.0, 100.0], a: [-0.5, 0.5], b: [-0.5, 0.5] }
13
+ end
14
+
15
+ private
16
+
17
+ def validate_input(colour_input)
18
+ OklabConverter.bounds.collect do |key, range|
19
+ "#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
20
+ end.compact
21
+ end
22
+
23
+ def input_to_rgba(colour_input)
24
+ x, y, z = OklabConverter.oklab_to_xyz(colour_input)
25
+ r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
26
+
27
+ [r, g, b, 1.0]
28
+ end
29
+
30
+ def self.oklab_to_xyz(colour_input)
31
+ l = colour_input[:l].to_d
32
+ a = colour_input[:a].to_d
33
+ b = colour_input[:b].to_d
34
+
35
+ # Now, scale l to a decimal
36
+ l /= 100.0.to_d
37
+
38
+ # Convert Oklab (L*a*b*) to LMS'
39
+ lab_to_lms_matrix = ::Matrix[
40
+ [1.0000000000000000.to_d, 0.3963377773761749.to_d, 0.2158037573099136.to_d],
41
+ [1.0000000000000000.to_d, -0.1055613458156586.to_d, -0.0638541728258133.to_d],
42
+ [1.0000000000000000.to_d, -0.0894841775298119.to_d, -1.2914855480194092.to_d]
43
+ ]
44
+
45
+ l_lms, m_lms, s_lms = (lab_to_lms_matrix * ::Matrix.column_vector([l, a, b])).to_a.flatten
46
+
47
+ l_lms **= 3.0.to_d
48
+ m_lms **= 3.0.to_d
49
+ s_lms **= 3.0.to_d
50
+
51
+ lms_to_xyz_matrix = ::Matrix[
52
+ [1.2268798758459243.to_d, -0.5578149944602171.to_d, 0.2813910456659647.to_d],
53
+ [-0.0405757452148008.to_d, 1.1122868032803170.to_d, -0.0717110580655164.to_d],
54
+ [-0.0763729366746601.to_d, -0.4214933324022432.to_d, 1.5869240198367816.to_d]
55
+ ]
56
+
57
+ x, y, z = (lms_to_xyz_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
58
+
59
+ x *= 100.0.to_d
60
+ y *= 100.0.to_d
61
+ z *= 100.0.to_d
62
+
63
+ [x, y, z]
64
+ end
65
+
66
+ def self.xyz_to_oklab(xyz_array)
67
+ x, y, z = xyz_array.map(&:to_d)
68
+
69
+ # The transformation matrix expects normalised X, Y, Z values.
70
+ x /= 100.0.to_d
71
+ y /= 100.0.to_d
72
+ z /= 100.0.to_d
73
+
74
+ # Given XYZ relative to D65, convert to OKLab
75
+ xyz_to_lms_matrix = ::Matrix[
76
+ [0.8190224379967030.to_d, 0.3619062600528904.to_d, -0.1288737815209879.to_d],
77
+ [0.0329836539323885.to_d, 0.9292868615863434.to_d, 0.0361446663506424.to_d],
78
+ [0.0481771893596242.to_d, 0.2642395317527308.to_d, 0.6335478284694309.to_d]
79
+ ]
80
+
81
+ l_lms, m_lms, s_lms = (xyz_to_lms_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
82
+
83
+ cube_root = (1.0.to_d / 3.0.to_d)
84
+ l_lms **= cube_root
85
+ m_lms **= cube_root
86
+ s_lms **= cube_root
87
+
88
+ lms_to_lab_matrix = ::Matrix[
89
+ [0.2104542683093140.to_d, 0.7936177747023054.to_d, -0.0040720430116193.to_d],
90
+ [1.9779985324311684.to_d, -2.4285922420485799.to_d, 0.4505937096174110.to_d],
91
+ [0.0259040424655478.to_d, 0.7827717124575296.to_d, -0.8086757549230774.to_d]
92
+ ]
93
+
94
+ l_lab, a_lab, b_lab = (lms_to_lab_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
95
+
96
+ # Now, scale l to a percentage
97
+ l_lab *= 100.0.to_d
98
+
99
+ [l_lab, a_lab, b_lab]
100
+ end
101
+ end
102
+ end
@@ -1,58 +1,61 @@
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
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class OklchConverter < BaseConverter
5
+ def self.matches?(colour_input)
6
+ return false unless colour_input.is_a?(Hash)
7
+
8
+ colour_input.keys - [:l, :c, :h, :space] == [] && colour_input[:space].to_s == 'ok'
9
+ end
10
+
11
+ def self.bounds
12
+ { l: [0.0, 100.0], c: [0.0, 500.0], h: [0.0, 360.0] }
13
+ end
14
+
15
+ private
16
+
17
+ def validate_input(colour_input)
18
+ OklchConverter.bounds.collect do |key, range|
19
+ "#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
20
+ end.compact
21
+ end
22
+
23
+ def input_to_rgba(colour_input)
24
+ l, a, b = OklchConverter.oklch_to_oklab(colour_input)
25
+ x, y, z = OklabConverter.oklab_to_xyz({ l: l, a: a, b: b })
26
+ r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
27
+
28
+ [r, g, b, 1.0]
29
+ end
30
+
31
+ def self.oklch_to_oklab(colour_input)
32
+ l = colour_input[:l].to_d
33
+ c = colour_input[:c].to_d
34
+ h = colour_input[:h].to_d
35
+
36
+ h_rad = h * (Math::PI.to_d / 180.0.to_d)
37
+
38
+ a = c * Math.cos(h_rad).to_d
39
+ b = c * Math.sin(h_rad).to_d
40
+
41
+ [l, a, b]
42
+ end
43
+
44
+ def self.oklab_to_oklch(lab_array)
45
+ l, aa, bb = lab_array.map(&:to_d)
46
+
47
+ e = 0.000015.to_d; # if chroma is smaller than this, set hue to 0 similar to CIELch
48
+
49
+ c = ((aa**2.to_d) + (bb**2.to_d))**0.5.to_d
50
+
51
+ h_rad = Math.atan2(bb, aa).to_d
52
+ h = h_rad * (180.0.to_d / Math::PI.to_d)
53
+
54
+ h %= 360
55
+
56
+ h = 0 if c < e
57
+
58
+ [l, c, h]
59
+ end
60
+ end
61
+ end