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/rgb.rb
CHANGED
@@ -1,660 +1,652 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The \RGB color model is an additive color model where the primary colors (red, green,
|
4
|
+
# and blue) of light are added to produce millions of colors. \RGB rendering is
|
5
|
+
# device-dependent and without color management, the same "red" color will render
|
6
|
+
# differently.
|
7
|
+
#
|
8
|
+
# This class does not implement color management and is not \RGB colorspace aware; that is,
|
9
|
+
# unless otherwise noted, it does not assume that the \RGB represented is sRGB or Adobe
|
10
|
+
# \RGB (opRGB).
|
11
|
+
#
|
12
|
+
# \RGB colors are immutable Data class instances. Array deconstruction is `[red, green,
|
13
|
+
# blue]` and hash deconstruction is `{r:, red:, g:, green:, b:, blue}`. See #r, #red, #g,
|
14
|
+
# #green, #b, #blue.
|
2
15
|
class Color::RGB
|
3
16
|
include Color
|
4
17
|
|
5
|
-
|
6
|
-
#
|
7
|
-
#
|
8
|
-
PDF_FORMAT_STR = "%.3f %.3f %.3f %s"
|
18
|
+
##
|
19
|
+
# :attr_reader: r
|
20
|
+
# Returns the red component of the color in the range 0.0..1.0.
|
9
21
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
22
|
+
##
|
23
|
+
# :attr_reader: red
|
24
|
+
# Returns the red component of the color in the normal 0..255 range.
|
14
25
|
|
15
|
-
|
26
|
+
##
|
27
|
+
# :attr_reader: red_p
|
28
|
+
# Returns the red component of the color as a percentage (0.0 .. 100.0).
|
16
29
|
#
|
17
|
-
|
18
|
-
#
|
19
|
-
|
20
|
-
@r, @g, @b = [ r, g, b ].map { |v| Color.normalize(v / radix) }
|
21
|
-
block.call(self) if block
|
22
|
-
end
|
30
|
+
##
|
31
|
+
# :attr_reader: g
|
32
|
+
# Returns the green component of the color in the range 0.0..1.0.
|
23
33
|
|
24
|
-
|
25
|
-
#
|
26
|
-
|
27
|
-
PDF_FORMAT_STR % [ @r, @g, @b, "rg" ]
|
28
|
-
end
|
34
|
+
##
|
35
|
+
# :attr_reader: green
|
36
|
+
# Returns the green component of the color in the normal 0 .. 255 range.
|
29
37
|
|
30
|
-
|
31
|
-
#
|
32
|
-
|
33
|
-
PDF_FORMAT_STR % [ @r, @g, @b, "RG" ]
|
34
|
-
end
|
38
|
+
##
|
39
|
+
# :attr_reader: green_p
|
40
|
+
# Returns the green component of the color as a percentage (0.0 .. 100.0).
|
35
41
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
r = 255 if r > 255
|
42
|
+
##
|
43
|
+
# :attr_reader: b
|
44
|
+
# Returns the blue component of the color in the range 0.0..1.0.
|
40
45
|
|
41
|
-
|
42
|
-
|
46
|
+
##
|
47
|
+
# :attr_reader: blue
|
48
|
+
# Returns the blue component of the color in the normal 0 .. 255 range.
|
43
49
|
|
44
|
-
|
45
|
-
|
50
|
+
##
|
51
|
+
# :attr_reader: blue_p
|
52
|
+
# Returns the blue component of the color as a percentage (0.0 .. 100.0).
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
#
|
51
|
-
|
52
|
-
|
54
|
+
##
|
55
|
+
# Creates a \RGB color object from fractional values (0.0 .. 1.0).
|
56
|
+
#
|
57
|
+
# ```ruby
|
58
|
+
# Color::RGB.from_fraction(0.3, 0.2, 0.1) # => RGB [#4d331a]
|
59
|
+
# Color::RGB.new(0.3, 0.2, 0.1) # => RGB [#4d331a]
|
60
|
+
# Color::RGB[r: 0.3, g: 0.2, b: 0.1] # => RGB [#4d331a]
|
61
|
+
# ```
|
62
|
+
def initialize(r:, g:, b:, names: nil)
|
63
|
+
super(r: normalize(r), g: normalize(g), b: normalize(b), names: names)
|
53
64
|
end
|
54
65
|
|
55
|
-
|
56
|
-
#
|
57
|
-
#
|
58
|
-
def css_rgb
|
59
|
-
"rgb(%3.2f%%, %3.2f%%, %3.2f%%)" % [ red_p, green_p, blue_p ]
|
60
|
-
end
|
66
|
+
##
|
67
|
+
# :attr_reader: name
|
68
|
+
# The primary name for this \RGB color.
|
61
69
|
|
62
|
-
|
63
|
-
#
|
64
|
-
#
|
65
|
-
def css_rgba
|
66
|
-
"rgba(%3.2f%%, %3.2f%%, %3.2f%%, %3.2f)" % [ red_p, green_p, blue_p, 1 ]
|
67
|
-
end
|
70
|
+
##
|
71
|
+
# :attr_reader: names
|
72
|
+
# The names for this \RGB color.
|
68
73
|
|
69
|
-
|
70
|
-
|
71
|
-
# default conversion formula.
|
72
|
-
def css_hsl
|
73
|
-
to_hsl.css_hsl
|
74
|
-
end
|
74
|
+
##
|
75
|
+
def name = names&.first # :nodoc:
|
75
76
|
|
76
|
-
|
77
|
-
#
|
78
|
-
|
79
|
-
def css_hsla
|
80
|
-
to_hsl.css_hsla
|
81
|
-
end
|
77
|
+
##
|
78
|
+
# Coerces the other Color object into \RGB.
|
79
|
+
def coerce(other) = other.to_rgb
|
82
80
|
|
83
|
-
|
84
|
-
#
|
85
|
-
# idea). CMYK represents additive percentages of inks on white paper,
|
86
|
-
# whereas RGB represents mixed colour intensities on a black screen.
|
81
|
+
##
|
82
|
+
# Converts the \RGB color to Color::CMYK.
|
87
83
|
#
|
88
|
-
#
|
89
|
-
#
|
84
|
+
# Most color experts strongly suggest that this is not a good idea (some suggesting that
|
85
|
+
# it's a very bad idea). CMYK represents additive percentages of inks on white paper,
|
86
|
+
# whereas \RGB represents mixed color intensities on an unlit (black) screen.
|
90
87
|
#
|
91
88
|
# 1. Convert the R, G, and B components to C, M, and Y components.
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
89
|
+
#
|
90
|
+
# c = 1.0 - r
|
91
|
+
# m = 1.0 - g
|
92
|
+
# y = 1.0 - b
|
93
|
+
#
|
94
|
+
# 2. Compute the minimum amount of black (K) required to smooth the color in inks.
|
95
|
+
#
|
96
|
+
# k = min(c, m, y)
|
97
|
+
#
|
98
|
+
# 3. Perform undercolor removal on the C, M, and Y components of the colors because less
|
99
|
+
# of each color is needed for each bit of black. Also, regenerate the black (K) based
|
100
|
+
# on the undercolor removal so that the color is more accurately represented in ink.
|
101
|
+
#
|
102
|
+
# c = min(1.0, max(0.0, c - UCR(k)))
|
103
|
+
# m = min(1.0, max(0.0, m - UCR(k)))
|
104
|
+
# y = min(1.0, max(0.0, y - UCR(k)))
|
105
|
+
# k = min(1.0, max(0.0, BG(k)))
|
106
|
+
#
|
107
|
+
# The undercolor removal function and the black generation functions return a value
|
108
|
+
# based on the brightness of the \RGB color.
|
109
|
+
def to_cmyk(...)
|
110
|
+
c = 1.0 - r.to_f
|
111
|
+
m = 1.0 - g.to_f
|
112
|
+
y = 1.0 - b.to_f
|
113
113
|
|
114
114
|
k = [c, m, y].min
|
115
|
-
k
|
115
|
+
k -= (k * brightness)
|
116
116
|
|
117
|
-
c =
|
118
|
-
m =
|
119
|
-
y =
|
120
|
-
k =
|
117
|
+
c = normalize(c - k)
|
118
|
+
m = normalize(m - k)
|
119
|
+
y = normalize(y - k)
|
120
|
+
k = normalize(k)
|
121
121
|
|
122
122
|
Color::CMYK.from_fraction(c, m, y, k)
|
123
123
|
end
|
124
124
|
|
125
|
-
|
126
|
-
|
127
|
-
|
125
|
+
##
|
126
|
+
def to_rgb(...) = self
|
127
|
+
|
128
|
+
##
|
129
|
+
# Convert \RGB to Color::Grayscale via Color::HSL (for the luminance value).
|
130
|
+
def to_grayscale(...) = Color::Grayscale.from_fraction(to_hsl.l)
|
128
131
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
132
|
+
##
|
133
|
+
# Converts \RGB to Color::YIQ.
|
134
|
+
def to_yiq(...)
|
135
|
+
y = (r * 0.299) + (g * 0.587) + (b * 0.114)
|
136
|
+
i = (r * 0.596) + (g * -0.275) + (b * -0.321)
|
137
|
+
q = (r * 0.212) + (g * -0.523) + (b * 0.311)
|
134
138
|
Color::YIQ.from_fraction(y, i, q)
|
135
139
|
end
|
136
140
|
|
137
|
-
|
138
|
-
#
|
141
|
+
##
|
142
|
+
# Converts \RGB to Color::HSL.
|
143
|
+
#
|
144
|
+
# The conversion here is based on formulas from http://www.easyrgb.com/math.php and
|
139
145
|
# elsewhere.
|
140
|
-
def to_hsl
|
141
|
-
min
|
142
|
-
max = [ @r, @g, @b ].max
|
146
|
+
def to_hsl(...)
|
147
|
+
min, max = [r, g, b].minmax
|
143
148
|
delta = (max - min).to_f
|
144
149
|
|
145
|
-
|
150
|
+
l = (max + min) / 2.0
|
146
151
|
|
147
|
-
if
|
148
|
-
|
149
|
-
|
152
|
+
if near_zero?(delta) # close to 0.0, so it's a gray
|
153
|
+
h = 0
|
154
|
+
s = 0
|
150
155
|
else
|
151
|
-
if
|
152
|
-
|
156
|
+
s = if near_zero_or_less?(l - 0.5)
|
157
|
+
delta / (max + min).to_f
|
153
158
|
else
|
154
|
-
|
159
|
+
delta / (2 - max - min).to_f
|
155
160
|
end
|
156
161
|
|
157
162
|
# This is based on the conversion algorithm from
|
158
163
|
# http://en.wikipedia.org/wiki/HSV_color_space#Conversion_from_RGB_to_HSL_or_HSV
|
159
164
|
# Contributed by Adam Johnson
|
160
165
|
sixth = 1 / 6.0
|
161
|
-
if
|
162
|
-
|
163
|
-
|
164
|
-
elsif
|
165
|
-
|
166
|
-
elsif
|
167
|
-
|
166
|
+
if r == max # near_zero_or_less?(r - max)
|
167
|
+
h = (sixth * ((g - b) / delta))
|
168
|
+
h += 1.0 if g < b
|
169
|
+
elsif g == max # near_zero_or_less(g - max)
|
170
|
+
h = (sixth * ((b - r) / delta)) + (1.0 / 3.0)
|
171
|
+
elsif b == max # near_zero_or_less?(b - max)
|
172
|
+
h = (sixth * ((r - g) / delta)) + (2.0 / 3.0)
|
168
173
|
end
|
169
174
|
|
170
|
-
|
171
|
-
|
175
|
+
h += 1 if h < 0
|
176
|
+
h -= 1 if h > 1
|
172
177
|
end
|
173
|
-
|
178
|
+
|
179
|
+
Color::HSL.from_fraction(h, s, l)
|
174
180
|
end
|
175
181
|
|
176
|
-
|
177
|
-
#
|
178
|
-
#
|
182
|
+
##
|
183
|
+
# Converts \RGB to Color::XYZ using the D65 reference white. This is based on conversion
|
184
|
+
# formulas presented by Bruce Lindbloom, in particular [RGB to XYZ][rgbxyz].
|
185
|
+
#
|
186
|
+
# [rgbxyz]: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
|
187
|
+
#
|
188
|
+
# The conversion is performed assuming the \RGB value is in the sRGB color space. No
|
189
|
+
# other \RGB color spaces are currently supported.
|
179
190
|
#
|
180
|
-
#
|
181
|
-
|
182
|
-
|
183
|
-
|
191
|
+
# :call-seq:
|
192
|
+
# to_xyz(color_space: :srgb)
|
193
|
+
def to_xyz(*args, **kwargs)
|
194
|
+
color_space = kwargs[:color_space] || args.first || :sRGB
|
195
|
+
|
196
|
+
case color_space.to_s.downcase
|
197
|
+
when "srgb"
|
198
|
+
# Inverse sRGB companding. Linearizes RGB channels with respect to energy.
|
199
|
+
rr, gg, bb = [r, g, b].map {
|
200
|
+
if _1 > 0.04045
|
201
|
+
(((_1 + 0.055) / 1.055)**2.4)
|
202
|
+
else
|
203
|
+
(_1 / 12.92)
|
204
|
+
end * 100.0
|
205
|
+
}
|
206
|
+
|
207
|
+
# Convert using the RGB/XYZ matrix at:
|
208
|
+
# http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html#WSMatrices
|
209
|
+
Color::XYZ.from_values(
|
210
|
+
rr * 0.4124564 + gg * 0.3575761 + bb * 0.1804375,
|
211
|
+
rr * 0.2126729 + gg * 0.7151522 + bb * 0.0721750,
|
212
|
+
rr * 0.0193339 + gg * 0.1191920 + bb * 0.9503041
|
213
|
+
)
|
214
|
+
else
|
215
|
+
raise ArgumentError, "Unsupported color space #{color_space}."
|
184
216
|
end
|
217
|
+
end
|
185
218
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
219
|
+
##
|
220
|
+
# Converts \RGB to Color::CIELAB via Color::XYZ.
|
221
|
+
#
|
222
|
+
# Based on the [XYZ to CIELAB][xyztolab] formula presented by Bruce Lindbloom.
|
223
|
+
#
|
224
|
+
# [xyztolab]: http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_Lab.html
|
225
|
+
#
|
226
|
+
# The conversion is performed assuming the \RGB value is in the sRGB color space. No
|
227
|
+
# other \RGB color spaces are currently supported. By default, uses the D65 reference
|
228
|
+
# white for the conversion.
|
229
|
+
#
|
230
|
+
# :call-seq:
|
231
|
+
# to_lab(color_space: :sRGB, white: Color::XYZ::D65)
|
232
|
+
def to_lab(...) = to_xyz(...).to_lab(...)
|
195
233
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
:y => (r * 0.2126729 + g * 0.7151522 + b * 0.0721750),
|
201
|
-
:z => (r * 0.0193339 + g * 0.1191920 + b * 0.9503041)
|
202
|
-
}
|
234
|
+
##
|
235
|
+
# Present the color as an HTML/CSS \RGB hex triplet (+ccddee+).
|
236
|
+
def hex
|
237
|
+
"%02x%02x%02x" % [red, green, blue].map(&:round)
|
203
238
|
end
|
204
239
|
|
205
|
-
|
206
|
-
#
|
207
|
-
|
208
|
-
|
209
|
-
#
|
210
|
-
# Currently only the sRGB colour space is supported and defaults to using
|
211
|
-
# a D65 reference white.
|
212
|
-
def to_lab(color_space = :sRGB, reference_white = [ 95.047, 100.00, 108.883 ])
|
213
|
-
xyz = to_xyz
|
214
|
-
|
215
|
-
# Calculate the ratio of the XYZ values to the reference white.
|
216
|
-
# http://www.brucelindbloom.com/index.html?Equations.html
|
217
|
-
xr = xyz[:x] / reference_white[0]
|
218
|
-
yr = xyz[:y] / reference_white[1]
|
219
|
-
zr = xyz[:z] / reference_white[2]
|
220
|
-
|
221
|
-
# NOTE: This should be using Rational instead of floating point values,
|
222
|
-
# otherwise there will be discontinuities.
|
223
|
-
# http://www.brucelindbloom.com/LContinuity.html
|
224
|
-
epsilon = (216 / 24389.0)
|
225
|
-
kappa = (24389 / 27.0)
|
226
|
-
|
227
|
-
# And now transform
|
228
|
-
# http://en.wikipedia.org/wiki/Lab_color_space#Forward_transformation
|
229
|
-
# There is a brief explanation there as far as the nature of the calculations,
|
230
|
-
# as well as a much nicer looking modeling of the algebra.
|
231
|
-
fx, fy, fz = [ xr, yr, zr ].map { |t|
|
232
|
-
if (t > (epsilon))
|
233
|
-
t ** (1.0 / 3)
|
234
|
-
else # t <= epsilon
|
235
|
-
((kappa * t) + 16) / 116.0
|
236
|
-
# The 4/29 here is for when t = 0 (black). 4/29 * 116 = 16, and 16 -
|
237
|
-
# 16 = 0, which is the correct value for L* with black.
|
238
|
-
# ((1.0/3)*((29.0/6)**2) * t) + (4.0/29)
|
239
|
-
end
|
240
|
-
}
|
241
|
-
{
|
242
|
-
:L => ((116 * fy) - 16),
|
243
|
-
:a => (500 * (fx - fy)),
|
244
|
-
:b => (200 * (fy - fz))
|
245
|
-
}
|
240
|
+
##
|
241
|
+
# Present the color as an HTML/CSS color string (+#ccddee+).
|
242
|
+
def html
|
243
|
+
"##{hex}"
|
246
244
|
end
|
247
245
|
|
248
|
-
|
249
|
-
#
|
250
|
-
#
|
251
|
-
|
252
|
-
|
253
|
-
|
246
|
+
##
|
247
|
+
# Present the color as an CSS `rgb` function with optional `alpha`.
|
248
|
+
#
|
249
|
+
# ```ruby
|
250
|
+
# rgb = Color::RGB.from_percentage(0, 50, 100)
|
251
|
+
# rgb.css # => rgb(0 50.00% 100.00%)
|
252
|
+
# rgb.css(alpha: 0.5) # => rgb(0 50.00% 100.00% / 0.50)
|
253
|
+
# ```
|
254
|
+
def css(alpha: nil)
|
255
|
+
params = [css_value(red_p, :percent), css_value(green_p, :percent), css_value(blue_p, :percent)].join(" ")
|
256
|
+
params = "#{params} / #{css_value(alpha)}" if alpha
|
254
257
|
|
255
|
-
|
256
|
-
# percentage of the resulting colour. Strictly speaking, this isn't a
|
257
|
-
# darken_by operation.
|
258
|
-
def darken_by(percent)
|
259
|
-
mix_with(Black, percent)
|
258
|
+
"rgb(#{params})"
|
260
259
|
end
|
261
260
|
|
262
|
-
|
263
|
-
#
|
264
|
-
def
|
265
|
-
|
266
|
-
|
261
|
+
##
|
262
|
+
# Computes the ΔE* 2000 difference via Color::CIELAB. See Color::CIELAB#delta_e2000.
|
263
|
+
def delta_e2000(other) = to_lab.delta_e2000(coerce(other).to_lab)
|
264
|
+
|
265
|
+
##
|
266
|
+
# Mix the \RGB hue with White so that the \RGB hue is the specified percentage of the
|
267
|
+
# resulting color.
|
268
|
+
#
|
269
|
+
# Strictly speaking, this isn't a `lighten_by` operation, but it mostly works.
|
270
|
+
def lighten_by(percent) = mix_with(Color::RGB::White, percent)
|
267
271
|
|
268
|
-
|
269
|
-
|
270
|
-
|
272
|
+
##
|
273
|
+
# Mix the \RGB hue with Black so that the \RGB hue is the specified percentage of the
|
274
|
+
# resulting color.
|
275
|
+
#
|
276
|
+
# Strictly speaking, this isn't a `darken_by` operation, but it mostly works.
|
277
|
+
def darken_by(percent) = mix_with(Color::RGB::Black, percent)
|
278
|
+
|
279
|
+
##
|
280
|
+
# Mix the mask color with the current color at the stated opacity percentage (0..100).
|
281
|
+
def mix_with(mask, opacity)
|
282
|
+
opacity = normalize(opacity / 100.0)
|
283
|
+
mask = coerce(mask)
|
271
284
|
|
272
|
-
|
285
|
+
with(
|
286
|
+
r: (r * opacity) + (mask.r * (1 - opacity)),
|
287
|
+
g: (g * opacity) + (mask.g * (1 - opacity)),
|
288
|
+
b: (b * opacity) + (mask.b * (1 - opacity))
|
289
|
+
)
|
273
290
|
end
|
274
291
|
|
275
|
-
|
276
|
-
#
|
277
|
-
# brightness.
|
292
|
+
##
|
293
|
+
# Returns the brightness value for a color, a number between 0..1.
|
278
294
|
#
|
279
|
-
#
|
280
|
-
#
|
281
|
-
def brightness
|
282
|
-
to_yiq.y
|
283
|
-
end
|
284
|
-
# Convert to grayscale.
|
285
|
-
def to_grayscale
|
286
|
-
Color::GrayScale.from_fraction(to_hsl.l)
|
287
|
-
end
|
288
|
-
alias to_greyscale to_grayscale
|
295
|
+
# Based on the Y value of Color::YIQ encoding, representing luminosity, or perceived
|
296
|
+
# brightness.
|
297
|
+
def brightness = to_yiq.y
|
289
298
|
|
290
|
-
|
291
|
-
#
|
292
|
-
# percentages will
|
299
|
+
##
|
300
|
+
# Returns a new \RGB color with the brightness adjusted by the specified percentage via
|
301
|
+
# Color::HSL. Negative percentages will darken the color; positive percentages will
|
302
|
+
# brighten the color.
|
293
303
|
#
|
294
|
-
#
|
295
|
-
#
|
304
|
+
# ```ruby
|
305
|
+
# dark_blue = Color::RGB::DarkBlue # => RGB [#00008b]
|
306
|
+
# dark_blue.adjust_brightness(10) # => RGB [#000099]
|
307
|
+
# dark_blue.adjust_brightness(-10) # => RGB [#00007d]
|
308
|
+
# ```
|
296
309
|
def adjust_brightness(percent)
|
297
|
-
|
298
|
-
hsl
|
299
|
-
hsl.l *= percent
|
300
|
-
hsl.to_rgb
|
310
|
+
hsl = to_hsl
|
311
|
+
hsl.with(l: hsl.l * percent_adjustment(percent)).to_rgb
|
301
312
|
end
|
302
313
|
|
303
|
-
|
304
|
-
#
|
305
|
-
# percentages will
|
314
|
+
##
|
315
|
+
# Returns a new \RGB color with the saturation adjusted by the specified percentage via
|
316
|
+
# Color::HSL. Negative percentages will reduce the saturation; positive percentages will
|
317
|
+
# increase the saturation.
|
306
318
|
#
|
307
|
-
#
|
308
|
-
#
|
319
|
+
# ```ruby
|
320
|
+
# dark_blue = Color::RGB::DarkBlue # => RGB [#00008b]
|
321
|
+
# dark_blue.adjust_saturation(10) # => RGB [#00008b]
|
322
|
+
# dark_blue.adjust_saturation(-10) # => RGB [#070784]
|
323
|
+
# ```
|
309
324
|
def adjust_saturation(percent)
|
310
|
-
|
311
|
-
hsl
|
312
|
-
hsl.s *= percent
|
313
|
-
hsl.to_rgb
|
325
|
+
hsl = to_hsl
|
326
|
+
hsl.with(s: hsl.s * percent_adjustment(percent)).to_rgb
|
314
327
|
end
|
315
328
|
|
316
|
-
|
317
|
-
#
|
329
|
+
##
|
330
|
+
# Returns a new \RGB color with the hue adjusted by the specified percentage via
|
331
|
+
# Color::HSL. Negative percentages will reduce the hue; positive percentages will
|
318
332
|
# increase the hue.
|
319
333
|
#
|
320
|
-
#
|
321
|
-
#
|
334
|
+
# ```ruby
|
335
|
+
# dark_blue = Color::RGB::DarkBlue # => RGB [#00008b]
|
336
|
+
# dark_blue.adjust_hue(10) # => RGB [#38008b]
|
337
|
+
# dark_blue.adjust_hue(-10) # => RGB [#00388b]
|
338
|
+
# ```
|
322
339
|
def adjust_hue(percent)
|
323
|
-
|
324
|
-
hsl
|
325
|
-
hsl.h *= percent
|
326
|
-
hsl.to_rgb
|
340
|
+
hsl = to_hsl
|
341
|
+
hsl.with(h: hsl.h * percent_adjustment(percent)).to_rgb
|
327
342
|
end
|
328
343
|
|
329
|
-
|
330
|
-
#
|
331
|
-
|
332
|
-
#
|
333
|
-
#
|
334
|
-
#
|
335
|
-
#
|
336
|
-
#
|
337
|
-
#
|
338
|
-
#
|
339
|
-
# an arbitrarily large number. The values
|
340
|
-
#
|
341
|
-
#
|
342
|
-
|
343
|
-
|
344
|
+
##
|
345
|
+
# Determines the closest match to this color from a list of provided colors or `nil` if
|
346
|
+
# `color_list` is empty or no color is found within the `threshold_distance`.
|
347
|
+
#
|
348
|
+
# The default search uses the CIE ΔE* 1994 algorithm (CIE94) to find near matches based
|
349
|
+
# on the perceived visual differences between the colors. The default value for
|
350
|
+
# `algorithm` is `:delta_e94`.
|
351
|
+
#
|
352
|
+
# `threshold_distance` is used to determine the minimum color distance permitted. Uses
|
353
|
+
# the CIE ΔE* 1994 algorithm (CIE94) to find near matches based on perceived visual
|
354
|
+
# color. The default value (1000.0) is an arbitrarily large number. The values `:jnd`
|
355
|
+
# and `:just_noticeable` may be passed as the `threshold_distance` to use the value
|
356
|
+
# `2.3`.
|
357
|
+
#
|
358
|
+
# All ΔE* formulae were designed to use 1.0 as a "just noticeable difference" (JND),
|
359
|
+
# but CIE ΔE*ab 1976 defined JND as 2.3.
|
360
|
+
#
|
361
|
+
# :call-seq:
|
362
|
+
# closest_match(color_list, algorithm: :delta_e94, threshold_distance: 1000.0)
|
363
|
+
def closest_match(color_list, *args, **kwargs)
|
364
|
+
color_list = [color_list].flatten(1)
|
344
365
|
return nil if color_list.empty?
|
345
366
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
367
|
+
algorithm = kwargs[:algorithm] || args.first || :delta_e94
|
368
|
+
threshold_distance = kwargs[:threshold_distance] || args[1] || 1000.0
|
369
|
+
|
370
|
+
threshold_distance =
|
371
|
+
case threshold_distance
|
372
|
+
when :jnd, :just_noticeable
|
373
|
+
2.3
|
374
|
+
else
|
375
|
+
threshold_distance.to_f
|
376
|
+
end
|
377
|
+
|
353
378
|
closest_distance = threshold_distance
|
354
379
|
best_match = nil
|
355
380
|
|
356
381
|
color_list.each do |c|
|
357
|
-
distance =
|
358
|
-
if
|
382
|
+
distance = contrast(c, algorithm)
|
383
|
+
if distance < closest_distance
|
359
384
|
closest_distance = distance
|
360
385
|
best_match = c
|
361
386
|
end
|
362
387
|
end
|
388
|
+
|
363
389
|
best_match
|
364
390
|
end
|
365
391
|
|
366
|
-
|
367
|
-
# http://en.wikipedia.org/wiki/Color_difference#CIE94
|
392
|
+
##
|
393
|
+
# The Delta E (CIE94) algorithm http://en.wikipedia.org/wiki/Color_difference#CIE94
|
368
394
|
#
|
369
|
-
# There is a newer version, CIEDE2000, that uses slightly more complicated
|
370
|
-
#
|
371
|
-
# the CIE94 algorithm. color_1 and color_2 are both L*a*b* hashes,
|
372
|
-
# rendered by #to_lab.
|
395
|
+
# There is a newer version, CIEDE2000, that uses slightly more complicated math, but
|
396
|
+
# addresses "the perceptual uniformity issue" left lingering by the CIE94 algorithm.
|
373
397
|
#
|
374
|
-
# Since our source is treated as sRGB, we use the "graphic arts" presets
|
375
|
-
#
|
398
|
+
# Since our source is treated as sRGB, we use the "graphic arts" presets for k_L, k_1,
|
399
|
+
# and k_2
|
376
400
|
#
|
377
401
|
# The calculations go through LCH(ab). (?)
|
378
402
|
#
|
379
403
|
# See also http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html
|
380
|
-
|
381
|
-
# NOTE: This should be moved to Color::Lab.
|
382
|
-
def delta_e94(color_1, color_2, weighting_type = :graphic_arts)
|
383
|
-
case weighting_type
|
384
|
-
when :graphic_arts
|
385
|
-
k_1 = 0.045
|
386
|
-
k_2 = 0.015
|
387
|
-
k_L = 1
|
388
|
-
when :textiles
|
389
|
-
k_1 = 0.048
|
390
|
-
k_2 = 0.014
|
391
|
-
k_L = 2
|
392
|
-
else
|
393
|
-
raise ArgumentError, "Unsupported weighting type #{weighting_type}."
|
394
|
-
end
|
404
|
+
def delta_e94(...) = to_lab.delta_e94(...)
|
395
405
|
|
396
|
-
|
397
|
-
|
398
|
-
# ((delta_C / (k_C * s_C)) ** 2) +
|
399
|
-
# ((delta_H / (k_H * s_H)) ** 2)
|
400
|
-
# )
|
401
|
-
#
|
402
|
-
# Under some circumstances in real computers, delta_H could be an
|
403
|
-
# imaginary number (it's a square root value), so we're going to treat
|
404
|
-
# this as:
|
405
|
-
#
|
406
|
-
# delta_E = Math.sqrt(
|
407
|
-
# ((delta_L / (k_L * s_L)) ** 2) +
|
408
|
-
# ((delta_C / (k_C * s_C)) ** 2) +
|
409
|
-
# (delta_H2 / ((k_H * s_H) ** 2)))
|
410
|
-
# )
|
411
|
-
#
|
412
|
-
# And not perform the square root when calculating delta_H2.
|
413
|
-
|
414
|
-
k_C = k_H = 1
|
415
|
-
|
416
|
-
l_1, a_1, b_1 = color_1.values_at(:L, :a, :b)
|
417
|
-
l_2, a_2, b_2 = color_2.values_at(:L, :a, :b)
|
418
|
-
|
419
|
-
delta_a = a_1 - a_2
|
420
|
-
delta_b = b_1 - b_2
|
421
|
-
|
422
|
-
c_1 = Math.sqrt((a_1 ** 2) + (b_1 ** 2))
|
423
|
-
c_2 = Math.sqrt((a_2 ** 2) + (b_2 ** 2))
|
424
|
-
|
425
|
-
delta_L = color_1[:L] - color_2[:L]
|
426
|
-
delta_C = c_1 - c_2
|
427
|
-
|
428
|
-
delta_H2 = (delta_a ** 2) + (delta_b ** 2) - (delta_C ** 2)
|
429
|
-
|
430
|
-
s_L = 1
|
431
|
-
s_C = 1 + k_1 * c_1
|
432
|
-
s_H = 1 + k_2 * c_1
|
433
|
-
|
434
|
-
composite_L = (delta_L / (k_L * s_L)) ** 2
|
435
|
-
composite_C = (delta_C / (k_C * s_C)) ** 2
|
436
|
-
composite_H = delta_H2 / ((k_H * s_H) ** 2)
|
437
|
-
Math.sqrt(composite_L + composite_C + composite_H)
|
438
|
-
end
|
406
|
+
##
|
407
|
+
def red = normalize(r * 255.0, 0.0..255.0) # :nodoc:
|
439
408
|
|
440
|
-
|
441
|
-
def
|
442
|
-
@r * 255.0
|
443
|
-
end
|
444
|
-
# Returns the red component of the colour as a percentage.
|
445
|
-
def red_p
|
446
|
-
@r * 100.0
|
447
|
-
end
|
448
|
-
# Returns the red component of the colour as a fraction in the range 0.0
|
449
|
-
# .. 1.0.
|
450
|
-
def r
|
451
|
-
@r
|
452
|
-
end
|
453
|
-
# Sets the red component of the colour in the normal 0 .. 255 range.
|
454
|
-
def red=(rr)
|
455
|
-
@r = Color.normalize(rr / 255.0)
|
456
|
-
end
|
457
|
-
# Sets the red component of the colour as a percentage.
|
458
|
-
def red_p=(rr)
|
459
|
-
@r = Color.normalize(rr / 100.0)
|
460
|
-
end
|
461
|
-
# Sets the red component of the colour as a fraction in the range 0.0 ..
|
462
|
-
# 1.0.
|
463
|
-
def r=(rr)
|
464
|
-
@r = Color.normalize(rr)
|
465
|
-
end
|
409
|
+
##
|
410
|
+
def red_p = normalize(r * 100.0, 0.0..100.0) # :nodoc:
|
466
411
|
|
467
|
-
|
468
|
-
def green
|
469
|
-
@g * 255.0
|
470
|
-
end
|
471
|
-
# Returns the green component of the colour as a percentage.
|
472
|
-
def green_p
|
473
|
-
@g * 100.0
|
474
|
-
end
|
475
|
-
# Returns the green component of the colour as a fraction in the range 0.0
|
476
|
-
# .. 1.0.
|
477
|
-
def g
|
478
|
-
@g
|
479
|
-
end
|
480
|
-
# Sets the green component of the colour in the normal 0 .. 255 range.
|
481
|
-
def green=(gg)
|
482
|
-
@g = Color.normalize(gg / 255.0)
|
483
|
-
end
|
484
|
-
# Sets the green component of the colour as a percentage.
|
485
|
-
def green_p=(gg)
|
486
|
-
@g = Color.normalize(gg / 100.0)
|
487
|
-
end
|
488
|
-
# Sets the green component of the colour as a fraction in the range 0.0 ..
|
489
|
-
# 1.0.
|
490
|
-
def g=(gg)
|
491
|
-
@g = Color.normalize(gg)
|
492
|
-
end
|
412
|
+
##
|
413
|
+
def green = normalize(g * 255.0, 0.0..255.0) # :nodoc:
|
493
414
|
|
494
|
-
|
495
|
-
def
|
496
|
-
@b * 255.0
|
497
|
-
end
|
498
|
-
# Returns the blue component of the colour as a percentage.
|
499
|
-
def blue_p
|
500
|
-
@b * 100.0
|
501
|
-
end
|
502
|
-
# Returns the blue component of the colour as a fraction in the range 0.0
|
503
|
-
# .. 1.0.
|
504
|
-
def b
|
505
|
-
@b
|
506
|
-
end
|
507
|
-
# Sets the blue component of the colour in the normal 0 .. 255 range.
|
508
|
-
def blue=(bb)
|
509
|
-
@b = Color.normalize(bb / 255.0)
|
510
|
-
end
|
511
|
-
# Sets the blue component of the colour as a percentage.
|
512
|
-
def blue_p=(bb)
|
513
|
-
@b = Color.normalize(bb / 100.0)
|
514
|
-
end
|
515
|
-
# Sets the blue component of the colour as a fraction in the range 0.0 ..
|
516
|
-
# 1.0.
|
517
|
-
def b=(bb)
|
518
|
-
@b = Color.normalize(bb)
|
519
|
-
end
|
415
|
+
##
|
416
|
+
def green_p = normalize(g * 100.0, 0.0..100.0) # :nodoc:
|
520
417
|
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
418
|
+
##
|
419
|
+
def blue = normalize(b * 255.0, 0.0..255.0) # :nodoc:
|
420
|
+
|
421
|
+
##
|
422
|
+
def blue_p = normalize(b * 100.0, 0.0..100.0) # :nodoc:
|
423
|
+
|
424
|
+
##
|
425
|
+
# Return a Grayscale color object created from the largest of the `r`, `g`, and `b`
|
426
|
+
# values.
|
427
|
+
def max_rgb_as_grayscale = Color::Grayscale.from_fraction([r, g, b].max)
|
428
|
+
|
429
|
+
##
|
430
|
+
def inspect = "RGB [#{html}]" # :nodoc:
|
431
|
+
|
432
|
+
##
|
433
|
+
def pretty_print(q) # :nodoc:
|
434
|
+
q.text "RGB"
|
435
|
+
q.breakable
|
436
|
+
q.group 2, "[", "]" do
|
437
|
+
q.text html
|
438
|
+
end
|
529
439
|
end
|
530
440
|
|
531
|
-
|
532
|
-
|
533
|
-
|
441
|
+
##
|
442
|
+
def to_a = [red, green, blue] # :nodoc:
|
443
|
+
|
444
|
+
##
|
445
|
+
alias_method :deconstruct, :to_a # :nodoc:
|
446
|
+
|
447
|
+
##
|
448
|
+
def deconstruct_keys(_keys) = {r:, g:, b:, red:, green:, blue:} # :nodoc:
|
449
|
+
|
450
|
+
##
|
451
|
+
def to_internal = [r, g, b] # :nodoc:
|
452
|
+
|
453
|
+
##
|
454
|
+
# Outputs how much contrast this color has with another RGB color.
|
455
|
+
#
|
456
|
+
# The `delta_e94` algorithm uses ΔE*94 for contrast calculations and the `delta_e2000`
|
457
|
+
# algorithm uses ΔE*2000.
|
534
458
|
#
|
535
|
-
# The
|
536
|
-
#
|
537
|
-
|
538
|
-
|
459
|
+
# The `naive` algorithm treats the foreground and background colors as the same.
|
460
|
+
# Any result over about 0.22 should have a high likelihood of being legible, but the
|
461
|
+
# larger the difference, the more contrast. Otherwise, to be safe go with something
|
462
|
+
# > 0.3.
|
463
|
+
#
|
464
|
+
# :call-seq:
|
465
|
+
# contrast(other, algorithm: :naive)
|
466
|
+
# contrast(other, algorithm: :delta_e94)
|
467
|
+
# contrast(other, algorithm: :delta_e2000)
|
468
|
+
def contrast(other, *args, **kwargs)
|
469
|
+
other = coerce(other)
|
470
|
+
|
471
|
+
algorithm = kwargs[:algorithm] || args.first || :naive
|
472
|
+
|
473
|
+
case algorithm
|
474
|
+
when :delta_e94
|
475
|
+
delta_e94(other)
|
476
|
+
when :delta_e2000
|
477
|
+
delta_e2000(other)
|
478
|
+
when :naive
|
479
|
+
# The following numbers have been set with some care.
|
480
|
+
((diff_brightness(other) * 0.65) +
|
481
|
+
(diff_hue(other) * 0.20) +
|
482
|
+
(diff_luminosity(other) * 0.15))
|
483
|
+
else
|
484
|
+
raise ARgumentError, "Unknown algorithm #{algorithm.inspect}"
|
485
|
+
end
|
539
486
|
end
|
540
487
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
488
|
+
private
|
489
|
+
|
490
|
+
##
|
491
|
+
def percent_adjustment(percent) # :nodoc:
|
492
|
+
percent /= 100.0
|
493
|
+
percent += 1.0
|
494
|
+
percent = [percent, 2.0].min
|
495
|
+
[0.0, percent].max
|
545
496
|
end
|
546
|
-
alias max_rgb_as_greyscale max_rgb_as_grayscale
|
547
497
|
|
548
|
-
|
549
|
-
|
498
|
+
##
|
499
|
+
# Provides the luminosity difference between two rbg vals
|
500
|
+
def diff_luminosity(other) # :nodoc:
|
501
|
+
l1 = (0.2126 * other.r**2.2) +
|
502
|
+
(0.7152 * other.b**2.2) +
|
503
|
+
(0.0722 * other.g**2.2)
|
504
|
+
|
505
|
+
l2 = (0.2126 * r**2.2) +
|
506
|
+
(0.7152 * b**2.2) +
|
507
|
+
(0.0722 * g**2.2)
|
508
|
+
|
509
|
+
(([l1, l2].max + 0.05) / ([l1, l2].min + 0.05) - 1) / 20.0
|
550
510
|
end
|
551
511
|
|
552
|
-
|
553
|
-
|
512
|
+
##
|
513
|
+
# Provides the brightness difference.
|
514
|
+
def diff_brightness(other) # :nodoc:
|
515
|
+
br1 = (299 * other.r + 587 * other.g + 114 * other.b)
|
516
|
+
br2 = (299 * r + 587 * g + 114 * b)
|
517
|
+
(br1 - br2).abs / 1000.0
|
554
518
|
end
|
555
519
|
|
556
|
-
|
557
|
-
#
|
558
|
-
def
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
rgb.instance_variable_set(:@b, -rgb.b)
|
563
|
-
rgb
|
520
|
+
##
|
521
|
+
# Provides the euclidean distance between the two color values
|
522
|
+
def diff_euclidean(other)
|
523
|
+
((((other.r - r)**2) +
|
524
|
+
((other.g - g)**2) +
|
525
|
+
((other.b - b)**2))**0.5) / 1.7320508075688772
|
564
526
|
end
|
565
527
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
percent
|
528
|
+
##
|
529
|
+
# Difference in the two colors' hue
|
530
|
+
def diff_hue(other) # :nodoc:
|
531
|
+
((r - other.r).abs +
|
532
|
+
(g - other.g).abs +
|
533
|
+
(b - other.b).abs) / 3
|
573
534
|
end
|
574
535
|
end
|
575
536
|
|
576
537
|
class << Color::RGB
|
577
|
-
|
538
|
+
##
|
539
|
+
# Creates a RGB color object from percentage values (0.0 .. 100.0).
|
578
540
|
#
|
579
|
-
#
|
580
|
-
|
581
|
-
|
541
|
+
# ```ruby
|
542
|
+
# Color::RGB.from_percentage(10, 20, 30)
|
543
|
+
# ```
|
544
|
+
def from_percentage(*args, **kwargs)
|
545
|
+
r, g, b, names =
|
546
|
+
case [args, kwargs]
|
547
|
+
in [[r, g, b], {}]
|
548
|
+
[r, g, b, nil]
|
549
|
+
in [[_, _, _, _], {}]
|
550
|
+
args
|
551
|
+
in [[], {r:, g:, b:}]
|
552
|
+
[r, g, b, nil]
|
553
|
+
in [[], {r:, g:, b:, names:}]
|
554
|
+
[r, g, b, names]
|
555
|
+
else
|
556
|
+
new(*args, **kwargs)
|
557
|
+
end
|
558
|
+
|
559
|
+
new(r: r / 100.0, g: g / 100.0, b: b / 100.0, names: names)
|
582
560
|
end
|
583
561
|
|
584
|
-
# Creates
|
562
|
+
# Creates a RGB color object from the standard three byte range (0 .. 255).
|
585
563
|
#
|
586
|
-
#
|
587
|
-
|
588
|
-
|
589
|
-
|
564
|
+
# ```ruby
|
565
|
+
# Color::RGB.from_values(32, 64, 128)
|
566
|
+
# Color::RGB.from_values(0x20, 0x40, 0x80)
|
567
|
+
# ```
|
568
|
+
def from_values(*args, **kwargs)
|
569
|
+
r, g, b, names =
|
570
|
+
case [args, kwargs]
|
571
|
+
in [[r, g, b], {}]
|
572
|
+
[r, g, b, nil]
|
573
|
+
in [[_, _, _, _], {}]
|
574
|
+
args
|
575
|
+
in [[], {r:, g:, b:}]
|
576
|
+
[r, g, b, nil]
|
577
|
+
in [[], {r:, g:, b:, names:}]
|
578
|
+
[r, g, b, names]
|
579
|
+
else
|
580
|
+
new(*args, **kwargs)
|
581
|
+
end
|
590
582
|
|
591
|
-
|
592
|
-
def from_grayscale_fraction(l = 0.0, &block)
|
593
|
-
new(l, l, l, 1.0, &block)
|
583
|
+
new(r: r / 255.0, g: g / 255.0, b: b / 255.0, names: names)
|
594
584
|
end
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
#
|
601
|
-
|
602
|
-
|
603
|
-
#
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
585
|
+
|
586
|
+
##
|
587
|
+
alias_method :from_fraction, :new
|
588
|
+
|
589
|
+
##
|
590
|
+
alias_method :from_internal, :new # :nodoc:
|
591
|
+
|
592
|
+
##
|
593
|
+
# Creates a RGB color object from an HTML color descriptor (e.g., `"fed"` or
|
594
|
+
# `"#cabbed;"`.
|
595
|
+
#
|
596
|
+
# ```ruby
|
597
|
+
# Color::RGB.from_html("fed")
|
598
|
+
# Color::RGB.from_html("#fed")
|
599
|
+
# Color::RGB.from_html("#cabbed")
|
600
|
+
# Color::RGB.from_html("cabbed")
|
601
|
+
# ```
|
602
|
+
def from_html(html_color)
|
603
|
+
h = html_color.scan(/\h/i)
|
604
|
+
r, g, b = case h.size
|
608
605
|
when 3
|
609
|
-
|
606
|
+
h.map { |v| (v * 2).to_i(16) }
|
610
607
|
when 6
|
611
|
-
|
608
|
+
h.each_slice(2).map { |v| v.join.to_i(16) }
|
612
609
|
else
|
613
|
-
raise ArgumentError, "Not a supported HTML
|
610
|
+
raise ArgumentError, "Not a supported HTML color type."
|
614
611
|
end
|
615
|
-
end
|
616
612
|
|
617
|
-
|
618
|
-
# #from_html method in that if the colour code matches a named colour,
|
619
|
-
# the existing colour will be returned.
|
620
|
-
#
|
621
|
-
# Color::RGB.by_hex('ff0000').name # => 'red'
|
622
|
-
# Color::RGB.by_hex('ff0001').name # => nil
|
623
|
-
#
|
624
|
-
# If a block is provided, the value that is returned by the block will
|
625
|
-
# be returned instead of the exception caused by an error in providing a
|
626
|
-
# correct hex format.
|
627
|
-
def by_hex(hex, &block)
|
628
|
-
__by_hex.fetch(html_hexify(hex)) { from_html(hex) }
|
629
|
-
rescue
|
630
|
-
if block
|
631
|
-
block.call
|
632
|
-
else
|
633
|
-
raise
|
634
|
-
end
|
613
|
+
from_values(r, g, b)
|
635
614
|
end
|
636
615
|
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
616
|
+
##
|
617
|
+
# Find or create a color by an HTML hex code. This differs from the #from_html method
|
618
|
+
# in that if the color code matches a named color, the existing color will be
|
619
|
+
# returned.
|
620
|
+
#
|
621
|
+
# ```ruby
|
622
|
+
# Color::RGB.by_hex('ff0000').name # => 'red'
|
623
|
+
# Color::RGB.by_hex('ff0001').name # => nil
|
624
|
+
# ```
|
625
|
+
#
|
626
|
+
# An exception will be raised if the value provided is not found or cannot be
|
627
|
+
# interpreted as a valid hex colour.
|
628
|
+
def by_hex(hex) = __by_hex.fetch(html_hexify(hex)) { from_html(hex) }
|
641
629
|
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
630
|
+
##
|
631
|
+
# Return a color as identified by the color name.
|
632
|
+
def by_name(name, &block) = __by_name.fetch(name.to_s.downcase, &block)
|
633
|
+
|
634
|
+
##
|
635
|
+
# Return a color as identified by the color name, or by hex.
|
636
|
+
def by_css(name_or_hex, &block) = by_name(name_or_hex) { by_hex(name_or_hex, &block) }
|
646
637
|
|
647
|
-
|
638
|
+
##
|
639
|
+
# Extract named or hex colors from the provided text.
|
648
640
|
def extract_colors(text, mode = :both)
|
649
|
-
text
|
641
|
+
text = text.downcase
|
650
642
|
regex = case mode
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
643
|
+
when :name
|
644
|
+
Regexp.union(__by_name.keys)
|
645
|
+
when :hex
|
646
|
+
Regexp.union(__by_hex.keys)
|
647
|
+
when :both
|
648
|
+
Regexp.union(__by_hex.keys + __by_name.keys)
|
649
|
+
end
|
658
650
|
|
659
651
|
text.scan(regex).map { |match|
|
660
652
|
case mode
|
@@ -667,43 +659,48 @@ class << Color::RGB
|
|
667
659
|
end
|
668
660
|
}
|
669
661
|
end
|
670
|
-
end
|
671
662
|
|
672
|
-
class << Color::RGB
|
673
663
|
private
|
674
|
-
|
675
|
-
|
676
|
-
|
664
|
+
|
665
|
+
##
|
666
|
+
def __create_named_color(mod, rgb, *names) # :nodoc:
|
667
|
+
used = names - mod.constants.map(&:to_sym)
|
668
|
+
|
669
|
+
if used.length < names.length
|
670
|
+
raise ArgumentError, "#{names.join(", ")} already defined in #{mod}"
|
677
671
|
end
|
678
672
|
|
679
|
-
names.
|
673
|
+
rgb = rgb.with(names: Array(names).flatten.compact.map { _1.to_s.downcase }.sort.uniq)
|
674
|
+
|
675
|
+
names.each { mod.const_set(_1, rgb) }
|
680
676
|
|
681
|
-
rgb.names =
|
682
|
-
|
677
|
+
rgb.names.each { __by_name[_1] = __by_name[_1.to_s] = rgb }
|
678
|
+
lower = rgb.name.downcase
|
679
|
+
|
680
|
+
__by_name[lower] = __by_name[lower.to_s] = rgb
|
683
681
|
__by_hex[rgb.hex] = rgb
|
684
|
-
rgb.freeze
|
685
682
|
end
|
686
683
|
|
687
|
-
|
684
|
+
##
|
685
|
+
def __by_hex # :nodoc:
|
688
686
|
@__by_hex ||= {}
|
689
687
|
end
|
690
688
|
|
691
|
-
|
689
|
+
##
|
690
|
+
def __by_name # :nodoc:
|
692
691
|
@__by_name ||= {}
|
693
692
|
end
|
694
693
|
|
695
|
-
|
696
|
-
|
697
|
-
h = hex.to_s.downcase.scan(/
|
694
|
+
##
|
695
|
+
def html_hexify(hex) # :nodoc:
|
696
|
+
h = hex.to_s.downcase.scan(/\h/)
|
698
697
|
case h.size
|
699
698
|
when 3
|
700
699
|
h.map { |v| (v * 2) }.join
|
701
700
|
when 6
|
702
701
|
h.join
|
703
702
|
else
|
704
|
-
raise ArgumentError, "Not a supported HTML
|
703
|
+
raise ArgumentError, "Not a supported HTML color type."
|
705
704
|
end
|
706
705
|
end
|
707
706
|
end
|
708
|
-
|
709
|
-
require 'color/rgb/colors'
|