color_converters 0.1.2 → 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,127 +1,110 @@
1
- module ColorConverters
2
- class XyzConverter < BaseConverter
3
- def self.matches?(color_input)
4
- return false unless color_input.is_a?(Hash)
5
-
6
- color_input.keys - [:x, :y, :z] == []
7
- end
8
-
9
- def self.bounds
10
- { x: [0.0, 95.047], y: [0.0, 100.0], z: [0.0, 108.883] }
11
- end
12
-
13
- private
14
-
15
- def validate_input(color_input)
16
- bounds = XyzConverter.bounds
17
- color_input[:x].to_f.between?(*bounds[:x]) && color_input[:y].to_f.between?(*bounds[:y]) && color_input[:z].to_f.between?(*bounds[:z])
18
- end
19
-
20
- def input_to_rgba(color_input)
21
- r, g, b = xyz_to_rgb(color_input)
22
-
23
- { r: r.round(IMPORT_DP), g: g.round(IMPORT_DP), b: b.round(IMPORT_DP), a: 1.0 }
24
- end
25
-
26
- def xyz_to_rgb(xyz_hash)
27
- # Convert XYZ (typically with Y=100 for white) to normalized XYZ (Y=1 for white).
28
- # The transformation matrix expects X, Y, Z values in the 0-1 range.
29
- x = xyz_hash[:x].to_f / 100.0
30
- y = xyz_hash[:y].to_f / 100.0
31
- z = xyz_hash[:z].to_f / 100.0
32
-
33
- # Convert normalized XYZ to Linear sRGB values.
34
- # This uses the inverse sRGB matrix for D65 illuminant.
35
- # The resulting rr, gg, bb values are linear (gamma-uncorrected) and in the 0-1 range.
36
- rr = (x * 3.2404542) + (y * -1.5371385) + (z * -0.4985314)
37
- gg = (x * -0.9692660) + (y * 1.8760108) + (z * 0.0415560)
38
- bb = (x * 0.0556434) + (y * -0.2040259) + (z * 1.0572252)
39
-
40
- # Apply sRGB Companding (gamma correction) to convert from Linear RGB to non-linear sRGB.
41
- # This is defined by the sRGB specification (IEC 61966-2-1).
42
- # The exponent for the non-linear segment is 1/2.4 (approximately 0.41666...).
43
- r, g, b = [rr, gg, bb].map do
44
- if _1 <= 0.0031308
45
- # Linear portion of the sRGB curve
46
- _1 * 12.92
47
- else
48
- # Non-linear (gamma-corrected) portion of the sRGB curve
49
- # The sRGB specification uses an exponent of 1/2.4.
50
- #
51
- (1.055 * (_1**(1.0 / 2.4))) - 0.055
52
-
53
- # IMPORTANT NUMERICAL NOTE:
54
- # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
55
- # the inverse power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
56
- #
57
- # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
58
- # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
59
- #
60
- # Choose 1/2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
61
- # or choose 1/2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
62
- #
63
- # (1.055 * (_1**(1.0 / 2.5))) - 0.055
64
- end
65
- end
66
-
67
- # Scale the 0-1 sRGB value to the 0-255 range for 8-bit color components.
68
- r *= 255.0
69
- g *= 255.0
70
- b *= 255.0
71
-
72
- # Clamping RGB values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
73
- r = r.clamp(0.0..255.0)
74
- g = g.clamp(0.0..255.0)
75
- b = b.clamp(0.0..255.0)
76
-
77
- [r, g, b]
78
- end
79
-
80
- # http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
81
- def self.rgb_to_xyz(rgb_array_frac)
82
- r, g, b = rgb_array_frac
83
-
84
- # Inverse sRGB companding. Linearizes RGB channels with respect to energy.
85
- rr, gg, bb = [r, g, b].map do
86
- if _1 <= 0.04045
87
- _1 / 12.92
88
- else
89
- # sRGB Inverse Companding (Non-linear to Linear RGB)
90
- # The sRGB specification (IEC 61966-2-1) defines the exponent as 2.4.
91
- #
92
- (((_1 + 0.055) / 1.055)**2.4)
93
-
94
- # IMPORTANT NUMERICAL NOTE:
95
- # On this specific system (and confirmed by Wolfram Alpha for direct calculation),
96
- # the power function for val**2.4 yields a result that deviates from the value expected by widely-used color science libraries (like Bruce Lindbloom's).
97
- #
98
- # To compensate for this numerical discrepancy and ensure the final CIELAB values match standard online calculators and specifications,
99
- # an empirically determined exponent of 2.5 has been found to produce the correct linearized sRGB values on this environment.
100
- #
101
- # Choose 2.4 for strict adherence to the standard's definition (knowing your results may slightly deviate from common calculators),
102
- # or choose 2.5 to ensure your calculated linear RGB values (and thus CIELAB) match authoritative external tools on this system.
103
- #
104
- # ((_1 + 0.055) / 1.055)**2.5
105
- end
106
- end
107
-
108
- # Convert using the RGB/XYZ matrix at:
109
- # http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices
110
- x = (rr * 0.4124564) + (gg * 0.3575761) + (bb * 0.1804375)
111
- y = (rr * 0.2126729) + (gg * 0.7151522) + (bb * 0.0721750)
112
- z = (rr * 0.0193339) + (gg * 0.1191920) + (bb * 0.9503041)
113
-
114
- # Now, scale X, Y, Z so that Y for D65 white would be 100.
115
- x *= 100.0
116
- y *= 100.0
117
- z *= 100.0
118
-
119
- # Clamping XYZ values to prevent out-of-gamut issues and numerical errors and ensures these values stay within the valid and expected range.
120
- x = x.clamp(0.0..95.047)
121
- y = y.clamp(0.0..100.0)
122
- z = z.clamp(0.0..108.883)
123
-
124
- [x, y, z]
125
- end
126
- end
127
- 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.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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ColorConverters
4
- VERSION = '0.1.2'
4
+ VERSION = '0.1.4'
5
5
  end
@@ -1,28 +1,31 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
-
5
- require 'color_converters/version'
6
-
7
- require 'color_converters/color'
8
- require 'color_converters/base_converter'
9
-
10
- require 'color_converters/converters/rgb_converter'
11
- require 'color_converters/converters/rgb_string_converter'
12
- require 'color_converters/converters/hex_converter'
13
- require 'color_converters/converters/hsl_converter'
14
- require 'color_converters/converters/hsl_string_converter'
15
- require 'color_converters/converters/hsv_converter'
16
- require 'color_converters/converters/cmyk_converter'
17
- require 'color_converters/converters/xyz_converter'
18
- require 'color_converters/converters/cielab_converter'
19
- require 'color_converters/converters/cielch_converter'
20
-
21
- require 'color_converters/converters/name_converter'
22
- require 'color_converters/converters/null_converter'
23
-
24
- module ColorConverters
25
- class Error < StandardError; end
26
- class InvalidColorError < Error; end
27
- # Your code goes here...
28
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require 'color_converters/version'
6
+
7
+ require 'color_converters/color'
8
+ require 'color_converters/base_converter'
9
+
10
+ # Converters
11
+ require 'color_converters/converters/rgb_converter'
12
+ require 'color_converters/converters/rgb_string_converter'
13
+ require 'color_converters/converters/hex_converter'
14
+ require 'color_converters/converters/hsl_converter'
15
+ require 'color_converters/converters/hsl_string_converter'
16
+ require 'color_converters/converters/hsv_converter'
17
+ require 'color_converters/converters/cmyk_converter'
18
+ require 'color_converters/converters/xyz_converter'
19
+ require 'color_converters/converters/cielab_converter'
20
+ require 'color_converters/converters/cielch_converter'
21
+ require 'color_converters/converters/oklab_converter'
22
+ require 'color_converters/converters/oklch_converter'
23
+
24
+ require 'color_converters/converters/name_converter'
25
+ require 'color_converters/converters/null_converter'
26
+
27
+ module ColorConverters
28
+ class Error < StandardError; end
29
+ class InvalidColorError < Error; end
30
+ # Your code goes here...
31
+ end
metadata CHANGED
@@ -1,28 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: color_converters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Louis Davis
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-26 00:00:00.000000000 Z
10
+ date: 2025-08-21 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 6.1.3
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 6.1.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: color_swatch_collection
14
28
  requirement: !ruby/object:Gem::Requirement
15
29
  requirements:
16
30
  - - "~>"
17
31
  - !ruby/object:Gem::Version
18
- version: 8.0.2
32
+ version: 0.1.0
19
33
  type: :runtime
20
34
  prerelease: false
21
35
  version_requirements: !ruby/object:Gem::Requirement
22
36
  requirements:
23
37
  - - "~>"
24
38
  - !ruby/object:Gem::Version
25
- version: 8.0.2
39
+ version: 0.1.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: matrix
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
26
54
  description: Convert colors to hex/rgb/hsv/cmyk/hsl/xyz/cielab/oklch
27
55
  email:
28
56
  - LouisWilliamDavis@gmail.com
@@ -44,6 +72,8 @@ files:
44
72
  - lib/color_converters/converters/hsv_converter.rb
45
73
  - lib/color_converters/converters/name_converter.rb
46
74
  - lib/color_converters/converters/null_converter.rb
75
+ - lib/color_converters/converters/oklab_converter.rb
76
+ - lib/color_converters/converters/oklch_converter.rb
47
77
  - lib/color_converters/converters/rgb_converter.rb
48
78
  - lib/color_converters/converters/rgb_string_converter.rb
49
79
  - lib/color_converters/converters/xyz_converter.rb