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.
- checksums.yaml +5 -13
- 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 -21
- data/README.md +54 -0
- data/Rakefile +74 -53
- 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 +534 -537
- 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 -141
- data/test/fixtures/cielab.json +444 -0
- data/test/minitest_helper.rb +20 -4
- data/test/test_cmyk.rb +49 -71
- data/test/test_color.rb +58 -106
- data/test/test_grayscale.rb +35 -56
- data/test/test_hsl.rb +72 -76
- data/test/test_rgb.rb +195 -267
- data/test/test_yiq.rb +12 -30
- metadata +165 -150
- checksums.yaml.gz.sig +0 -0
- data/.autotest +0 -5
- data/.gemtest +0 -0
- data/.hoerc +0 -2
- data/.minitest.rb +0 -2
- data/.travis.yml +0 -35
- data/Contributing.rdoc +0 -60
- data/Gemfile +0 -9
- data/History.rdoc +0 -172
- data/Licence.rdoc +0 -27
- data/README.rdoc +0 -50
- 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.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
data/lib/color/hsl.rb
CHANGED
@@ -1,237 +1,269 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
#
|
5
|
-
# the
|
3
|
+
##
|
4
|
+
# The \HSL color model is a cylindrical-coordinate representation of the sRGB color model,
|
5
|
+
# standing for hue (measured in degrees on the cylinder), saturation (measured in
|
6
|
+
# percentage), and lightness (measured in percentage).
|
7
|
+
#
|
8
|
+
# \HSL colors are immutable Data class instances. Array deconstruction is `[hue,
|
9
|
+
# saturation, luminosity]` and hash deconstruction is `{h:, hue:, s:, saturation:, l:,
|
10
|
+
# luminosity:}`. See #h, #hue, #s, #saturation, #l, #luminosity.
|
6
11
|
class Color::HSL
|
7
12
|
include Color
|
8
13
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
new(h, s, l, 1.0, 1.0, &block)
|
13
|
-
end
|
14
|
-
end
|
14
|
+
##
|
15
|
+
# :attr_reader: h
|
16
|
+
# Returns the hue of the color in the range 0.0..1.0.
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
18
|
+
##
|
19
|
+
# :attr_reader: hue
|
20
|
+
# Returns the hue of the color in degrees (0.0..360.0).
|
20
21
|
|
21
|
-
|
22
|
-
#
|
23
|
-
|
24
|
-
@h = Color.normalize(h / radix1)
|
25
|
-
@s = Color.normalize(s / radix2)
|
26
|
-
@l = Color.normalize(l / radix2)
|
27
|
-
block.call if block
|
28
|
-
end
|
22
|
+
##
|
23
|
+
# :attr_reader: s
|
24
|
+
# Returns the saturation of the color in the range 0.0..1.0.
|
29
25
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
26
|
+
##
|
27
|
+
# :attr_reader: saturation
|
28
|
+
# Returns the percentage of saturation of the color (0.0..100.0).
|
34
29
|
|
35
|
-
|
36
|
-
#
|
37
|
-
#
|
38
|
-
def css_rgb
|
39
|
-
to_rgb.css_rgb
|
40
|
-
end
|
30
|
+
##
|
31
|
+
# :attr_reader: brightness
|
32
|
+
# Returns the luminosity (#l) of the color.
|
41
33
|
|
42
|
-
|
43
|
-
#
|
44
|
-
#
|
45
|
-
|
46
|
-
|
34
|
+
##
|
35
|
+
# :attr_reader: l
|
36
|
+
# Returns the luminosity of the color in the range 0.0..1.0.
|
37
|
+
|
38
|
+
##
|
39
|
+
# :attr_reader: luminosity
|
40
|
+
# Returns the percentage of luminosity of the color.
|
41
|
+
|
42
|
+
##
|
43
|
+
# Creates a \HSL color object from degrees (0.0 .. 360.0) and percentage values
|
44
|
+
# (0.0 .. 100.0).
|
45
|
+
#
|
46
|
+
# ```ruby
|
47
|
+
# Color::HSL.from_values(145, 30, 50) # => HSL [145deg 30% 50%]
|
48
|
+
# Color::HSL.from_values(h: 145, s: 30, l: 50) # => HSL [145deg 30% 50%]
|
49
|
+
# ```
|
50
|
+
#
|
51
|
+
# :call-seq:
|
52
|
+
# from_values(h, s, l)
|
53
|
+
# from_values(h:, s:, l:)
|
54
|
+
def self.from_values(*args, **kwargs)
|
55
|
+
h, s, l =
|
56
|
+
case [args, kwargs]
|
57
|
+
in [[_, _, _], {}]
|
58
|
+
args
|
59
|
+
in [[], {h:, s:, l:}]
|
60
|
+
[h, s, l]
|
61
|
+
else
|
62
|
+
new(*args, **kwargs)
|
63
|
+
end
|
64
|
+
|
65
|
+
new(h: h / 360.0, s: s / 100.0, l: l / 100.0)
|
47
66
|
end
|
48
67
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
"hsl(%3.2f, %3.2f%%, %3.2f%%)" % [ hue, saturation, luminosity ]
|
68
|
+
class << self
|
69
|
+
alias_method :from_fraction, :new
|
70
|
+
alias_method :from_internal, :new # :nodoc:
|
53
71
|
end
|
54
72
|
|
55
|
-
|
56
|
-
#
|
57
|
-
|
58
|
-
|
73
|
+
##
|
74
|
+
# Creates a \HSL color object from fractional values (0.0 .. 1.0).
|
75
|
+
#
|
76
|
+
# ```ruby
|
77
|
+
# Color::HSL.from_fraction(0.3, 0.3, 0.5) # => HSL [108deg 30% 50%]
|
78
|
+
# Color::HSL.new(h: 0.3, s: 0.3, l: 0.5) # => HSL [108deg 30% 50%]
|
79
|
+
# Color::HSL[0.3, 0.3, 0.5] # => HSL [108deg 30% 50%]
|
80
|
+
# ```
|
81
|
+
def initialize(h:, s:, l:)
|
82
|
+
super(h: normalize(h), s: normalize(s), l: normalize(l))
|
59
83
|
end
|
60
84
|
|
61
|
-
|
62
|
-
#
|
63
|
-
|
85
|
+
##
|
86
|
+
# Coerces the other Color object into \HSL.
|
87
|
+
def coerce(other) = other.to_hsl
|
88
|
+
|
89
|
+
##
|
90
|
+
# Converts from \HSL to Color::RGB.
|
91
|
+
#
|
92
|
+
# As with all color conversions, this is an approximation. The code here is adapted from
|
93
|
+
# fvd and van Dam, originally found at [1] (implemented similarly at [2]). This
|
94
|
+
# simplifies the calculations with the following assumptions:
|
64
95
|
#
|
65
|
-
# This simplifies the calculations with the following assumptions:
|
66
96
|
# - Luminance values <= 0 always translate to Color::RGB::Black.
|
67
97
|
# - Luminance values >= 1 always translate to Color::RGB::White.
|
68
|
-
# - Saturation values <= 0 always translate to a shade of gray using
|
69
|
-
#
|
98
|
+
# - Saturation values <= 0 always translate to a shade of gray using luminance as
|
99
|
+
# a percentage of gray.
|
70
100
|
#
|
71
101
|
# [1] http://bobpowell.net/RGBHSB.aspx
|
72
102
|
# [2] http://support.microsoft.com/kb/29240
|
73
|
-
def to_rgb(
|
74
|
-
if
|
103
|
+
def to_rgb(...)
|
104
|
+
if near_zero_or_less?(l)
|
75
105
|
Color::RGB::Black
|
76
|
-
elsif
|
106
|
+
elsif near_one_or_more?(l)
|
77
107
|
Color::RGB::White
|
78
|
-
elsif
|
79
|
-
Color::RGB.
|
108
|
+
elsif near_zero?(s)
|
109
|
+
Color::RGB.from_fraction(l, l, l)
|
80
110
|
else
|
81
|
-
|
82
|
-
# Color::RGB.new(*compute_fvd_rgb, 1.0)
|
83
|
-
Color::RGB.new(*(compute_fvd_rgb + [ 1.0 ]))
|
111
|
+
Color::RGB.from_fraction(*compute_fvd_rgb)
|
84
112
|
end
|
85
113
|
end
|
86
114
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
end
|
115
|
+
##
|
116
|
+
# Converts from \HSL to Color::YIQ via Color::RGB.
|
117
|
+
def to_yiq(...) = to_rgb(...).to_yiq(...)
|
91
118
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
119
|
+
##
|
120
|
+
# Converts from \HSL to Color::CMYK via Color::RGB.
|
121
|
+
def to_cmyk(...) = to_rgb(...).to_cmyk(...)
|
96
122
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
def
|
102
|
-
Color::GrayScale.from_fraction(@l)
|
103
|
-
end
|
104
|
-
alias to_grayscale to_greyscale
|
123
|
+
##
|
124
|
+
# Converts from \HSL to Color::Grayscale.
|
125
|
+
#
|
126
|
+
# Luminance is treated as the Grayscale ratio.
|
127
|
+
def to_grayscale(...) = Color::Grayscale.from_fraction(l)
|
105
128
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
110
|
-
# Returns the hue of the colour in the range 0.0 .. 1.0.
|
111
|
-
def h
|
112
|
-
@h
|
113
|
-
end
|
114
|
-
# Sets the hue of the colour in degrees. Colour is perceived as a wheel,
|
115
|
-
# so values should be set properly even with negative degree values.
|
116
|
-
def hue=(hh)
|
117
|
-
hh = hh / 360.0
|
129
|
+
##
|
130
|
+
# Converts from \HSL to Color::CIELAB via Color::RGB.
|
131
|
+
def to_lab(...) = to_rgb(...).to_lab(...)
|
118
132
|
|
119
|
-
|
120
|
-
|
133
|
+
##
|
134
|
+
# Converts from \HSL to Color::XYZ via Color::RGB.
|
135
|
+
def to_xyz(...) = to_rgb(...).to_xyz(...)
|
121
136
|
|
122
|
-
|
123
|
-
|
124
|
-
# Sets the hue of the colour in the range 0.0 .. 1.0.
|
125
|
-
def h=(hh)
|
126
|
-
@h = Color.normalize(hh)
|
127
|
-
end
|
128
|
-
# Returns the percentage of saturation of the colour.
|
129
|
-
def saturation
|
130
|
-
@s * 100.0
|
131
|
-
end
|
132
|
-
# Returns the saturation of the colour in the range 0.0 .. 1.0.
|
133
|
-
def s
|
134
|
-
@s
|
135
|
-
end
|
136
|
-
# Sets the percentage of saturation of the colour.
|
137
|
-
def saturation=(ss)
|
138
|
-
@s = Color.normalize(ss / 100.0)
|
139
|
-
end
|
140
|
-
# Sets the saturation of the colour in the ragne 0.0 .. 1.0.
|
141
|
-
def s=(ss)
|
142
|
-
@s = Color.normalize(ss)
|
143
|
-
end
|
137
|
+
##
|
138
|
+
def to_hsl(...) = self
|
144
139
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
#
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
def l=(ll)
|
161
|
-
@l = Color.normalize(ll)
|
162
|
-
end
|
140
|
+
##
|
141
|
+
# Present the color as a CSS `hsl` function with optional `alpha`.
|
142
|
+
#
|
143
|
+
# ```ruby
|
144
|
+
# hsl = Color::HSL.from_values(145, 30, 50)
|
145
|
+
# hsl.css # => hsl(145deg 30% 50%)
|
146
|
+
# hsl.css(alpha: 0.5) # => hsl(145deg 30% 50% / 0.50)
|
147
|
+
# ```
|
148
|
+
def css(alpha: nil)
|
149
|
+
params = [
|
150
|
+
css_value(hue, :degrees),
|
151
|
+
css_value(saturation, :percent),
|
152
|
+
css_value(luminosity, :percent)
|
153
|
+
].join(" ")
|
154
|
+
params = "#{params} / #{css_value(alpha)}" if alpha
|
163
155
|
|
164
|
-
|
165
|
-
self
|
156
|
+
"hsl(#{params})"
|
166
157
|
end
|
167
158
|
|
168
|
-
|
169
|
-
|
159
|
+
##
|
160
|
+
def brightness = l # :nodoc:
|
161
|
+
|
162
|
+
##
|
163
|
+
def hue = h * 360.0 # :nodoc:
|
164
|
+
|
165
|
+
##
|
166
|
+
def saturation = s * 100.0 # :nodoc:
|
167
|
+
|
168
|
+
##
|
169
|
+
def luminosity = l * 100.0 # :nodoc:
|
170
|
+
|
171
|
+
##
|
172
|
+
alias_method :lightness, :luminosity
|
173
|
+
|
174
|
+
##
|
175
|
+
def inspect = "HSL [%.2fdeg %.2f%% %.2f%%]" % [hue, saturation, luminosity] # :nodoc:
|
176
|
+
|
177
|
+
##
|
178
|
+
def pretty_print(q) # :nodoc:
|
179
|
+
q.text "HSL"
|
180
|
+
q.breakable
|
181
|
+
q.group 2, "[", "]" do
|
182
|
+
q.text "%.2fdeg" % hue
|
183
|
+
q.fill_breakable
|
184
|
+
q.text "%.2f%%" % saturation
|
185
|
+
q.fill_breakable
|
186
|
+
q.text "%.2f%%" % luminosity
|
187
|
+
end
|
170
188
|
end
|
171
189
|
|
172
|
-
|
173
|
-
#
|
190
|
+
##
|
191
|
+
# Mix the mask color (which will be converted to a \HSL color) with the current color
|
192
|
+
# at the stated fractional mix ratio (0.0..1.0).
|
174
193
|
#
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
end
|
194
|
+
# This implementation differs from Color::RGB#mix_with.
|
195
|
+
#
|
196
|
+
# :call-seq:
|
197
|
+
# mix_with(mask, mix_ratio: 0.5)
|
198
|
+
def mix_with(mask, *args, **kwargs)
|
199
|
+
mix_ratio = normalize(kwargs[:mix_ratio] || args.first || 0.5)
|
182
200
|
|
183
|
-
|
184
|
-
[ h, s, l ]
|
201
|
+
map_with(mask) { ((_2 - _1) * mix_ratio) + _1 }
|
185
202
|
end
|
186
203
|
|
204
|
+
##
|
205
|
+
def to_a = [hue, saturation, luminosity] # :nodoc:
|
206
|
+
|
207
|
+
##
|
208
|
+
alias_method :deconstruct, :to_a
|
209
|
+
|
210
|
+
##
|
211
|
+
def deconstruct_keys(_keys) = {h:, s:, l:, hue:, saturation:, luminance:} # :nodoc:
|
212
|
+
|
213
|
+
##
|
214
|
+
def to_internal = [h, s, l] # :nodoc:
|
215
|
+
|
187
216
|
private
|
188
217
|
|
189
|
-
|
190
|
-
#
|
191
|
-
# hue - 1/3 positions in a circular
|
192
|
-
# four parts (confusing, I know, but it's the way
|
193
|
-
# #hue_to_rgb for more information.
|
194
|
-
def compute_fvd_rgb
|
218
|
+
##
|
219
|
+
# This algorithm calculates based on a mixture of the saturation and luminance, and then
|
220
|
+
# takes the RGB values from the hue + 1/3, hue, and hue - 1/3 positions in a circular
|
221
|
+
# representation of color divided into four parts (confusing, I know, but it's the way
|
222
|
+
# that it works). See #hue_to_rgb for more information.
|
223
|
+
def compute_fvd_rgb # :nodoc:
|
195
224
|
t1, t2 = fvd_mix_sat_lum
|
196
|
-
[
|
225
|
+
[h + (1 / 3.0), h, h - (1 / 3.0)].map { |v|
|
197
226
|
hue_to_rgb(rotate_hue(v), t1, t2)
|
198
227
|
}
|
199
228
|
end
|
200
229
|
|
201
|
-
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
230
|
+
##
|
231
|
+
# Mix saturation and luminance for use in hue_to_rgb. The base value is different
|
232
|
+
# depending on whether luminance is <= 50% or > 50%.
|
233
|
+
def fvd_mix_sat_lum # :nodoc:
|
234
|
+
t = if near_zero_or_less?(l - 0.5)
|
235
|
+
l * (1.0 + s.to_f)
|
236
|
+
else
|
237
|
+
l + s - (l * s.to_f)
|
238
|
+
end
|
239
|
+
[2.0 * l - t, t]
|
210
240
|
end
|
211
241
|
|
212
|
-
|
213
|
-
#
|
214
|
-
#
|
215
|
-
|
216
|
-
|
217
|
-
h
|
242
|
+
##
|
243
|
+
# In \HSL, hues are referenced as degrees in a color circle. The flow itself is endless;
|
244
|
+
# therefore, we can rotate around. The only thing our implementation restricts is that
|
245
|
+
# you should not be > 1.0.
|
246
|
+
def rotate_hue(h) # :nodoc:
|
247
|
+
h += 1.0 if near_zero_or_less?(h)
|
248
|
+
h -= 1.0 if near_one_or_more?(h)
|
218
249
|
h
|
219
250
|
end
|
220
251
|
|
221
|
-
|
222
|
-
#
|
223
|
-
#
|
252
|
+
##
|
253
|
+
# We calculate the interaction of the saturation/luminance mix (calculated earlier)
|
254
|
+
# based on the position of the hue in the circular color space divided into quadrants.
|
255
|
+
# Our hue range is [0, 1), not [0, 360º).
|
224
256
|
#
|
225
257
|
# - The first quadrant covers the first 60º [0, 60º].
|
226
258
|
# - The second quadrant covers the next 120º (60º, 180º].
|
227
259
|
# - The third quadrant covers the next 60º (180º, 240º].
|
228
260
|
# - The fourth quadrant covers the final 120º (240º, 360º).
|
229
|
-
def hue_to_rgb(h, t1, t2)
|
230
|
-
if
|
261
|
+
def hue_to_rgb(h, t1, t2) # :nodoc:
|
262
|
+
if near_zero_or_less?((6.0 * h) - 1.0)
|
231
263
|
t1 + ((t2 - t1) * h * 6.0)
|
232
|
-
elsif
|
264
|
+
elsif near_zero_or_less?((2.0 * h) - 1.0)
|
233
265
|
t2
|
234
|
-
elsif
|
266
|
+
elsif near_zero_or_less?((3.0 * h) - 2.0)
|
235
267
|
t1 + (t2 - t1) * ((2 / 3.0) - h) * 6.0
|
236
268
|
else
|
237
269
|
t1
|