color 1.7.1 → 2.0.0.pre.0

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.
Files changed (53) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +298 -0
  3. data/CODE_OF_CONDUCT.md +128 -0
  4. data/CONTRIBUTING.md +70 -0
  5. data/CONTRIBUTORS.md +10 -0
  6. data/LICENCE.md +27 -0
  7. data/Manifest.txt +11 -21
  8. data/README.md +54 -0
  9. data/Rakefile +74 -53
  10. data/SECURITY.md +34 -0
  11. data/lib/color/cielab.rb +348 -0
  12. data/lib/color/cmyk.rb +279 -213
  13. data/lib/color/grayscale.rb +128 -160
  14. data/lib/color/hsl.rb +205 -173
  15. data/lib/color/rgb/colors.rb +177 -163
  16. data/lib/color/rgb.rb +534 -537
  17. data/lib/color/version.rb +5 -0
  18. data/lib/color/xyz.rb +214 -0
  19. data/lib/color/yiq.rb +91 -46
  20. data/lib/color.rb +208 -141
  21. data/test/fixtures/cielab.json +444 -0
  22. data/test/minitest_helper.rb +20 -4
  23. data/test/test_cmyk.rb +49 -71
  24. data/test/test_color.rb +58 -106
  25. data/test/test_grayscale.rb +35 -56
  26. data/test/test_hsl.rb +72 -76
  27. data/test/test_rgb.rb +195 -267
  28. data/test/test_yiq.rb +12 -30
  29. metadata +165 -150
  30. checksums.yaml.gz.sig +0 -0
  31. data/.autotest +0 -5
  32. data/.gemtest +0 -0
  33. data/.hoerc +0 -2
  34. data/.minitest.rb +0 -2
  35. data/.travis.yml +0 -35
  36. data/Contributing.rdoc +0 -60
  37. data/Gemfile +0 -9
  38. data/History.rdoc +0 -172
  39. data/Licence.rdoc +0 -27
  40. data/README.rdoc +0 -50
  41. data/lib/color/css.rb +0 -7
  42. data/lib/color/palette/adobecolor.rb +0 -260
  43. data/lib/color/palette/gimp.rb +0 -104
  44. data/lib/color/palette/monocontrast.rb +0 -164
  45. data/lib/color/palette.rb +0 -4
  46. data/lib/color/rgb/contrast.rb +0 -57
  47. data/lib/color/rgb/metallic.rb +0 -28
  48. data/test/test_adobecolor.rb +0 -405
  49. data/test/test_css.rb +0 -19
  50. data/test/test_gimp.rb +0 -87
  51. data/test/test_monocontrast.rb +0 -130
  52. data.tar.gz.sig +0 -0
  53. metadata.gz.sig +0 -0
data/Rakefile CHANGED
@@ -1,67 +1,88 @@
1
- # -*- ruby encoding: utf-8 -*-
1
+ require "rubygems"
2
+ require "hoe"
3
+ require "rake/clean"
4
+ require "rdoc/task"
5
+ require "minitest"
6
+ require "minitest/test_task"
2
7
 
3
- require 'rubygems'
4
- require 'hoe'
8
+ Hoe.plugin :halostatue
9
+ Hoe.plugin :rubygems
5
10
 
6
- Hoe.plugin :doofus
7
- Hoe.plugin :email
8
- Hoe.plugin :gemspec2
9
- Hoe.plugin :git
10
- Hoe.plugin :minitest
11
- Hoe.plugin :travis
12
- Hoe.plugin :email unless ENV['CI'] or ENV['TRAVIS']
11
+ Hoe.plugins.delete :debug
12
+ Hoe.plugins.delete :newb
13
+ Hoe.plugins.delete :publish
14
+ Hoe.plugins.delete :signing
15
+ Hoe.plugins.delete :test
13
16
 
14
- spec = Hoe.spec 'color' do
15
- developer('Austin Ziegler', 'halostatue@gmail.com')
16
- developer('Matt Lyon', 'matt@postsomnia.com')
17
+ hoe = Hoe.spec "color" do
18
+ developer("Austin Ziegler", "halostatue@gmail.com")
19
+ developer("Matt Lyon", "matt@postsomnia.com")
17
20
 
18
- license 'MIT'
21
+ self.trusted_release = ENV["rubygems_release_gem"] == "true"
19
22
 
20
- self.need_tar = true
23
+ require_ruby_version ">= 3.2"
21
24
 
22
- self.history_file = 'History.rdoc'
23
- self.readme_file = 'README.rdoc'
24
- self.extra_rdoc_files = FileList["*.rdoc"].to_a
25
+ license "MIT"
25
26
 
26
- self.extra_dev_deps << ['hoe-doofus', '~> 1.0']
27
- self.extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
28
- self.extra_dev_deps << ['hoe-git', '~> 1.5']
29
- self.extra_dev_deps << ['hoe-rubygems', '~> 1.0']
30
- self.extra_dev_deps << ['hoe-travis', '~> 1.2']
31
- self.extra_dev_deps << ['minitest', '~> 5.0']
32
- self.extra_dev_deps << ['rake', '~> 10.0']
27
+ spec_extras[:metadata] = ->(val) {
28
+ val.merge!({"rubygems_mfa_required" => "true"})
29
+ }
33
30
 
34
- if RUBY_VERSION >= '1.9' and (ENV['CI'] or ENV['TRAVIS'])
35
- self.extra_dev_deps << ['simplecov', '~> 0.7']
36
- self.extra_dev_deps << ['coveralls', '~> 0.7']
37
- end
31
+ extra_dev_deps << ["hoe", "~> 4.0"]
32
+ extra_dev_deps << ["hoe-halostatue", "~> 2.0"]
33
+ extra_dev_deps << ["hoe-doofus", "~> 1.0"]
34
+ extra_dev_deps << ["hoe-rubygems", "~> 1.0"]
35
+ extra_dev_deps << ["hoe-gemspec2", "~> 1.4"]
36
+ extra_dev_deps << ["hoe-git", "~> 1.6"]
37
+ extra_dev_deps << ["minitest", "~> 5.8"]
38
+ extra_dev_deps << ["minitest-autotest", "~> 1.0"]
39
+ extra_dev_deps << ["minitest-focus", "~> 1.1"]
40
+ extra_dev_deps << ["minitest-moar", "~> 0.0"]
41
+ extra_dev_deps << ["rake", ">= 10.0", "< 14"]
42
+ extra_dev_deps << ["rdoc", ">= 0.0"]
43
+ extra_dev_deps << ["standard", "~> 1.0"]
44
+ extra_dev_deps << ["json", ">= 0.0"]
38
45
  end
39
46
 
40
- if RUBY_VERSION >= '1.9'
41
- namespace :test do
42
- desc "Submit test coverage to Coveralls"
43
- task :coveralls do
44
- spec.test_prelude = [
45
- 'require "psych"',
46
- 'require "simplecov"',
47
- 'require "coveralls"',
48
- 'SimpleCov.formatter = Coveralls::SimpleCov::Formatter',
49
- 'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
50
- 'gem "minitest"'
51
- ].join('; ')
52
- Rake::Task['test'].execute
53
- end
47
+ Minitest::TestTask.create :test
48
+ Minitest::TestTask.create :coverage do |t|
49
+ formatters = <<-RUBY.split($/).join(" ")
50
+ SimpleCov::Formatter::MultiFormatter.new([
51
+ SimpleCov::Formatter::HTMLFormatter,
52
+ SimpleCov::Formatter::LcovFormatter,
53
+ SimpleCov::Formatter::SimpleFormatter
54
+ ])
55
+ RUBY
56
+ t.test_prelude = <<-RUBY.split($/).join("; ")
57
+ require "simplecov"
58
+ require "simplecov-lcov"
59
+
60
+ SimpleCov::Formatter::LcovFormatter.config do |config|
61
+ config.report_with_single_file = true
62
+ config.lcov_file_name = "lcov.info"
63
+ end
54
64
 
55
- desc "Runs test coverage. Only works Ruby 1.9+ and assumes 'simplecov' is installed."
56
- task :coverage do
57
- spec.test_prelude = [
58
- 'require "simplecov"',
59
- 'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
60
- 'gem "minitest"'
61
- ].join('; ')
62
- Rake::Task['test'].execute
63
- end
65
+ SimpleCov.start "test_frameworks" do
66
+ enable_coverage :branch
67
+ primary_coverage :branch
68
+ formatter #{formatters}
64
69
  end
70
+ RUBY
71
+ end
65
72
 
66
- Rake::Task['travis'].prerequisites.replace(%w(test:coveralls))
73
+ task default: :test
74
+
75
+ task :version do
76
+ require "color/version"
77
+ puts Color::VERSION
78
+ end
79
+
80
+ RDoc::Task.new do
81
+ _1.title = "Color -- Color Math with Ruby"
82
+ _1.main = "lib/color.rb"
83
+ _1.rdoc_dir = "doc"
84
+ _1.rdoc_files = hoe.spec.require_paths - ["Manifest.txt"] + hoe.spec.extra_rdoc_files
85
+ _1.markup = "markdown"
67
86
  end
87
+
88
+ task docs: :rerdoc
data/SECURITY.md ADDED
@@ -0,0 +1,34 @@
1
+ # color Security
2
+
3
+ ## Supported Versions
4
+
5
+ Security reports are accepted for the most recent major release and the previous
6
+ version for a limited time after the initial major release version. After a
7
+ major release, the previous version will receive full support for three months
8
+ and security support for an additional three months (for a total of six months).
9
+
10
+ Because color 1.x supports a wide range of Ruby versions that are themselves end
11
+ of life, security reports will only be accepted when they can be demonstrated on
12
+ Ruby 3.2 or higher.
13
+
14
+ > | Version | Release Date | Support Ends | Security Support Ends |
15
+ > | ------- | ------------ | -------------- | --------------------- |
16
+ > | 1.x | 2015-10-26 | 2.x + 3 months | 2.x + 6 months |
17
+ > | 2.x | 2025-MM-DD | - | - |
18
+
19
+ ## Reporting a Vulnerability
20
+
21
+ By preference, use the [Tidelift security contact][tidelift]. Tidelift will
22
+ coordinate the fix and disclosure.
23
+
24
+ Alternatively, Send an email to [color@halostatue.ca][email] with the text
25
+ `Color` in the subject. Emails sent to this address should be encrypted using
26
+ [age][age] with the following public key:
27
+
28
+ ```
29
+ age1fc6ngxmn02m62fej5cl30lrvwmxn4k3q2atqu53aatekmnqfwumqj4g93w
30
+ ```
31
+
32
+ [tidelift]: https://tidelift.com/security
33
+ [email]: mailto:color@halostatue.ca
34
+ [age]: https://github.com/FiloSottile/age
@@ -0,0 +1,348 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # A \Color object for the \CIELAB color space (also known as L\*a\*\b*). Color is
5
+ # expressed in a three-dimensional, device-independent "standard observer" model, often
6
+ # in relation to a "reference white" color, usually Color::XYZ::D65 (most purposes) or
7
+ # Color::XYZ::D50 (printing).
8
+ #
9
+ # `L*` is the perceptual lightness, bounded to values between 0 (black) and 100 (white).
10
+ # `a*` is the range of green (negative) / red (positive) and `b*` is the range of blue
11
+ # (negative) / yellow (positive).
12
+ #
13
+ # The `a*` and `b*` ranges are _technically_ unbounded but \Color clamps them to the
14
+ # values `-128..127`.
15
+ #
16
+ # For more information, see [CIELAB](https://en.wikipedia.org/wiki/CIELAB_color_space).
17
+ #
18
+ # \CIELAB colors are immutable Data class instances. Array deconstruction is `[l, a, b]`
19
+ # and hash deconstruction is `{l:, a:, b:}` (see #l, #a, #b).
20
+ class Color::CIELAB
21
+ include Color
22
+
23
+ ##
24
+ # Standard weights applied for perceptual differences using the ΔE*94 algorithm.
25
+ DE94_WEIGHTS = {
26
+ graphic_arts: {k_1: 0.045, k_2: 0.015, k_l: 1.0}.freeze,
27
+ textiles: {k_1: 0.048, k_2: 0.014, k_l: 2.0}.freeze
28
+ }.freeze
29
+
30
+ RANGES = {L: 0.0..100.0, ab: -128.0..127.0}.freeze # :nodoc:
31
+ private_constant :RANGES
32
+
33
+ ##
34
+ # :attr_reader: l
35
+ # The `L*` attribute of this \CIELAB color object expressed as a value 0..100.
36
+
37
+ ##
38
+ # :attr_reader: a
39
+ # The `a*` attribute of this \CIELAB color object expressed as a value -128..127.
40
+
41
+ ##
42
+ # :attr_reader: b
43
+ # The `b*` attribute of this \CIELAB color object expressed as a value -128..127.
44
+
45
+ ##
46
+ # Creates a \CIELAB color representation from percentage values.
47
+ #
48
+ # `l` must be between 0% and 100%; `a` and `b` must be between -100% and 100% and will
49
+ # be transposed to the native value -128..127.
50
+ #
51
+ # ```ruby
52
+ # Color::CIELAB.from_percentage(10, -30, 30) # => CIELAB [10.0000 -38.7500 37.7500]
53
+ # ```
54
+ #
55
+ # :call-seq:
56
+ # from_percentage(l, a, b)
57
+ # from_percentage(l:, a:, b:)
58
+ def self.from_percentage(*args, **kwargs)
59
+ l, a, b =
60
+ case [args, kwargs]
61
+ in [[_, _, _], {}]
62
+ args
63
+ in [[], {l:, a:, b:}]
64
+ [l, a, b]
65
+ else
66
+ new(*args, **kwargs)
67
+ end
68
+
69
+ new(
70
+ l: l,
71
+ a: Color.translate_range(a, from: -100.0..100.0, to: RANGES[:ab]),
72
+ b: Color.translate_range(b, from: -100.0..100.0, to: RANGES[:ab])
73
+ )
74
+ end
75
+
76
+ class << self
77
+ alias_method :from_values, :new
78
+ alias_method :from_internal, :new # :nodoc:
79
+ end
80
+
81
+ ##
82
+ # Creates a \CIELAB color representation from `L*a*b*` native values. The `l` value
83
+ # must be between 0 and 100 and the `a` and `b` values must be between -128 and 127.
84
+ #
85
+ # ```ruby
86
+ # Color::CIELAB.new(10, 35, -35) # => CIELAB [10.00 35.00 -35.00]
87
+ # Color::CIELAB.from_values(10, 35, -35) # => CIELAB [10.00 35.00 -35.00]
88
+ # Color::CIELAB[l: 10, a: 35, b: -35] # => CIELAB [10.00 35.00 -35.00]
89
+ # ```
90
+ def initialize(l:, a:, b:)
91
+ super(
92
+ l: normalize(l, RANGES[:L]),
93
+ a: normalize(a, RANGES[:ab]),
94
+ b: normalize(b, RANGES[:ab])
95
+ )
96
+ end
97
+
98
+ ##
99
+ # Coerces the other Color object into \CIELAB.
100
+ def coerce(other) = other.to_lab
101
+
102
+ ##
103
+ # Converts \CIELAB to Color::CMYK via Color::RGB.
104
+ #
105
+ # See #to_rgb and Color::RGB#to_cmyk.
106
+ def to_cmyk(...) = to_rgb(...).to_cmyk(...)
107
+
108
+ ##
109
+ # Converts \CIELAB to Color::Grayscale via Color::RGB.
110
+ #
111
+ # See #to_rgb and Color::RGB#to_grayscale.
112
+ def to_grayscale(...) = to_rgb(...).to_grayscale(...)
113
+
114
+ ##
115
+ def to_lab(...) = self
116
+
117
+ ##
118
+ # Converts \CIELAB to Color::HSL via Color::RGB.
119
+ #
120
+ # See #to_rgb and Color::RGB#to_hsl.
121
+ def to_hsl(...) = to_rgb(...).to_hsl(...)
122
+
123
+ ##
124
+ # Converts \CIELAB to Color::RGB via Color::XYZ.
125
+ #
126
+ # See #to_xyz and Color::XYZ#to_rgb.
127
+ def to_rgb(...) = to_xyz(...).to_rgb(...)
128
+
129
+ ##
130
+ # Converts \CIELAB to Color::XYZ based on a reference white.
131
+ #
132
+ # Accepts a single keyword parameter, `white`, indicating the reference white used for
133
+ # conversion scaling. If none is provided, Color::XYZ::D65 is used.
134
+ #
135
+ # :call-seq:
136
+ # to_xyz(white: Color::XYZ::D65)
137
+ def to_xyz(*args, **kwargs)
138
+ fy = (l + 16.0) / 116
139
+ fz = fy - b / 200.0
140
+ fx = a / 500.0 + fy
141
+
142
+ xr = ((fx3 = fx**3) > Color::XYZ::E) ? fx3 : (116.0 * fx - 16) / Color::XYZ::K
143
+ yr = (l > Color::XYZ::EK) ? ((l + 16.0) / 116)**3 : l
144
+ zr = ((fz3 = fz**3) > Color::XYZ::E) ? fz3 : (116.0 * fz - 16) / Color::XYZ::K
145
+
146
+ ref = kwargs[:white] || args.first
147
+ ref = Color::XYZ::D65 unless ref.is_a?(Color::XYZ)
148
+
149
+ ref.scale(xr, yr, zr)
150
+ end
151
+
152
+ ##
153
+ # Render the CSS `lab()` function for this \CIELAB object, adding an `alpha` if
154
+ # provided.
155
+ def css(alpha: nil, **)
156
+ params = [css_value(l, :percent), css_value(a), css_value(b)].join(" ")
157
+ params = "#{params} / #{css_value(alpha)}" if alpha
158
+
159
+ "lab(#{params})"
160
+ end
161
+
162
+ ##
163
+ # Implements the \CIELAB ΔE* 2000 perceptual color distance metric with more reliable
164
+ # results over \CIELAB ΔE* 1994.
165
+ #
166
+ # See [CIEDE2000][ciede2000] for precise details on the mathematical formulas. The
167
+ # implementation here is based on Sharma, Wu, and Dala in [CIEDE2000.xls][ciede2000xls],
168
+ # published as supplementary materials for their paper "The CIEDE2000 Color-Difference
169
+ # Formula: Implementation Notes, Supplementary Test Data, and Mathematical
170
+ # Observations,", G. Sharma, W. Wu, E. N. Dalal, Color Research and Application, vol.
171
+ # 30. No. 1, pp. 21-30, February 2005.
172
+ #
173
+ # Do not override the `klch` parameter unless you _really_ know what you're doing.
174
+ #
175
+ # See also <http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE2000.html>
176
+ #
177
+ # [ciede2000]: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
178
+ # [ciede2000xls]: http://www.ece.rochester.edu/~gsharma/ciede2000/dataNprograms/CIEDE2000.xls
179
+ def delta_e2000(other, klch: {L: 1.0, C: 1.0, H: 1.0})
180
+ other = coerce(other)
181
+ klch => L: k_l, C: k_c, H: k_h
182
+ self => l: l_star_1, a: a_star_1, b: b_star_1
183
+ other => l: l_star_2, a: a_star_2, b: b_star_2
184
+
185
+ v_25_pow_7 = 25**7
186
+
187
+ c_star_1 = Math.sqrt(a_star_1**2 + b_star_1**2)
188
+ c_star_2 = Math.sqrt(a_star_2**2 + b_star_2**2)
189
+
190
+ c_mean = ((c_star_1 + c_star_2) / 2.0)
191
+ c_mean_pow_7 = c_mean**7
192
+ c_mean_g = (0.5 * (1.0 - Math.sqrt(c_mean_pow_7 / (c_mean_pow_7 + v_25_pow_7))))
193
+
194
+ a_1_prime = ((1.0 + c_mean_g) * a_star_1)
195
+ a_2_prime = ((1.0 + c_mean_g) * a_star_2)
196
+
197
+ c_1_prime = Math.sqrt(a_1_prime**2 + b_star_1**2)
198
+ c_2_prime = Math.sqrt(a_2_prime**2 + b_star_2**2)
199
+
200
+ h_1_prime =
201
+ if a_1_prime + b_star_1 == 0
202
+ 0
203
+ else
204
+ (to_degrees(Math.atan2(b_star_1, a_1_prime)) % 360.0)
205
+ end
206
+ h_2_prime =
207
+ if a_2_prime + b_star_2 == 0
208
+ 0
209
+ else
210
+ (to_degrees(Math.atan2(b_star_2, a_2_prime)) % 360.0)
211
+ end
212
+
213
+ delta_lower_h_prime =
214
+ if h_2_prime - h_1_prime < -180
215
+ h_2_prime + 360 - h_1_prime
216
+ elsif h_2_prime - h_1_prime > 180
217
+ h_2_prime - h_1_prime - 360.0
218
+ else
219
+ h_2_prime - h_1_prime
220
+ end
221
+
222
+ delta_upper_l_prime = l_star_2 - l_star_1
223
+ delta_upper_c_prime = c_2_prime - c_1_prime
224
+ delta_upper_h_prime = (
225
+ 2.0 *
226
+ Math.sqrt(c_1_prime * c_2_prime) *
227
+ Math.sin(to_radians(delta_lower_h_prime / 2.0))
228
+ )
229
+
230
+ l_prime_mean = ((l_star_1 + l_star_2) / 2.0)
231
+ c_prime_mean = ((c_1_prime + c_2_prime) / 2.0)
232
+ h_prime_mean =
233
+ if c_1_prime * c_2_prime == 0
234
+ h_1_prime + h_2_prime
235
+ elsif (h_2_prime - h_1_prime).abs <= 180
236
+ ((h_1_prime + h_2_prime) / 2.0)
237
+ elsif h_2_prime + h_1_prime <= 360
238
+ ((h_1_prime + h_2_prime) / 2.0 + 180.0)
239
+ else
240
+ ((h_1_prime + h_2_prime) / 2.0 - 180.0)
241
+ end
242
+
243
+ l_prime_mean50sq = ((l_prime_mean - 50)**2)
244
+
245
+ upper_s_l = (1 + (0.015 * l_prime_mean50sq / Math.sqrt(20 + l_prime_mean50sq)))
246
+ upper_s_c = (1 + 0.045 * c_prime_mean)
247
+ upper_t = (
248
+ 1 -
249
+ 0.17 * Math.cos(to_radians(h_prime_mean - 30)) +
250
+ 0.24 * Math.cos(to_radians(2 * h_prime_mean)) +
251
+ 0.32 * Math.cos(to_radians(3 * h_prime_mean + 6)) -
252
+ 0.2 * Math.cos(to_radians(4 * h_prime_mean - 63))
253
+ )
254
+
255
+ upper_s_h = (1 + 0.015 * c_prime_mean * upper_t)
256
+
257
+ delta_theta = (30 * Math.exp(-1 * ((h_prime_mean - 275) / 25.0)**2))
258
+ upper_r_c = (2 * Math.sqrt(c_prime_mean**7 / (c_prime_mean**7 + v_25_pow_7)))
259
+ upper_r_t = (-Math.sin(to_radians(2 * delta_theta)) * upper_r_c)
260
+ delta_l_prime_div_kl_div_sl = (delta_upper_l_prime / upper_s_l / k_l.to_f)
261
+ delta_c_prime_div_kc_div_sc = (delta_upper_c_prime / upper_s_c / k_c.to_f)
262
+ delta_h_prime_div_kh_div_sh = (delta_upper_h_prime / upper_s_h / k_h.to_f)
263
+
264
+ Math.sqrt(
265
+ delta_l_prime_div_kl_div_sl**2 +
266
+ delta_c_prime_div_kc_div_sc**2 +
267
+ delta_h_prime_div_kh_div_sh**2 +
268
+ upper_r_t * delta_c_prime_div_kc_div_sc * delta_h_prime_div_kh_div_sh
269
+ )
270
+ end
271
+
272
+ ##
273
+ # Implements the \CIELAB ΔE* 1994 perceptual color distance metric. This version is an
274
+ # improvement over previous versions, but it does not handle perceptual discontinuities
275
+ # as well as \CIELAB ΔE* 2000. This is implemented because some functions still require
276
+ # the 1994 algorithm for proper operation.
277
+ #
278
+ # See [CIE94][cie94] for precise details on the mathematical formulas.
279
+ #
280
+ # Different weights for `k_l`, `k_1`, and `k_2` may be applied via the `weight` keyword
281
+ # parameter. This may be provided either as a Hash with `k_l`, `k_1`, and `k_2` values
282
+ # or as a key to DE94_WEIGHTS. The default weight is `:graphic_arts`.
283
+ #
284
+ # See also <http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html>.
285
+ #
286
+ # [cie94]: https://en.wikipedia.org/wiki/Color_difference#CIE94
287
+ def delta_e94(other, weight: :graphic_arts)
288
+ weight = DE94_WEIGHTS[weight] if DE94_WEIGHTS.key?(weight)
289
+ raise ArgumentError, "Unsupported weight #{weight.inspect}." unless weight.is_a?(Hash)
290
+
291
+ weight => k_1:, k_2:, k_l:
292
+
293
+ # Under some circumstances in real computers, the computed value of ΔH could be an
294
+ # imaginary number (it's a square root value), so instead of √(((ΔL/(kL*sL))²) +
295
+ # ((ΔC/(kC*sC))²) + ((ΔH/(kH*sH))²)), we have implemented the final computation as
296
+ # √(((ΔL/(kL*sL))²) + ((ΔC/(kC*sC))²) + (ΔH2/(kH*sH)²)) and not performing the square
297
+ # root when computing ΔH2.
298
+
299
+ k_c = k_h = 1.0
300
+
301
+ other = coerce(other)
302
+
303
+ self => l: l_1, a: a_1, b: b_1
304
+ other => l: l_2, a: a_2, b: b_2
305
+
306
+ delta_a = a_1 - a_2
307
+ delta_b = b_1 - b_2
308
+
309
+ cab_1 = Math.sqrt((a_1**2) + (b_1**2))
310
+ cab_2 = Math.sqrt((a_2**2) + (b_2**2))
311
+
312
+ delta_upper_l = l_1 - l_2
313
+ delta_upper_c = cab_1 - cab_2
314
+
315
+ delta_h2 = (delta_a**2) + (delta_b**2) - (delta_upper_c**2)
316
+
317
+ s_upper_l = 1.0
318
+ s_upper_c = 1 + k_1 * cab_1
319
+ s_upper_h = 1 + k_2 * cab_1
320
+
321
+ composite_upper_l = (delta_upper_l / (k_l * s_upper_l))**2
322
+ composite_upper_c = (delta_upper_c / (k_c * s_upper_c))**2
323
+ composite_upper_h = delta_h2 / ((k_h * s_upper_h)**2)
324
+ Math.sqrt(composite_upper_l + composite_upper_c + composite_upper_h)
325
+ end
326
+
327
+ ##
328
+ alias_method :to_a, :deconstruct
329
+
330
+ ##
331
+ alias_method :to_internal, :deconstruct # :nodoc:
332
+
333
+ ##
334
+ def inspect = "CIELAB [%.4f %.4f %.4f]" % [l, a, b] # :nodoc:
335
+
336
+ ##
337
+ def pretty_print(q) # :nodoc:
338
+ q.text "CIELAB"
339
+ q.breakable
340
+ q.group 2, "[", "]" do
341
+ q.text "%.4f" % l
342
+ q.fill_breakable
343
+ q.text "%.4f" % a
344
+ q.fill_breakable
345
+ q.text "%.4f" % b
346
+ end
347
+ end
348
+ end