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,109 +1,113 @@
1
- # frozen_string_literal: true
2
-
3
- module ColorConverters
4
- class RgbConverter < BaseConverter
5
- def self.matches?(colour_input)
6
- return false unless colour_input.is_a?(Hash)
7
-
8
- colour_input.keys - [:r, :g, :b] == [] || colour_input.keys - [:r, :g, :b, :a] == []
9
- end
10
-
11
- def self.bounds
12
- { r: [0.0, 255.0], g: [0.0, 255.0], b: [0.0, 255.0], a: [0.0, 1.0] }
13
- end
14
-
15
- private
16
-
17
- def validate_input(colour_input)
18
- RgbConverter.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
- r = colour_input[:r].to_f
25
- g = colour_input[:g].to_f
26
- b = colour_input[:b].to_f
27
- a = (colour_input[:a] || 1.0).to_f
28
-
29
- [r, g, b, a]
30
- end
31
-
32
- def self.rgb_to_lrgb(rgb_array_frac)
33
- # [0, 1]
34
- r, g, b = rgb_array_frac
35
-
36
- # Inverse sRGB companding. Linearizes RGB channels with respect to energy.
37
- # Assumption that r, g, b are always positive
38
- rr, gg, bb = [r, g, b].map do
39
- if _1.to_d <= 0.04045.to_d
40
- _1.to_d / 12.92.to_d
41
- else
42
- # sRGB Inverse Companding (Non-linear to Linear RGB)
43
- # The sRGB specification (IEC 61966-2-1) defines the exponent as 2.4.
44
- #
45
- (((_1.to_d + 0.055.to_d) / 1.055.to_d)**2.4.to_d)
46
-
47
- # IMPORTANT NUMERICAL NOTE:
48
- # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
49
- # the power function for val**2.4 yields a result that deviates from the value expected by widely-used colour science libraries (like Bruce Lindbloom's).
50
- #
51
- # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
52
- # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
53
- #
54
- # Choose 2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
55
- # or choose 2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
56
- #
57
- # ((_1 + 0.055) / 1.055)**2.5
58
- end
59
- end
60
-
61
- # [0, 1]
62
- [rr, gg, bb]
63
- end
64
-
65
- def self.lrgb_to_rgb(lrgb_array)
66
- rr, gg, bb = lrgb_array
67
-
68
- # Apply sRGB Companding (gamma correction) to convert from Linear RGB to non-linear sRGB.
69
- # This is defined by the sRGB specification (IEC 61966-2-1).
70
- # The exponent for the non-linear segment is 1/2.4 (approximately 0.41666...).
71
- # Assumption that rr, gg, bb are always positive
72
- r, g, b = [rr, gg, bb].map do
73
- if _1.to_d <= 0.0031308.to_d
74
- # Linear portion of the sRGB curve
75
- _1.to_d * 12.92.to_d
76
- else
77
- # Non-linear (gamma-corrected) portion of the sRGB curve
78
- # The sRGB specification uses an exponent of 1/2.4.
79
- #
80
- (1.055.to_d * (_1.to_d**(1.0.to_d / 2.4.to_d))) - 0.055.to_d
81
-
82
- # IMPORTANT NUMERICAL NOTE:
83
- # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
84
- # the inverse power function for val**2.4 yields a result that deviates from the value expected by widely-used colour science libraries (like Bruce Lindbloom's).
85
- #
86
- # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
87
- # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
88
- #
89
- # Choose 1/2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
90
- # or choose 1/2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
91
- #
92
- # (1.055 * (_1**(1.0 / 2.5))) - 0.055
93
- end
94
- end
95
-
96
- # Scale the 0-1 sRGB value to the 0-255 range for 8-bit colour components.
97
- r *= 255.0.to_d
98
- g *= 255.0.to_d
99
- b *= 255.0.to_d
100
-
101
- # Clamping RGB values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
102
- r = r.clamp(0.0..255.0)
103
- g = g.clamp(0.0..255.0)
104
- b = b.clamp(0.0..255.0)
105
-
106
- [r, g, b]
107
- end
108
- end
109
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class RgbConverter < BaseConverter
5
+ def self.matches?(colour_input)
6
+ return false unless colour_input.is_a?(Hash)
7
+
8
+ colour_input.keys - [:r, :g, :b] == [] || colour_input.keys - [:r, :g, :b, :a] == []
9
+ end
10
+
11
+ def self.bounds
12
+ { r: [0.0, 255.0], g: [0.0, 255.0], b: [0.0, 255.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(*RgbConverter.bounds[key]) }
19
+ # end
20
+
21
+ def validate_input(colour_input)
22
+ RgbConverter.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
+ r = colour_input[:r].to_f
29
+ g = colour_input[:g].to_f
30
+ b = colour_input[:b].to_f
31
+ a = (colour_input[:a] || 1.0).to_f
32
+
33
+ [r, g, b, a]
34
+ end
35
+
36
+ def self.rgb_to_lrgb(rgb_array_frac)
37
+ # [0, 1]
38
+ r, g, b = rgb_array_frac
39
+
40
+ # Inverse sRGB companding. Linearizes RGB channels with respect to energy.
41
+ # Assumption that r, g, b are always positive
42
+ rr, gg, bb = [r, g, b].map do
43
+ if _1.to_d <= 0.04045.to_d
44
+ _1.to_d / 12.92.to_d
45
+ else
46
+ # sRGB Inverse Companding (Non-linear to Linear RGB)
47
+ # The sRGB specification (IEC 61966-2-1) defines the exponent as 2.4.
48
+ #
49
+ (((_1.to_d + 0.055.to_d) / 1.055.to_d)**2.4.to_d)
50
+
51
+ # IMPORTANT NUMERICAL NOTE:
52
+ # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
53
+ # the power function for val**2.4 yields a result that deviates from the value expected by widely-used colour science libraries (like Bruce Lindbloom's).
54
+ #
55
+ # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
56
+ # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
57
+ #
58
+ # Choose 2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
59
+ # or choose 2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
60
+ #
61
+ # ((_1 + 0.055) / 1.055)**2.5
62
+ end
63
+ end
64
+
65
+ # [0, 1]
66
+ [rr, gg, bb]
67
+ end
68
+
69
+ def self.lrgb_to_rgb(lrgb_array)
70
+ rr, gg, bb = lrgb_array
71
+
72
+ # Apply sRGB Companding (gamma correction) to convert from Linear RGB to non-linear sRGB.
73
+ # This is defined by the sRGB specification (IEC 61966-2-1).
74
+ # The exponent for the non-linear segment is 1/2.4 (approximately 0.41666...).
75
+ # Assumption that rr, gg, bb are always positive
76
+ r, g, b = [rr, gg, bb].map do
77
+ if _1.to_d <= 0.0031308.to_d
78
+ # Linear portion of the sRGB curve
79
+ _1.to_d * 12.92.to_d
80
+ else
81
+ # Non-linear (gamma-corrected) portion of the sRGB curve
82
+ # The sRGB specification uses an exponent of 1/2.4.
83
+ #
84
+ (1.055.to_d * (_1.to_d**(1.0.to_d / 2.4.to_d))) - 0.055.to_d
85
+
86
+ # IMPORTANT NUMERICAL NOTE:
87
+ # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
88
+ # the inverse power function for val**2.4 yields a result that deviates from the value expected by widely-used colour science libraries (like Bruce Lindbloom's).
89
+ #
90
+ # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
91
+ # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
92
+ #
93
+ # Choose 1/2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
94
+ # or choose 1/2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
95
+ #
96
+ # (1.055 * (_1**(1.0 / 2.5))) - 0.055
97
+ end
98
+ end
99
+
100
+ # Scale the 0-1 sRGB value to the 0-255 range for 8-bit colour components.
101
+ r *= 255.0.to_d
102
+ g *= 255.0.to_d
103
+ b *= 255.0.to_d
104
+
105
+ # Clamping RGB values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
106
+ r = r.clamp(0.0..255.0)
107
+ g = g.clamp(0.0..255.0)
108
+ b = b.clamp(0.0..255.0)
109
+
110
+ [r, g, b]
111
+ end
112
+ end
113
+ end
@@ -1,49 +1,64 @@
1
- # frozen_string_literal: true
2
-
3
- module ColorConverters
4
- class RgbStringConverter < BaseConverter
5
- def self.matches?(colour_input)
6
- return false unless colour_input.is_a?(String)
7
-
8
- colour_input.include?('rgb(') || colour_input.include?('rgba(')
9
- end
10
-
11
- def self.bounds
12
- RgbConverter.bounds
13
- end
14
-
15
- private
16
-
17
- def validate_input(colour_input)
18
- keys = colour_input.include?('rgba(') ? [:r, :g, :b, :a] : [:r, :g, :b]
19
- colour_input = RgbStringConverter.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
- RgbStringConverter.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 = RgbStringConverter.sanitize_input(colour_input)
34
-
35
- r = colour_input[:r].to_f
36
- g = colour_input[:g].to_f
37
- b = colour_input[:b].to_f
38
- a = (colour_input[:a] || 1.0).to_f
39
-
40
- [r, g, b, a]
41
- end
42
-
43
- def self.sanitize_input(colour_input)
44
- matches = colour_input.match(/rgba?\(([0-9.,%\s]+)\)/) || []
45
- r, g, b, a = matches[1]&.split(',')&.map(&:strip)
46
- { r: r, g: g, b: b, a: a }
47
- end
48
- end
49
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class RgbStringConverter < BaseConverter
5
+ def self.matches?(colour_input)
6
+ return false unless colour_input.is_a?(String)
7
+
8
+ colour_input.include?('rgb(') || colour_input.include?('rgba(')
9
+ end
10
+
11
+ def self.bounds
12
+ RgbConverter.bounds
13
+ end
14
+
15
+ private
16
+
17
+ # def clamp_input(colour_input)
18
+ # colour_input = RgbStringConverter.sanitize_input(colour_input)
19
+ # colour_input.each { |key, value| colour_input[key] = value.to_f.clamp(*RgbConverter.bounds[key]) }
20
+ # RgbStringConverter.rgb_to_rgbstring([colour_input[:r], colour_input[:g], colour_input[:b]], colour_input[:a])
21
+ # end
22
+
23
+ def validate_input(colour_input)
24
+ keys = colour_input.include?('rgba(') ? [:r, :g, :b, :a] : [:r, :g, :b]
25
+ colour_input = RgbStringConverter.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
+ RgbStringConverter.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 = RgbStringConverter.sanitize_input(colour_input)
40
+
41
+ r = colour_input[:r].to_f
42
+ g = colour_input[:g].to_f
43
+ b = colour_input[:b].to_f
44
+ a = (colour_input[:a] || 1.0).to_f
45
+
46
+ [r, g, b, a]
47
+ end
48
+
49
+ def self.sanitize_input(colour_input)
50
+ matches = colour_input.match(/rgba?\(([0-9.,%\s]+)\)/) || []
51
+ r, g, b, a = matches[1]&.split(',')&.map(&:strip)
52
+ { r: r, g: g, b: b, a: a }
53
+ end
54
+
55
+ # def self.rgb_to_rgbstring(rgb_array_frac, alpha)
56
+ # r, g, b = rgb_array_frac
57
+ # if alpha == 1.0
58
+ # "rgb(#{[r, g, b].join(', ')})"
59
+ # else
60
+ # "rgba(#{[r, g, b, alpha].join(', ')})"
61
+ # end
62
+ # end
63
+ end
64
+ end
@@ -1,110 +1,114 @@
1
- # frozen_string_literal: true
2
-
3
- module ColorConverters
4
- class XyzConverter < BaseConverter
5
- def self.matches?(colour_input)
6
- return false unless colour_input.is_a?(Hash)
7
-
8
- colour_input.keys - [:x, :y, :z] == []
9
- end
10
-
11
- def self.d65
12
- { x: 95.047, y: 100.0, z: 108.883 }
13
- end
14
-
15
- def self.bounds
16
- { x: [0.0, 100.0], y: [0.0, 100.0], z: [0.0, 110.0] }
17
- end
18
-
19
- private
20
-
21
- def validate_input(colour_input)
22
- XyzConverter.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
- r, g, b = XyzConverter.xyz_to_rgb(colour_input)
29
-
30
- [r, g, b, 1.0]
31
- end
32
-
33
- def self.xyz_to_rgb(xyz_hash)
34
- # [0, 100]
35
- x = xyz_hash[:x].to_d
36
- y = xyz_hash[:y].to_d
37
- z = xyz_hash[:z].to_d
38
-
39
- # Convert XYZ (typically with Y=100 for white) to normalized XYZ (Y=1 for white).
40
- # The transformation matrix expects X, Y, Z values in the 0-1 range.
41
- x /= 100.0.to_d
42
- y /= 100.0.to_d
43
- z /= 100.0.to_d
44
-
45
- # Convert normalized XYZ to Linear sRGB values using sRGB's own white, D65 (no chromatic adaptation)
46
- # https://www.w3.org/TR/css-color-4/#color-conversion-code
47
- conversion_matrix = ::Matrix[
48
- [BigDecimal('3.2409699419045213'), BigDecimal('-1.5373831775700935'), BigDecimal('-0.4986107602930033')],
49
- [BigDecimal('-0.9692436362808798'), BigDecimal('1.8759675015077206'), BigDecimal('0.04155505740717561')],
50
- [BigDecimal('0.05563007969699361'), BigDecimal('-0.20397695888897657'), BigDecimal('1.0569715142428786')]
51
- ]
52
-
53
- rr, gg, bb = (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
54
-
55
- # [0, 1]
56
- RgbConverter.lrgb_to_rgb([rr, gg, bb])
57
- end
58
-
59
- # http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
60
- def self.rgb_to_xyz(rgb_array_frac)
61
- rr, gg, bb = RgbConverter.rgb_to_lrgb(rgb_array_frac)
62
-
63
- # Convert using the RGB/XYZ matrix and sRGB's own white, D65 (no chromatic adaptation)
64
- # https://www.w3.org/TR/css-color-4/#color-conversion-code
65
- conversion_matrix = ::Matrix[
66
- [BigDecimal('0.4123907992659595'), BigDecimal('0.35758433938387796'), BigDecimal('0.1804807884018343')],
67
- [BigDecimal('0.21263900587151036'), BigDecimal('0.7151686787677559'), BigDecimal('0.07219231536073371')],
68
- [BigDecimal('0.01933081871559185'), BigDecimal('0.11919477979462599'), BigDecimal('0.9505321522496606')]
69
- ]
70
-
71
- x, y, z = (conversion_matrix * ::Matrix.column_vector([rr, gg, bb])).to_a.flatten
72
-
73
- # Now, scale X, Y, Z so that Y for D65 white would be 100.
74
- x *= 100.0
75
- y *= 100.0
76
- z *= 100.0
77
-
78
- # Clamping XYZ values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
79
- # x = x.clamp(0.0..95.047)
80
- # y = y.clamp(0.0..100.0)
81
- # z = z.clamp(0.0..108.883)
82
-
83
- [x, y, z]
84
- end
85
-
86
- def self.d50_to_d65(xyz_array)
87
- x, y, z = xyz_array
88
-
89
- conversion_matrix = ::Matrix[
90
- [BigDecimal('0.955473421488075'), BigDecimal('-0.02309845494876471'), BigDecimal('0.06325924320057072')],
91
- [BigDecimal('-0.0283697093338637'), BigDecimal('1.0099953980813041'), BigDecimal('0.021041441191917323')],
92
- [BigDecimal('0.012314014864481998'), BigDecimal('-0.020507649298898964'), BigDecimal('1.330365926242124')]
93
- ]
94
-
95
- (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
96
- end
97
-
98
- def self.d65_to_d50(xyz_array)
99
- x, y, z = xyz_array
100
-
101
- conversion_matrix = ::Matrix[
102
- [BigDecimal('1.0479297925449969'), BigDecimal('0.022946870601609652'), BigDecimal('-0.05019226628920524')],
103
- [BigDecimal('0.02962780877005599'), BigDecimal('0.9904344267538799'), BigDecimal('-0.017073799063418826')],
104
- [BigDecimal('-0.009243040646204504'), BigDecimal('0.015055191490298152'), BigDecimal('0.7518742814281371')]
105
- ]
106
-
107
- (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
108
- end
109
- end
110
- end
1
+ # frozen_string_literal: true
2
+
3
+ module ColorConverters
4
+ class XyzConverter < BaseConverter
5
+ def self.matches?(colour_input)
6
+ return false unless colour_input.is_a?(Hash)
7
+
8
+ colour_input.keys - [:x, :y, :z] == []
9
+ end
10
+
11
+ def self.bounds
12
+ { x: [0.0, 100.0], y: [0.0, 100.0], z: [0.0, 110.0] }
13
+ end
14
+
15
+ def self.d65
16
+ { x: 95.047, y: 100.0, z: 108.883 }
17
+ end
18
+
19
+ private
20
+
21
+ # def clamp_input(colour_input)
22
+ # colour_input.each { |key, value| colour_input[key] = value.clamp(*XyzConverter.bounds[key]) }
23
+ # end
24
+
25
+ def validate_input(colour_input)
26
+ XyzConverter.bounds.collect do |key, range|
27
+ "#{key} must be between #{range[0]} and #{range[1]}" unless colour_input[key].to_f.between?(*range)
28
+ end.compact
29
+ end
30
+
31
+ def input_to_rgba(colour_input)
32
+ r, g, b = XyzConverter.xyz_to_rgb(colour_input)
33
+
34
+ [r, g, b, 1.0]
35
+ end
36
+
37
+ def self.xyz_to_rgb(xyz_hash)
38
+ # [0, 100]
39
+ x = xyz_hash[:x].to_d
40
+ y = xyz_hash[:y].to_d
41
+ z = xyz_hash[:z].to_d
42
+
43
+ # Convert XYZ (typically with Y=100 for white) to normalized XYZ (Y=1 for white).
44
+ # The transformation matrix expects X, Y, Z values in the 0-1 range.
45
+ x /= 100.0.to_d
46
+ y /= 100.0.to_d
47
+ z /= 100.0.to_d
48
+
49
+ # Convert normalized XYZ to Linear sRGB values using sRGB's own white, D65 (no chromatic adaptation)
50
+ # https://www.w3.org/TR/css-color-4/#color-conversion-code
51
+ conversion_matrix = ::Matrix[
52
+ [BigDecimal('3.2409699419045213'), BigDecimal('-1.5373831775700935'), BigDecimal('-0.4986107602930033')],
53
+ [BigDecimal('-0.9692436362808798'), BigDecimal('1.8759675015077206'), BigDecimal('0.04155505740717561')],
54
+ [BigDecimal('0.05563007969699361'), BigDecimal('-0.20397695888897657'), BigDecimal('1.0569715142428786')]
55
+ ]
56
+
57
+ rr, gg, bb = (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
58
+
59
+ # [0, 1]
60
+ RgbConverter.lrgb_to_rgb([rr, gg, bb])
61
+ end
62
+
63
+ # http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
64
+ def self.rgb_to_xyz(rgb_array_frac)
65
+ rr, gg, bb = RgbConverter.rgb_to_lrgb(rgb_array_frac)
66
+
67
+ # Convert using the RGB/XYZ matrix and sRGB's own white, D65 (no chromatic adaptation)
68
+ # https://www.w3.org/TR/css-color-4/#color-conversion-code
69
+ conversion_matrix = ::Matrix[
70
+ [BigDecimal('0.4123907992659595'), BigDecimal('0.35758433938387796'), BigDecimal('0.1804807884018343')],
71
+ [BigDecimal('0.21263900587151036'), BigDecimal('0.7151686787677559'), BigDecimal('0.07219231536073371')],
72
+ [BigDecimal('0.01933081871559185'), BigDecimal('0.11919477979462599'), BigDecimal('0.9505321522496606')]
73
+ ]
74
+
75
+ x, y, z = (conversion_matrix * ::Matrix.column_vector([rr, gg, bb])).to_a.flatten
76
+
77
+ # Now, scale X, Y, Z so that Y for D65 white would be 100.
78
+ x *= 100.0
79
+ y *= 100.0
80
+ z *= 100.0
81
+
82
+ # Clamping XYZ values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
83
+ # x = x.clamp(0.0..95.047)
84
+ # y = y.clamp(0.0..100.0)
85
+ # z = z.clamp(0.0..108.883)
86
+
87
+ [x, y, z]
88
+ end
89
+
90
+ def self.d50_to_d65(xyz_array)
91
+ x, y, z = xyz_array
92
+
93
+ conversion_matrix = ::Matrix[
94
+ [BigDecimal('0.955473421488075'), BigDecimal('-0.02309845494876471'), BigDecimal('0.06325924320057072')],
95
+ [BigDecimal('-0.0283697093338637'), BigDecimal('1.0099953980813041'), BigDecimal('0.021041441191917323')],
96
+ [BigDecimal('0.012314014864481998'), BigDecimal('-0.020507649298898964'), BigDecimal('1.330365926242124')]
97
+ ]
98
+
99
+ (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
100
+ end
101
+
102
+ def self.d65_to_d50(xyz_array)
103
+ x, y, z = xyz_array
104
+
105
+ conversion_matrix = ::Matrix[
106
+ [BigDecimal('1.0479297925449969'), BigDecimal('0.022946870601609652'), BigDecimal('-0.05019226628920524')],
107
+ [BigDecimal('0.02962780877005599'), BigDecimal('0.9904344267538799'), BigDecimal('-0.017073799063418826')],
108
+ [BigDecimal('-0.009243040646204504'), BigDecimal('0.015055191490298152'), BigDecimal('0.7518742814281371')]
109
+ ]
110
+
111
+ (conversion_matrix * ::Matrix.column_vector([x, y, z])).to_a.flatten
112
+ end
113
+ end
114
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ColorConverters
4
- VERSION = '0.1.4'
4
+ VERSION = '0.1.5'
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: color_converters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Davis
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-08-21 00:00:00.000000000 Z
10
+ date: 2025-08-30 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport