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,58 +1,61 @@
1
- module ColorConverters
2
- class CielchConverter < 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 == 'cie'
7
- end
8
-
9
- def self.bounds
10
- { l: [0.0, 100.0], c: [0.0, 150.0], h: [0.0, 360.0] }
11
- end
12
-
13
- private
14
-
15
- def validate_input(color_input)
16
- bounds = CielchConverter.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 = 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]
26
- end
27
-
28
- def self.cielch_to_cielab(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.cielab_to_cielch(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]
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 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,56 +1,59 @@
1
- module ColorConverters
2
- class CmykConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(Hash)
5
-
6
- color_input.keys - [:c, :m, :y, :k] == []
7
- end
8
-
9
- def self.bounds
10
- { c: [0.0, 100.0], m: [0.0, 100.0], y: [0.0, 100.0], k: [0.0, 100.0] }
11
- end
12
-
13
- private
14
-
15
- def validate_input(color_input)
16
- bounds = CmykConverter.bounds
17
- color_input[:c].to_f.between?(*bounds[:c]) && color_input[:m].to_f.between?(*bounds[:m]) && color_input[:y].to_f.between?(*bounds[:y]) && color_input[:k].to_f.between?(*bounds[:k])
18
- end
19
-
20
- def input_to_rgba(color_input)
21
- c = color_input[:c].to_f
22
- m = color_input[:m].to_f
23
- y = color_input[:y].to_f
24
- k = color_input[:k].to_f
25
-
26
- c /= 100.0
27
- m /= 100.0
28
- y /= 100.0
29
- k /= 100.0
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))
34
-
35
- [r, g, b, 1.0]
36
- end
37
-
38
- def self.rgb_to_cmyk(rgb_array_frac)
39
- r, g, b = rgb_array_frac
40
-
41
- k = (1.0 - [r, g, b].max)
42
- k_frac = k == 1.0 ? 1.0 : 1.0 - k
43
-
44
- c = (1.0 - r - k) / k_frac
45
- m = (1.0 - g - k) / k_frac
46
- y = (1.0 - b - k) / k_frac
47
-
48
- c *= 100
49
- m *= 100
50
- y *= 100
51
- k *= 100
52
-
53
- [c, m, y, k]
54
- end
55
- end
56
- 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 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,32 +1,45 @@
1
- module ColorConverters
2
- class HexConverter < BaseConverter
3
- def self.matches?(color)
4
- return false unless color.is_a?(String)
5
-
6
- color.include?('#') && [4, 7, 9].include?(color.length)
7
- end
8
-
9
- private
10
-
11
- def validate_input(_color_input)
12
- # TODO
13
- true
14
- end
15
-
16
- def input_to_rgba(color_input)
17
- color_input = self.normalize_hex(color_input)
18
-
19
- r = color_input[0, 2].hex
20
- g = color_input[2, 2].hex
21
- b = color_input[4, 2].hex
22
- a = color_input.length == 8 ? hex[6, 2].hex : 1.0
23
-
24
- [r, g, b, a]
25
- end
26
-
27
- def normalize_hex(hex_input)
28
- hex_input = hex_input.gsub('#', '')
29
- (hex_input.length == 3 ? hex_input[0, 1] * 2 + hex_input[1, 1] * 2 + hex_input[2, 1] * 2 : hex_input).downcase
30
- end
31
- end
32
- 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 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,65 +1,68 @@
1
- module ColorConverters
2
- class HslConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(Hash)
5
-
6
- color_input.keys - [:h, :s, :l] == [] || color_input.keys - [:h, :s, :l, :a] == []
7
- end
8
-
9
- def self.bounds
10
- { h: [0.0, 360.0], s: [0.0, 100.0], l: [0.0, 100.0] }
11
- end
12
-
13
- private
14
-
15
- def validate_input(color_input)
16
- bounds = HslConverter.bounds
17
- color_input[:h].to_f.between?(*bounds[:h]) && color_input[:s].to_f.between?(*bounds[:s]) && color_input[:l].to_f.between?(*bounds[:l])
18
- end
19
-
20
- def input_to_rgba(color_input)
21
- h = color_input[:h].to_s.gsub(/[^0-9.]/, '').to_f / 360.0
22
- s = color_input[:s].to_s.gsub(/[^0-9.]/, '').to_f / 100.0
23
- l = color_input[:l].to_s.gsub(/[^0-9.]/, '').to_f / 100.0
24
- a = color_input[:a] ? color_input[:a].to_s.gsub(/[^0-9.]/, '').to_f : 1.0
25
-
26
- return greyscale(l, a) if s.zero?
27
-
28
- t2 = if l < 0.5
29
- l * (1 + s)
30
- else
31
- l + s - l * s
32
- end
33
-
34
- t1 = 2 * l - t2
35
-
36
- rgb = [0, 0, 0]
37
-
38
- (0..2).each do |i|
39
- t3 = h + 1 / 3.0 * - (i - 1)
40
- t3 < 0 && t3 += 1
41
- t3 > 1 && t3 -= 1
42
-
43
- val = if 6 * t3 < 1
44
- t1 + (t2 - t1) * 6 * t3
45
- elsif 2 * t3 < 1
46
- t2
47
- elsif 3 * t3 < 2
48
- t1 + (t2 - t1) * (2 / 3.0 - t3) * 6
49
- else
50
- t1
51
- end
52
-
53
- rgb[i] = (val * 255)
54
- end
55
-
56
- [rgb[0], rgb[1], rgb[2], a]
57
- end
58
-
59
- def greyscale(luminosity, alpha)
60
- rgb_equal_value = (luminosity * 255).round(IMPORT_DP)
61
-
62
- [rgb_equal_value, rgb_equal_value, rgb_equal_value, alpha]
63
- end
64
- end
65
- 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 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,27 +1,46 @@
1
- module ColorConverters
2
- class HslStringConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(String)
5
-
6
- color_input.include?('hsl(') || color_input.include?('hsla(')
7
- end
8
-
9
- private
10
-
11
- def validate_input(_color_input)
12
- true
13
- end
14
-
15
- def input_to_rgba(color_input)
16
- matches = color_input.match(/hsla?\(([0-9.,%\s]+)\)/)
17
- raise InvalidColorError unless matches
18
-
19
- h, s, l, a = matches[1].split(',').map(&:strip)
20
- raise InvalidColorError unless h.present? && s.present? && l.present?
21
-
22
- rgba = HslConverter.new(h: h, s: s, l: l, a: a).rgba
23
-
24
- [rgba[:r], rgba[:g], rgba[:b], rgba[:a]]
25
- end
26
- end
27
- 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 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,58 +1,60 @@
1
- module ColorConverters
2
- class HsvConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(Hash)
5
-
6
- color_input.keys - [:h, :s, :v] == [] || color_input.keys - [:h, :s, :b] == []
7
- end
8
-
9
- def self.bounds
10
- { h: [0.0, 360.0], s: [-128.0, 127.0], v: [-128.0, 127.0], b: [-128.0, 127.0] }
11
- end
12
-
13
- private
14
-
15
- def validate_input(color_input)
16
- bounds = HsvConverter.bounds
17
- light = (color_input[:v].present? && color_input[:v].to_f.between?(*bounds[:v])) || (color_input[:b].present? && color_input[:b].to_f.between?(*bounds[:v]))
18
- color_input[:h].to_f.between?(*bounds[:h]) && color_input[:s].to_f.between?(*bounds[:s]) && light
19
- end
20
-
21
- def input_to_rgba(color_input)
22
- h = color_input[:h].to_f
23
- s = color_input[:s].to_f
24
- v = (color_input[:v] || color_input[:b]).to_f
25
-
26
- h /= 360
27
- s /= 100
28
- v /= 100
29
-
30
- i = (h * 6).floor
31
- f = h * 6 - i
32
-
33
- p = v * (1 - s)
34
- q = v * (1 - f * s)
35
- t = v * (1 - (1 - f) * s)
36
-
37
- p = (p * 255).round(IMPORT_DP)
38
- q = (q * 255).round(IMPORT_DP)
39
- t = (t * 255).round(IMPORT_DP)
40
- v = (v * 255).round(IMPORT_DP)
41
-
42
- case i % 6
43
- when 0
44
- [v, t, p, 1.0]
45
- when 1
46
- [q, v, p, 1.0]
47
- when 2
48
- [p, v, t, 1.0]
49
- when 3
50
- [p, q, v, 1.0]
51
- when 4
52
- [t, p, v, 1.0]
53
- when 5
54
- [v, p, q, 1.0]
55
- end
56
- end
57
- end
58
- end
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 validate_input(colour_input)
18
+ HsvConverter.bounds.collect do |key, range|
19
+ "#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].blank? || 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_f
25
+ s = colour_input[:s].to_f
26
+ v = (colour_input[:v] || colour_input[:b]).to_f
27
+
28
+ h /= 360
29
+ s /= 100
30
+ v /= 100
31
+
32
+ i = (h * 6).floor
33
+ f = h * 6 - i
34
+
35
+ p = v * (1 - s)
36
+ q = v * (1 - f * s)
37
+ t = v * (1 - (1 - f) * s)
38
+
39
+ p = (p * 255).round(IMPORT_DP)
40
+ q = (q * 255).round(IMPORT_DP)
41
+ t = (t * 255).round(IMPORT_DP)
42
+ v = (v * 255).round(IMPORT_DP)
43
+
44
+ case i % 6
45
+ when 0
46
+ [v, t, p, 1.0]
47
+ when 1
48
+ [q, v, p, 1.0]
49
+ when 2
50
+ [p, v, t, 1.0]
51
+ when 3
52
+ [p, q, v, 1.0]
53
+ when 4
54
+ [t, p, v, 1.0]
55
+ when 5
56
+ [v, p, q, 1.0]
57
+ end
58
+ end
59
+ end
60
+ end