color 1.8 → 2.0.1
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 +314 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +84 -0
- data/CONTRIBUTORS.md +11 -0
- data/LICENCE.md +50 -0
- data/Manifest.txt +12 -23
- data/README.md +54 -0
- data/Rakefile +72 -61
- data/SECURITY.md +39 -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 +211 -161
- data/lib/color/rgb.rb +527 -551
- 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 +204 -142
- data/licences/dco.txt +34 -0
- 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 +104 -121
- 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/lib/color.rb
CHANGED
@@ -1,113 +1,222 @@
|
|
1
|
-
# :
|
2
|
-
|
3
|
-
|
4
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# # \Color -- \Color Math in Ruby
|
4
|
+
#
|
5
|
+
# - **code**: [github.com/halostatue/color](https://github.com/halostatue/color)
|
6
|
+
# - **issues**: [github.com/halostatue/color/issues](https://github.com/halostatue/color/issues)
|
7
|
+
# - **changelog**: [CHANGELOG](rdoc-ref:CHANGELOG.md)
|
8
|
+
#
|
9
|
+
# \Color is a Ruby library to provide RGB, CMYK, HSL, and other color space manipulation
|
10
|
+
# support to applications that require it. It provides optional named RGB colors that are
|
11
|
+
# commonly supported in HTML, SVG, and X11 applications.
|
12
|
+
#
|
13
|
+
# The \Color library performs purely mathematical manipulation of the colors based on
|
14
|
+
# color theory without reference to device color profiles (such as sRGB or Adobe RGB). For
|
15
|
+
# most purposes, when working with RGB and HSL color spaces, this won't matter. Absolute
|
16
|
+
# color spaces (like CIE LAB and CIE XYZ) cannot be reliably converted to relative color
|
17
|
+
# spaces (like RGB) without color profiles. When necessary for conversions, \Color
|
18
|
+
# provides \D65 and \D50 reference white values in Color::XYZ.
|
19
|
+
#
|
20
|
+
# Color 2.0 is a major release, dropping support for all versions of Ruby prior to 3.2 as
|
21
|
+
# well as removing or renaming a number of features. The main breaking changes are:
|
22
|
+
#
|
23
|
+
# - Color classes are immutable Data objects; they are no longer mutable.
|
24
|
+
# - RGB named colors are no longer loaded on gem startup, but must be required explicitly
|
25
|
+
# (this is _not_ done via `autoload` because there are more than 100 named colors with
|
26
|
+
# spelling variations) with `require "color/rgb/colors"`.
|
27
|
+
# - Color palettes have been removed.
|
28
|
+
# - `Color::CSS` and `Color::CSS#[]` have been removed.
|
5
29
|
module Color
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
#
|
15
|
-
|
16
|
-
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
#
|
30
|
+
##
|
31
|
+
# The maximum "resolution" for color math; if any value is less than or equal to this
|
32
|
+
# value, it is treated as zero.
|
33
|
+
EPSILON = 1e-5
|
34
|
+
|
35
|
+
##
|
36
|
+
# The tolerance for comparing the components of two colors. In general, colors are
|
37
|
+
# considered equal if all of their components are within this tolerance value of each
|
38
|
+
# other.
|
39
|
+
TOLERANCE = 1e-4
|
40
|
+
|
41
|
+
# :stopdoc:
|
42
|
+
CIELAB = Data.define(:l, :a, :b)
|
43
|
+
CMYK = Data.define(:c, :m, :y, :k)
|
44
|
+
Grayscale = Data.define(:g)
|
45
|
+
HSL = Data.define(:h, :s, :l)
|
46
|
+
RGB = Data.define(:r, :g, :b, :names)
|
47
|
+
XYZ = Data.define(:x, :y, :z)
|
48
|
+
YIQ = Data.define(:y, :i, :q)
|
49
|
+
# :startdoc:
|
50
|
+
|
51
|
+
##
|
52
|
+
# It is useful to know the number of components in some cases. Since most colors are
|
53
|
+
# defined with three components, we define a constant value here. Color classes that
|
54
|
+
# require more or less should override this.
|
26
55
|
#
|
27
|
-
#
|
28
|
-
#
|
56
|
+
# We _could_ define this as `members.count`, but this would require a special case
|
57
|
+
# for Color::RGB _regardless_ because there's an additional member for RGB colors
|
58
|
+
# (names).
|
59
|
+
def components = 3 # :nodoc:
|
60
|
+
|
61
|
+
##
|
62
|
+
# Compares the `other` color to this one. The `other` color will be coerced to the same
|
63
|
+
# type as the current color. Such converted color comparisons will always be more
|
64
|
+
# approximate than non-converted comparisons.
|
29
65
|
#
|
30
|
-
# All values are compared as floating-point values, so two
|
31
|
-
#
|
32
|
-
# of each other.
|
66
|
+
# All values are compared as floating-point values, so two colors will be reported
|
67
|
+
# equivalent if all component values are within +TOLERANCE+ of each other.
|
33
68
|
def ==(other)
|
34
|
-
Color.
|
69
|
+
other.is_a?(Color) && to_internal.zip(coerce(other).to_internal).all? { near?(_1, _2) }
|
35
70
|
end
|
36
71
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
72
|
+
##
|
73
|
+
# Apply the provided block to each color component in turn, returning a new color
|
74
|
+
# instance.
|
75
|
+
def map(&block) = self.class.from_internal(*to_internal.map(&block))
|
41
76
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
end
|
47
|
-
def names=(n) # :nodoc:
|
48
|
-
@names = Array(n).flatten.compact.map(&:to_s).map(&:downcase).sort.uniq
|
49
|
-
end
|
50
|
-
alias_method :name=, :names=
|
51
|
-
end
|
77
|
+
##
|
78
|
+
# Apply the provided block to the color component pairs in turn, returning a new color
|
79
|
+
# instance.
|
80
|
+
def map_with(other, &block) = self.class.from_internal(*zip(other).map(&block))
|
52
81
|
|
53
|
-
|
54
|
-
#
|
55
|
-
def
|
56
|
-
(value.abs <= Color::COLOR_EPSILON)
|
57
|
-
end
|
82
|
+
##
|
83
|
+
# Zip the color component pairs together.
|
84
|
+
def zip(other) = to_internal.zip(coerce(other).to_internal)
|
58
85
|
|
59
|
-
|
60
|
-
#
|
61
|
-
|
62
|
-
|
86
|
+
##
|
87
|
+
# Multiplies each component value by the scaling factor or factors, returning a new
|
88
|
+
# color object with the scaled values.
|
89
|
+
#
|
90
|
+
# If a single scaling factor is provided, it is applied to all components:
|
91
|
+
#
|
92
|
+
# ```ruby
|
93
|
+
# rgb = Color::RGB::Wheat # => RGB [#f5deb3]
|
94
|
+
# rgb.scale(0.75) # => RGB [#b8a786]
|
95
|
+
# ```
|
96
|
+
#
|
97
|
+
# If more than one scaling factor is provided, there must be exactly one factor for each
|
98
|
+
# color component of the color object or an `ArgumentError` will be raised.
|
99
|
+
#
|
100
|
+
# ```ruby
|
101
|
+
# rgb = Color::RGB::Wheat # => RGB [#f5deb3]
|
102
|
+
# # 0xf5 * 0 == 0x00, 0xde * 0.5 == 0x6f, 0xb3 * 2 == 0x166 (clamped to 0xff)
|
103
|
+
# rgb.scale(0, 0.5, 2) # => RGB [#006fff]
|
104
|
+
#
|
105
|
+
# rgb.scale(1, 2) # => Invalid scaling factors [1, 2] for Color::RGB (ArgumentError)
|
106
|
+
# ```
|
107
|
+
def scale(*factors)
|
108
|
+
if factors.size == 1
|
109
|
+
factor = factors.first
|
110
|
+
map { _1 * factor }
|
111
|
+
elsif factors.size != components
|
112
|
+
raise ArgumentError, "Invalid scaling factors #{factors.inspect} for #{self.class}"
|
113
|
+
else
|
114
|
+
new_components = to_internal.zip(factors).map { _1 * _2 }
|
115
|
+
self.class.from_internal(*new_components)
|
116
|
+
end
|
63
117
|
end
|
64
118
|
|
65
|
-
|
66
|
-
def
|
67
|
-
|
119
|
+
##
|
120
|
+
def css_value(value, format = nil) # :nodoc:
|
121
|
+
if value.nil?
|
122
|
+
"none"
|
123
|
+
elsif near_zero?(value)
|
124
|
+
"0"
|
125
|
+
else
|
126
|
+
suffix =
|
127
|
+
case format
|
128
|
+
in :percent
|
129
|
+
"%"
|
130
|
+
in :degrees
|
131
|
+
"deg"
|
132
|
+
else
|
133
|
+
""
|
134
|
+
end
|
135
|
+
|
136
|
+
"%3.2f%s" % [value, suffix]
|
137
|
+
end
|
68
138
|
end
|
69
139
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
140
|
+
private
|
141
|
+
|
142
|
+
##
|
143
|
+
def from_internal(...) = self.class.from_internal(...)
|
144
|
+
|
145
|
+
##
|
146
|
+
# Returns `true` if the value is less than EPSILON.
|
147
|
+
def near_zero?(value) = (value.abs <= Color::EPSILON) # :nodoc:
|
148
|
+
|
149
|
+
##
|
150
|
+
# Returns `true` if the value is within EPSILON of zero or less than zero.
|
151
|
+
def near_zero_or_less?(value) = (value < 0.0 or near_zero?(value)) # :nodoc:
|
152
|
+
|
153
|
+
##
|
154
|
+
# Returns +true+ if the value is within EPSILON of one.
|
155
|
+
def near_one?(value) = near_zero?(value - 1.0) # :nodoc:
|
156
|
+
|
157
|
+
##
|
158
|
+
# Returns +true+ if the value is within EPSILON of one or more than one.
|
159
|
+
def near_one_or_more?(value) = (value > 1.0 or near_one?(value)) # :nodoc:
|
75
160
|
|
161
|
+
##
|
76
162
|
# Returns +true+ if the two values provided are near each other.
|
77
|
-
def near?(x, y)
|
78
|
-
(x - y).abs <= Color::COLOR_TOLERANCE
|
79
|
-
end
|
163
|
+
def near?(x, y) = (x - y).abs <= Color::TOLERANCE # :nodoc:
|
80
164
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
165
|
+
##
|
166
|
+
def to_degrees(radians) # :nodoc:
|
167
|
+
if radians < 0
|
168
|
+
(Math::PI + radians % -Math::PI) * (180 / Math::PI) + 180
|
169
|
+
else
|
170
|
+
(radians % Math::PI) * (180 / Math::PI)
|
171
|
+
end
|
87
172
|
end
|
88
173
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
174
|
+
##
|
175
|
+
def to_radians(degrees) # :nodoc:
|
176
|
+
degrees = ((degrees % 360) + 360) % 360
|
177
|
+
if degrees >= 180
|
178
|
+
Math::PI * (degrees - 360) / 180.0
|
179
|
+
else
|
180
|
+
Math::PI * degrees / 180.0
|
181
|
+
end
|
94
182
|
end
|
95
183
|
|
184
|
+
##
|
96
185
|
# Normalizes the value to the range (0.0) .. (1.0).
|
97
|
-
def normalize(value)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
186
|
+
module_function def normalize(value, range = 0.0..1.0) # :nodoc:
|
187
|
+
value = value.clamp(range)
|
188
|
+
if near?(value, range.begin)
|
189
|
+
range.begin
|
190
|
+
elsif near?(value, range.end)
|
191
|
+
range.end
|
102
192
|
else
|
103
193
|
value
|
104
194
|
end
|
105
195
|
end
|
106
|
-
alias normalize_fractional normalize
|
107
196
|
|
197
|
+
##
|
198
|
+
# Translates a value from range `from` to range `to`. Both ranges must be closed.
|
199
|
+
# As 0.0 .. 1.0 is a common internal range, it is the default for `from`.
|
200
|
+
#
|
201
|
+
# This is based on the formula:
|
202
|
+
#
|
203
|
+
# [a, b] ← from ← [from.begin, from.end]
|
204
|
+
# [c, d] ← to ← [to.begin, to.end]
|
205
|
+
#
|
206
|
+
# y = (((x - a) * (d - c)) / (b - a)) + c
|
207
|
+
#
|
208
|
+
# The value is clamped to the values of `to`.
|
209
|
+
module_function def translate_range(x, to:, from: 0.0..1.0) # :nodoc:
|
210
|
+
a, b = [from.begin, from.end]
|
211
|
+
c, d = [to.begin, to.end]
|
212
|
+
y = (((x - a) * (d - c)) / (b - a)) + c
|
213
|
+
y.clamp(to)
|
214
|
+
end
|
215
|
+
|
216
|
+
##
|
108
217
|
# Normalizes the value to the specified range.
|
109
|
-
def normalize_to_range(value, range)
|
110
|
-
range = (range.end..range.begin) if
|
218
|
+
def normalize_to_range(value, range) # :nodoc:
|
219
|
+
range = (range.end..range.begin) if range.end < range.begin
|
111
220
|
|
112
221
|
if value <= range.begin
|
113
222
|
range.begin
|
@@ -118,68 +227,21 @@ class << Color
|
|
118
227
|
end
|
119
228
|
end
|
120
229
|
|
230
|
+
##
|
121
231
|
# Normalize the value to the range (0) .. (255).
|
122
|
-
def normalize_byte(value)
|
123
|
-
normalize_to_range(value, 0..255).to_i
|
124
|
-
end
|
125
|
-
alias normalize_8bit normalize_byte
|
232
|
+
def normalize_byte(value) = normalize_to_range(value, 0..255).to_i # :nodoc:
|
126
233
|
|
234
|
+
##
|
127
235
|
# Normalize the value to the range (0) .. (65535).
|
128
|
-
def normalize_word(value)
|
129
|
-
normalize_to_range(value, 0..65535).to_i
|
130
|
-
end
|
131
|
-
alias normalize_16bit normalize_word
|
236
|
+
def normalize_word(value) = normalize_to_range(value, 0..65535).to_i # :nodoc:
|
132
237
|
end
|
133
238
|
|
134
|
-
require
|
135
|
-
require
|
136
|
-
require
|
137
|
-
require
|
138
|
-
require
|
139
|
-
require
|
140
|
-
|
141
|
-
class << Color
|
142
|
-
def const_missing(name) #:nodoc:
|
143
|
-
case name
|
144
|
-
when "VERSION", :VERSION, "COLOR_TOOLS_VERSION", :COLOR_TOOLS_VERSION
|
145
|
-
warn "Color::#{name} has been deprecated. Use Color::COLOR_VERSION instead."
|
146
|
-
Color::COLOR_VERSION
|
147
|
-
else
|
148
|
-
if Color::RGB.const_defined?(name)
|
149
|
-
warn "Color::#{name} has been deprecated. Use Color::RGB::#{name} instead."
|
150
|
-
Color::RGB.const_get(name)
|
151
|
-
else
|
152
|
-
super
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
239
|
+
require "color/cmyk"
|
240
|
+
require "color/grayscale"
|
241
|
+
require "color/hsl"
|
242
|
+
require "color/cielab"
|
243
|
+
require "color/rgb"
|
244
|
+
require "color/xyz"
|
245
|
+
require "color/yiq"
|
156
246
|
|
157
|
-
|
158
|
-
# is Color 0.1.0 (a class) and not Color 1.4 (a module). This
|
159
|
-
# "constructor" will be removed in the future.
|
160
|
-
#
|
161
|
-
# mode = :hsl:: +values+ must be an array of [ hue deg, sat %, lum % ].
|
162
|
-
# A Color::HSL object will be created.
|
163
|
-
# mode = :rgb:: +values+ will either be an HTML-style colour string or
|
164
|
-
# an array of [ red, green, blue ] (range 0 .. 255). A
|
165
|
-
# Color::RGB object will be created.
|
166
|
-
# mode = :cmyk:: +values+ must be an array of [ cyan %, magenta %, yellow
|
167
|
-
# %, black % ]. A Color::CMYK object will be created.
|
168
|
-
def new(values, mode = :rgb)
|
169
|
-
warn "Color.new has been deprecated. Use Color::#{mode.to_s.upcase}.new instead."
|
170
|
-
color = case mode
|
171
|
-
when :hsl
|
172
|
-
Color::HSL.new(*values)
|
173
|
-
when :rgb
|
174
|
-
values = [ values ].flatten
|
175
|
-
if values.size == 1
|
176
|
-
Color::RGB.from_html(*values)
|
177
|
-
else
|
178
|
-
Color::RGB.new(*values)
|
179
|
-
end
|
180
|
-
when :cmyk
|
181
|
-
Color::CMYK.new(*values)
|
182
|
-
end
|
183
|
-
color.to_hsl
|
184
|
-
end
|
185
|
-
end
|
247
|
+
require "color/version"
|
data/licences/dco.txt
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Developer Certificate of Origin
|
2
|
+
Version 1.1
|
3
|
+
|
4
|
+
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
5
|
+
|
6
|
+
Everyone is permitted to copy and distribute verbatim copies of this
|
7
|
+
license document, but changing it is not allowed.
|
8
|
+
|
9
|
+
|
10
|
+
Developer's Certificate of Origin 1.1
|
11
|
+
|
12
|
+
By making a contribution to this project, I certify that:
|
13
|
+
|
14
|
+
(a) The contribution was created in whole or in part by me and I
|
15
|
+
have the right to submit it under the open source license
|
16
|
+
indicated in the file; or
|
17
|
+
|
18
|
+
(b) The contribution is based upon previous work that, to the best
|
19
|
+
of my knowledge, is covered under an appropriate open source
|
20
|
+
license and I have the right under that license to submit that
|
21
|
+
work with modifications, whether created in whole or in part
|
22
|
+
by me, under the same open source license (unless I am
|
23
|
+
permitted to submit under a different license), as indicated
|
24
|
+
in the file; or
|
25
|
+
|
26
|
+
(c) The contribution was provided directly to me by some other
|
27
|
+
person who certified (a), (b) or (c) and I have not modified
|
28
|
+
it.
|
29
|
+
|
30
|
+
(d) I understand and agree that this project and the contribution
|
31
|
+
are public and that a record of the contribution (including all
|
32
|
+
personal information I submit with it, including my sign-off) is
|
33
|
+
maintained indefinitely and may be redistributed consistent with
|
34
|
+
this project or the open source license(s) involved.
|