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