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,61 +1,65 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class CielchConverter < 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 == 'cie'
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.bounds
|
12
|
-
{ l: [0.0, 100.0], c: [0.0, 150.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 CielchConverter < 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 == 'cie'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bounds
|
12
|
+
{ l: [0.0, 100.0], c: [0.0, 150.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(*CielchConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
CielchConverter.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 = CielchConverter.cielch_to_cielab(colour_input)
|
29
|
+
x, y, z = CielabConverter.cielab_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.cielch_to_cielab(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.cielab_to_cielch(lab_array)
|
49
|
+
l, aa, bb = lab_array.map(&:to_d)
|
50
|
+
|
51
|
+
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]
|
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
|
@@ -1,59 +1,63 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class CmykConverter < BaseConverter
|
5
|
-
def self.matches?(colour_input)
|
6
|
-
return false unless colour_input.is_a?(Hash)
|
7
|
-
|
8
|
-
colour_input.keys - [:c, :m, :y, :k] == []
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.bounds
|
12
|
-
{ c: [0.0, 100.0], m: [0.0, 100.0], y: [0.0, 100.0], k: [0.0, 100.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
|
-
r, g, b
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
c
|
52
|
-
m
|
53
|
-
y
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorConverters
|
4
|
+
class CmykConverter < BaseConverter
|
5
|
+
def self.matches?(colour_input)
|
6
|
+
return false unless colour_input.is_a?(Hash)
|
7
|
+
|
8
|
+
colour_input.keys - [:c, :m, :y, :k] == []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bounds
|
12
|
+
{ c: [0.0, 100.0], m: [0.0, 100.0], y: [0.0, 100.0], k: [0.0, 100.0] }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# def clamp_input(colour_input)
|
18
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*CmykConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
CmykConverter.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
|
+
c = colour_input[:c].to_f
|
29
|
+
m = colour_input[:m].to_f
|
30
|
+
y = colour_input[:y].to_f
|
31
|
+
k = colour_input[:k].to_f
|
32
|
+
|
33
|
+
c /= 100.0
|
34
|
+
m /= 100.0
|
35
|
+
y /= 100.0
|
36
|
+
k /= 100.0
|
37
|
+
|
38
|
+
r = (255 * (1.0 - [1.0, c * (1.0 - k) + k].min))
|
39
|
+
g = (255 * (1.0 - [1.0, m * (1.0 - k) + k].min))
|
40
|
+
b = (255 * (1.0 - [1.0, y * (1.0 - k) + k].min))
|
41
|
+
|
42
|
+
[r, g, b, 1.0]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.rgb_to_cmyk(rgb_array_frac)
|
46
|
+
r, g, b = rgb_array_frac
|
47
|
+
|
48
|
+
k = (1.0 - [r, g, b].max)
|
49
|
+
k_frac = k == 1.0 ? 1.0 : 1.0 - k
|
50
|
+
|
51
|
+
c = (1.0 - r - k) / k_frac
|
52
|
+
m = (1.0 - g - k) / k_frac
|
53
|
+
y = (1.0 - b - k) / k_frac
|
54
|
+
|
55
|
+
c *= 100
|
56
|
+
m *= 100
|
57
|
+
y *= 100
|
58
|
+
k *= 100
|
59
|
+
|
60
|
+
[c, m, y, k]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -1,45 +1,49 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class HexConverter < BaseConverter
|
5
|
-
def self.matches?(colour)
|
6
|
-
return false unless colour.is_a?(String)
|
7
|
-
|
8
|
-
colour.include?('#') && [4, 7, 9].include?(colour.length)
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
r, g, b
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorConverters
|
4
|
+
class HexConverter < BaseConverter
|
5
|
+
def self.matches?(colour)
|
6
|
+
return false unless colour.is_a?(String)
|
7
|
+
|
8
|
+
colour.include?('#') && [4, 7, 9].include?(colour.length)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# def clamp_input(colour_input)
|
14
|
+
# colour_input # return it as it was
|
15
|
+
# end
|
16
|
+
|
17
|
+
def validate_input(_colour_input)
|
18
|
+
# TODO
|
19
|
+
[] # to return no errors
|
20
|
+
end
|
21
|
+
|
22
|
+
def input_to_rgba(colour_input)
|
23
|
+
HexConverter.hex_to_rgba(colour_input)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.hex_to_rgba(hex_input)
|
27
|
+
hex_input = self.normalize_hex(hex_input)
|
28
|
+
|
29
|
+
r = hex_input[0, 2].hex
|
30
|
+
g = hex_input[2, 2].hex
|
31
|
+
b = hex_input[4, 2].hex
|
32
|
+
a = hex_input.length == 8 ? hex[6, 2].hex : 1.0
|
33
|
+
|
34
|
+
[r, g, b, a]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.rgb_to_hex(rgb_array)
|
38
|
+
r, g, b = rgb_array
|
39
|
+
|
40
|
+
format('#%<r>02x%<g>02x%<b>02x', r: r, g: g, b: b)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.normalize_hex(hex_input)
|
44
|
+
hex_input = hex_input.gsub('#', '')
|
45
|
+
|
46
|
+
(hex_input.length == 3 ? hex_input[0, 1] * 2 + hex_input[1, 1] * 2 + hex_input[2, 1] * 2 : hex_input).downcase
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,68 +1,72 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class HslConverter < BaseConverter
|
5
|
-
def self.matches?(colour_input)
|
6
|
-
return false unless colour_input.is_a?(Hash)
|
7
|
-
|
8
|
-
colour_input.keys - [:h, :s, :l] == [] || colour_input.keys - [:h, :s, :l, :a] == []
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.bounds
|
12
|
-
{ h: [0.0, 360.0], s: [0.0, 100.0], l: [0.0, 100.0], a: [0.0, 1.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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
t1 + (t2 - t1) *
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorConverters
|
4
|
+
class HslConverter < BaseConverter
|
5
|
+
def self.matches?(colour_input)
|
6
|
+
return false unless colour_input.is_a?(Hash)
|
7
|
+
|
8
|
+
colour_input.keys - [:h, :s, :l] == [] || colour_input.keys - [:h, :s, :l, :a] == []
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bounds
|
12
|
+
{ h: [0.0, 360.0], s: [0.0, 100.0], l: [0.0, 100.0], a: [0.0, 1.0] }
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# def clamp_input(colour_input)
|
18
|
+
# colour_input.each { |key, value| colour_input[key] = value.clamp(*HslConverter.bounds[key]) }
|
19
|
+
# end
|
20
|
+
|
21
|
+
def validate_input(colour_input)
|
22
|
+
HslConverter.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
|
+
h = colour_input[:h].to_s.gsub(/[^0-9.]/, '').to_f / 360.0
|
29
|
+
s = colour_input[:s].to_s.gsub(/[^0-9.]/, '').to_f / 100.0
|
30
|
+
l = colour_input[:l].to_s.gsub(/[^0-9.]/, '').to_f / 100.0
|
31
|
+
a = colour_input[:a] ? colour_input[:a].to_s.gsub(/[^0-9.]/, '').to_f : 1.0
|
32
|
+
|
33
|
+
return greyscale(l, a) if s.zero?
|
34
|
+
|
35
|
+
t2 = if l < 0.5
|
36
|
+
l * (1 + s)
|
37
|
+
else
|
38
|
+
l + s - l * s
|
39
|
+
end
|
40
|
+
|
41
|
+
t1 = 2 * l - t2
|
42
|
+
|
43
|
+
rgb = [0, 0, 0]
|
44
|
+
|
45
|
+
(0..2).each do |i|
|
46
|
+
t3 = h + 1 / 3.0 * - (i - 1)
|
47
|
+
t3.negative? && t3 += 1
|
48
|
+
t3 > 1 && t3 -= 1
|
49
|
+
|
50
|
+
val = if 6 * t3 < 1
|
51
|
+
t1 + (t2 - t1) * 6 * t3
|
52
|
+
elsif 2 * t3 < 1
|
53
|
+
t2
|
54
|
+
elsif 3 * t3 < 2
|
55
|
+
t1 + (t2 - t1) * (2 / 3.0 - t3) * 6
|
56
|
+
else
|
57
|
+
t1
|
58
|
+
end
|
59
|
+
|
60
|
+
rgb[i] = (val * 255)
|
61
|
+
end
|
62
|
+
|
63
|
+
[rgb[0], rgb[1], rgb[2], a]
|
64
|
+
end
|
65
|
+
|
66
|
+
def greyscale(luminosity, alpha)
|
67
|
+
rgb_equal_value = (luminosity * 255).round(IMPORT_DP)
|
68
|
+
|
69
|
+
[rgb_equal_value, rgb_equal_value, rgb_equal_value, alpha]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -1,46 +1,61 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ColorConverters
|
4
|
-
class HslStringConverter < BaseConverter
|
5
|
-
def self.matches?(colour_input)
|
6
|
-
return false unless colour_input.is_a?(String)
|
7
|
-
|
8
|
-
colour_input.include?('hsl(') || colour_input.include?('hsla(')
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.bounds
|
12
|
-
HslConverter.bounds
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
"#{key} must be
|
29
|
-
end.compact
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorConverters
|
4
|
+
class HslStringConverter < BaseConverter
|
5
|
+
def self.matches?(colour_input)
|
6
|
+
return false unless colour_input.is_a?(String)
|
7
|
+
|
8
|
+
colour_input.include?('hsl(') || colour_input.include?('hsla(')
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.bounds
|
12
|
+
HslConverter.bounds
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# def clamp_input(colour_input)
|
18
|
+
# colour_input = HslStringConverter.sanitize_input(colour_input)
|
19
|
+
# colour_input.each { |key, value| colour_input[key] = value.to_f.clamp(*HslConverter.bounds[key]) }
|
20
|
+
# HslStringConverter.rgb_to_rgbstring([colour_input[:h], colour_input[:s], colour_input[:l]], colour_input[:a])
|
21
|
+
# end
|
22
|
+
|
23
|
+
def validate_input(colour_input)
|
24
|
+
keys = colour_input.include?('hsla(') ? [:h, :s, :l, :a] : [:h, :s, :l]
|
25
|
+
colour_input = HslStringConverter.sanitize_input(colour_input)
|
26
|
+
|
27
|
+
errors = keys.collect do |key|
|
28
|
+
"#{key} must be present" if colour_input[key].blank?
|
29
|
+
end.compact
|
30
|
+
|
31
|
+
return errors if errors.present?
|
32
|
+
|
33
|
+
HslStringConverter.bounds.collect do |key, range|
|
34
|
+
"#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
|
35
|
+
end.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
def input_to_rgba(colour_input)
|
39
|
+
colour_input = HslStringConverter.sanitize_input(colour_input)
|
40
|
+
|
41
|
+
rgba = HslConverter.new({ h: colour_input[:h], s: colour_input[:s], l: colour_input[:l], a: colour_input[:a] }, limit_override: true).rgba
|
42
|
+
|
43
|
+
[rgba[:r], rgba[:g], rgba[:b], rgba[:a]]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.sanitize_input(colour_input)
|
47
|
+
matches = colour_input.match(/hsla?\(([0-9.,%\s]+)\)/) || []
|
48
|
+
h, s, l, a = matches[1]&.split(',')&.map(&:strip)
|
49
|
+
{ h: h, s: s, l: l, a: a }
|
50
|
+
end
|
51
|
+
|
52
|
+
# def self.rgb_to_hslstring(rgb_array_frac, alpha)
|
53
|
+
# r, g, b = rgb_array_frac
|
54
|
+
# if alpha == 1.0
|
55
|
+
# "hsl(#{[r, g, b].join(', ')})"
|
56
|
+
# else
|
57
|
+
# "hsla(#{[r, g, b, alpha].join(', ')})"
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
end
|
61
|
+
end
|