color_converters 0.1.4 → 0.1.5
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 +199 -190
- data/Rakefile +8 -8
- data/lib/color_converters/base_converter.rb +182 -184
- data/lib/color_converters/color.rb +31 -18
- data/lib/color_converters/converters/cielab_converter.rb +99 -95
- data/lib/color_converters/converters/cielch_converter.rb +65 -61
- data/lib/color_converters/converters/cmyk_converter.rb +63 -59
- data/lib/color_converters/converters/hex_converter.rb +49 -45
- data/lib/color_converters/converters/hsl_converter.rb +72 -68
- data/lib/color_converters/converters/hsl_string_converter.rb +61 -46
- data/lib/color_converters/converters/hsv_converter.rb +64 -60
- data/lib/color_converters/converters/name_converter.rb +69 -67
- data/lib/color_converters/converters/null_converter.rb +23 -19
- data/lib/color_converters/converters/oklab_converter.rb +106 -102
- data/lib/color_converters/converters/oklch_converter.rb +65 -61
- data/lib/color_converters/converters/rgb_converter.rb +113 -109
- data/lib/color_converters/converters/rgb_string_converter.rb +64 -49
- data/lib/color_converters/converters/xyz_converter.rb +114 -110
- data/lib/color_converters/version.rb +1 -1
- metadata +2 -2
@@ -1,60 +1,64 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class HsvConverter < BaseConverter
|
5
|
-
def self.matches?(colour_input)
|
6
|
-
return false unless colour_input.is_a?(Hash)
|
7
|
-
|
8
|
-
colour_input.keys - [:h, :s, :v] == [] || colour_input.keys - [:h, :s, :b] == []
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.bounds
|
12
|
-
{ h: [0.0, 360.0], s: [-128.0, 127.0], v: [-128.0, 127.0], b: [-128.0, 127.0] }
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
h
|
29
|
-
s
|
30
|
-
v
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
p =
|
40
|
-
q = (
|
41
|
-
t = (
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
when
|
50
|
-
[
|
51
|
-
when
|
52
|
-
[
|
53
|
-
when
|
54
|
-
[
|
55
|
-
when
|
56
|
-
[
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorConverters
|
4
|
+
class HsvConverter < BaseConverter
|
5
|
+
def self.matches?(colour_input)
|
6
|
+
return false unless colour_input.is_a?(Hash)
|
7
|
+
|
8
|
+
colour_input.keys - [:h, :s, :v] == [] || colour_input.keys - [:h, :s, :b] == []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bounds
|
12
|
+
{ h: [0.0, 360.0], s: [-128.0, 127.0], v: [-128.0, 127.0], b: [-128.0, 127.0] }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# def clamp_input(colour_input)
|
18
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*HsvConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
HsvConverter.bounds.collect do |key, range|
|
23
|
+
"#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].blank? || colour_input[key].to_f.between?(*range)
|
24
|
+
end.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def input_to_rgba(colour_input)
|
28
|
+
h = colour_input[:h].to_f
|
29
|
+
s = colour_input[:s].to_f
|
30
|
+
v = (colour_input[:v] || colour_input[:b]).to_f
|
31
|
+
|
32
|
+
h /= 360
|
33
|
+
s /= 100
|
34
|
+
v /= 100
|
35
|
+
|
36
|
+
i = (h * 6).floor
|
37
|
+
f = h * 6 - i
|
38
|
+
|
39
|
+
p = v * (1 - s)
|
40
|
+
q = v * (1 - f * s)
|
41
|
+
t = v * (1 - (1 - f) * s)
|
42
|
+
|
43
|
+
p = (p * 255).round(IMPORT_DP)
|
44
|
+
q = (q * 255).round(IMPORT_DP)
|
45
|
+
t = (t * 255).round(IMPORT_DP)
|
46
|
+
v = (v * 255).round(IMPORT_DP)
|
47
|
+
|
48
|
+
case i % 6
|
49
|
+
when 0
|
50
|
+
[v, t, p, 1.0]
|
51
|
+
when 1
|
52
|
+
[q, v, p, 1.0]
|
53
|
+
when 2
|
54
|
+
[p, v, t, 1.0]
|
55
|
+
when 3
|
56
|
+
[p, q, v, 1.0]
|
57
|
+
when 4
|
58
|
+
[t, p, v, 1.0]
|
59
|
+
when 5
|
60
|
+
[v, p, q, 1.0]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,67 +1,69 @@
|
|
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
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
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 clamp_input(colour_input)
|
32
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*CielchConverter.bounds[key]) }
|
33
|
+
# end
|
34
|
+
|
35
|
+
def validate_input(colour_input)
|
36
|
+
self.class.match_name_from_palettes(colour_input).present? ? [] : ['name could not be found across colour collections']
|
37
|
+
end
|
38
|
+
|
39
|
+
def input_to_rgba(colour_input)
|
40
|
+
found_colour = self.class.match_name_from_palettes(colour_input) || ''
|
41
|
+
|
42
|
+
HexConverter.hex_to_rgba(found_colour) if found_colour.present?
|
43
|
+
end
|
44
|
+
|
45
|
+
# this is a checking for a direct naming match against the ColorSwatchCollection
|
46
|
+
def self.match_name_from_palettes(colour_name)
|
47
|
+
::ColorSwatchCollection.get_from_name(colour_name).dig(:hex)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.add_colour_distances_to_collection(collection_colours, source_colour)
|
51
|
+
collection_colours.map do |swatch|
|
52
|
+
swatch[:distance] = self.distance_between_colours(ColorConverters::Color.new(swatch.dig(:hex)), source_colour)
|
53
|
+
end
|
54
|
+
|
55
|
+
collection_colours
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.distance_between_colours(comparison_colour, source_colour)
|
59
|
+
# https://en.wikipedia.org/wiki/Euclidean_distance#Higher_dimensions
|
60
|
+
# https://www.baeldung.com/cs/compute-similarity-of-colours
|
61
|
+
# TODO: allow the type of matching to be set via config. Use HSL for now as it's far faster than CIELab
|
62
|
+
conversion_1 = comparison_colour.hsl
|
63
|
+
conversion_2 = source_colour.hsl
|
64
|
+
|
65
|
+
keys = conversion_1.keys
|
66
|
+
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)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,19 +1,23 @@
|
|
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
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
def
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
|
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 clamp_input(colour_input)
|
12
|
+
# colour_input
|
13
|
+
# end
|
14
|
+
|
15
|
+
def validate_input(_colour_input)
|
16
|
+
['did not recognise colour input']
|
17
|
+
end
|
18
|
+
|
19
|
+
def input_to_rgba(_colour_input)
|
20
|
+
raise InvalidColorError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -1,102 +1,106 @@
|
|
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
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
s_lms
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
z
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
x, y, z
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
y
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
m_lms
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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 clamp_input(colour_input)
|
18
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*OklabConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
OklabConverter.bounds.collect do |key, range|
|
23
|
+
"#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
|
24
|
+
end.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def input_to_rgba(colour_input)
|
28
|
+
x, y, z = OklabConverter.oklab_to_xyz(colour_input)
|
29
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
30
|
+
|
31
|
+
[r, g, b, 1.0]
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.oklab_to_xyz(colour_input)
|
35
|
+
l = colour_input[:l].to_d
|
36
|
+
a = colour_input[:a].to_d
|
37
|
+
b = colour_input[:b].to_d
|
38
|
+
|
39
|
+
# Now, scale l to a decimal
|
40
|
+
l /= 100.0.to_d
|
41
|
+
|
42
|
+
# Convert Oklab (L*a*b*) to LMS'
|
43
|
+
lab_to_lms_matrix = ::Matrix[
|
44
|
+
[1.0000000000000000.to_d, 0.3963377773761749.to_d, 0.2158037573099136.to_d],
|
45
|
+
[1.0000000000000000.to_d, -0.1055613458156586.to_d, -0.0638541728258133.to_d],
|
46
|
+
[1.0000000000000000.to_d, -0.0894841775298119.to_d, -1.2914855480194092.to_d]
|
47
|
+
]
|
48
|
+
|
49
|
+
l_lms, m_lms, s_lms = (lab_to_lms_matrix * ::Matrix.column_vector([l, a, b])).to_a.flatten
|
50
|
+
|
51
|
+
l_lms **= 3.0.to_d
|
52
|
+
m_lms **= 3.0.to_d
|
53
|
+
s_lms **= 3.0.to_d
|
54
|
+
|
55
|
+
lms_to_xyz_matrix = ::Matrix[
|
56
|
+
[1.2268798758459243.to_d, -0.5578149944602171.to_d, 0.2813910456659647.to_d],
|
57
|
+
[-0.0405757452148008.to_d, 1.1122868032803170.to_d, -0.0717110580655164.to_d],
|
58
|
+
[-0.0763729366746601.to_d, -0.4214933324022432.to_d, 1.5869240198367816.to_d]
|
59
|
+
]
|
60
|
+
|
61
|
+
x, y, z = (lms_to_xyz_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
|
62
|
+
|
63
|
+
x *= 100.0.to_d
|
64
|
+
y *= 100.0.to_d
|
65
|
+
z *= 100.0.to_d
|
66
|
+
|
67
|
+
[x, y, z]
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.xyz_to_oklab(xyz_array)
|
71
|
+
x, y, z = xyz_array.map(&:to_d)
|
72
|
+
|
73
|
+
# The transformation matrix expects normalised X, Y, Z values.
|
74
|
+
x /= 100.0.to_d
|
75
|
+
y /= 100.0.to_d
|
76
|
+
z /= 100.0.to_d
|
77
|
+
|
78
|
+
# Given XYZ relative to D65, convert to OKLab
|
79
|
+
xyz_to_lms_matrix = ::Matrix[
|
80
|
+
[0.8190224379967030.to_d, 0.3619062600528904.to_d, -0.1288737815209879.to_d],
|
81
|
+
[0.0329836539323885.to_d, 0.9292868615863434.to_d, 0.0361446663506424.to_d],
|
82
|
+
[0.0481771893596242.to_d, 0.2642395317527308.to_d, 0.6335478284694309.to_d]
|
83
|
+
]
|
84
|
+
|
85
|
+
l_lms, m_lms, s_lms = (xyz_to_lms_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
|
86
|
+
|
87
|
+
cube_root = (1.0.to_d / 3.0.to_d)
|
88
|
+
l_lms **= cube_root
|
89
|
+
m_lms **= cube_root
|
90
|
+
s_lms **= cube_root
|
91
|
+
|
92
|
+
lms_to_lab_matrix = ::Matrix[
|
93
|
+
[0.2104542683093140.to_d, 0.7936177747023054.to_d, -0.0040720430116193.to_d],
|
94
|
+
[1.9779985324311684.to_d, -2.4285922420485799.to_d, 0.4505937096174110.to_d],
|
95
|
+
[0.0259040424655478.to_d, 0.7827717124575296.to_d, -0.8086757549230774.to_d]
|
96
|
+
]
|
97
|
+
|
98
|
+
l_lab, a_lab, b_lab = (lms_to_lab_matrix * ::Matrix.column_vector([l_lms, m_lms, s_lms])).to_a.flatten
|
99
|
+
|
100
|
+
# Now, scale l to a percentage
|
101
|
+
l_lab *= 100.0.to_d
|
102
|
+
|
103
|
+
[l_lab, a_lab, b_lab]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -1,61 +1,65 @@
|
|
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
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
l,
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
h =
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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 clamp_input(colour_input)
|
18
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*OklchConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
OklchConverter.bounds.collect do |key, range|
|
23
|
+
"#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
|
24
|
+
end.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def input_to_rgba(colour_input)
|
28
|
+
l, a, b = OklchConverter.oklch_to_oklab(colour_input)
|
29
|
+
x, y, z = OklabConverter.oklab_to_xyz({ l: l, a: a, b: b })
|
30
|
+
r, g, b = XyzConverter.xyz_to_rgb({ x: x, y: y, z: z })
|
31
|
+
|
32
|
+
[r, g, b, 1.0]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.oklch_to_oklab(colour_input)
|
36
|
+
l = colour_input[:l].to_d
|
37
|
+
c = colour_input[:c].to_d
|
38
|
+
h = colour_input[:h].to_d
|
39
|
+
|
40
|
+
h_rad = h * (Math::PI.to_d / 180.0.to_d)
|
41
|
+
|
42
|
+
a = c * Math.cos(h_rad).to_d
|
43
|
+
b = c * Math.sin(h_rad).to_d
|
44
|
+
|
45
|
+
[l, a, b]
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.oklab_to_oklch(lab_array)
|
49
|
+
l, aa, bb = lab_array.map(&:to_d)
|
50
|
+
|
51
|
+
e = 0.000015.to_d; # if chroma is smaller than this, set hue to 0 similar to CIELch
|
52
|
+
|
53
|
+
c = ((aa**2.to_d) + (bb**2.to_d))**0.5.to_d
|
54
|
+
|
55
|
+
h_rad = Math.atan2(bb, aa).to_d
|
56
|
+
h = h_rad * (180.0.to_d / Math::PI.to_d)
|
57
|
+
|
58
|
+
h %= 360
|
59
|
+
|
60
|
+
h = 0 if c < e
|
61
|
+
|
62
|
+
[l, c, h]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|