color 1.8 → 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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +298 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +70 -0
- data/CONTRIBUTORS.md +10 -0
- data/LICENCE.md +27 -0
- data/Manifest.txt +11 -23
- data/README.md +54 -0
- data/Rakefile +74 -61
- data/SECURITY.md +34 -0
- data/lib/color/cielab.rb +348 -0
- data/lib/color/cmyk.rb +279 -213
- data/lib/color/grayscale.rb +128 -160
- data/lib/color/hsl.rb +205 -173
- data/lib/color/rgb/colors.rb +177 -163
- data/lib/color/rgb.rb +531 -541
- data/lib/color/version.rb +5 -0
- data/lib/color/xyz.rb +214 -0
- data/lib/color/yiq.rb +91 -46
- data/lib/color.rb +208 -142
- data/test/fixtures/cielab.json +444 -0
- data/test/minitest_helper.rb +20 -4
- data/test/test_cmyk.rb +49 -72
- data/test/test_color.rb +58 -112
- data/test/test_grayscale.rb +35 -57
- data/test/test_hsl.rb +71 -77
- data/test/test_rgb.rb +195 -267
- data/test/test_yiq.rb +12 -30
- metadata +90 -107
- data/.autotest +0 -5
- data/.coveralls.yml +0 -2
- data/.gemtest +0 -0
- data/.hoerc +0 -2
- data/.minitest.rb +0 -2
- data/.travis.yml +0 -41
- data/Code-of-Conduct.rdoc +0 -41
- data/Contributing.rdoc +0 -62
- data/Gemfile +0 -9
- data/History.rdoc +0 -194
- data/Licence.rdoc +0 -27
- data/README.rdoc +0 -52
- data/lib/color/css.rb +0 -7
- data/lib/color/palette/adobecolor.rb +0 -260
- data/lib/color/palette/gimp.rb +0 -104
- data/lib/color/palette/monocontrast.rb +0 -164
- data/lib/color/palette.rb +0 -4
- data/lib/color/rgb/contrast.rb +0 -57
- data/lib/color/rgb/metallic.rb +0 -28
- data/test/test_adobecolor.rb +0 -405
- data/test/test_css.rb +0 -19
- data/test/test_gimp.rb +0 -87
- data/test/test_monocontrast.rb +0 -130
data/Rakefile
CHANGED
@@ -1,75 +1,88 @@
|
|
1
|
-
|
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
|
-
|
4
|
-
|
5
|
-
require 'rake/clean'
|
8
|
+
Hoe.plugin :halostatue
|
9
|
+
Hoe.plugin :rubygems
|
6
10
|
|
7
|
-
Hoe.
|
8
|
-
Hoe.
|
9
|
-
Hoe.
|
10
|
-
Hoe.
|
11
|
-
Hoe.
|
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
|
-
|
15
|
-
developer(
|
16
|
-
developer(
|
17
|
+
hoe = Hoe.spec "color" do
|
18
|
+
developer("Austin Ziegler", "halostatue@gmail.com")
|
19
|
+
developer("Matt Lyon", "matt@postsomnia.com")
|
17
20
|
|
18
|
-
|
21
|
+
self.trusted_release = ENV["rubygems_release_gem"] == "true"
|
19
22
|
|
20
|
-
|
21
|
-
self.readme_file = 'README.rdoc'
|
23
|
+
require_ruby_version ">= 3.2"
|
22
24
|
|
23
|
-
|
24
|
-
extra_dev_deps << ['hoe-gemspec2', '~> 1.1']
|
25
|
-
extra_dev_deps << ['hoe-git', '~> 1.6']
|
26
|
-
extra_dev_deps << ['hoe-travis', '~> 1.2']
|
27
|
-
extra_dev_deps << ['minitest', '~> 5.8']
|
28
|
-
extra_dev_deps << ['minitest-around', '~> 0.3']
|
29
|
-
extra_dev_deps << ['minitest-autotest', '~> 1.0']
|
30
|
-
extra_dev_deps << ['minitest-bisect', '~> 1.2']
|
31
|
-
extra_dev_deps << ['minitest-focus', '~> 1.1']
|
32
|
-
extra_dev_deps << ['minitest-moar', '~> 0.0']
|
33
|
-
extra_dev_deps << ['minitest-pretty_diff', '~> 0.1']
|
34
|
-
extra_dev_deps << ['rake', '~> 10.0']
|
25
|
+
license "MIT"
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
end
|
27
|
+
spec_extras[:metadata] = ->(val) {
|
28
|
+
val.merge!({"rubygems_mfa_required" => "true"})
|
29
|
+
}
|
41
30
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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"]
|
45
|
+
end
|
57
46
|
|
58
|
-
|
59
|
-
|
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"
|
60
59
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
'SimpleCov.start("test_frameworks") { command_name "Minitest" }',
|
66
|
-
'gem "minitest"'
|
67
|
-
].join('; ')
|
68
|
-
Rake::Task['test'].execute
|
69
|
-
end
|
60
|
+
SimpleCov::Formatter::LcovFormatter.config do |config|
|
61
|
+
config.report_with_single_file = true
|
62
|
+
config.lcov_file_name = "lcov.info"
|
63
|
+
end
|
70
64
|
|
71
|
-
|
65
|
+
SimpleCov.start "test_frameworks" do
|
66
|
+
enable_coverage :branch
|
67
|
+
primary_coverage :branch
|
68
|
+
formatter #{formatters}
|
72
69
|
end
|
70
|
+
RUBY
|
71
|
+
end
|
72
|
+
|
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"
|
73
86
|
end
|
74
87
|
|
75
|
-
|
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
|
data/lib/color/cielab.rb
ADDED
@@ -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
|