color_contrast_calc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.travis.yml +9 -0
- data/.yardopts +4 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.ja.md +296 -0
- data/README.md +295 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/color_contrast_calc.gemspec +30 -0
- data/examples/color_instance.rb +12 -0
- data/examples/color_lists.rb +26 -0
- data/examples/grayscale.rb +10 -0
- data/examples/sort_colors.rb +30 -0
- data/examples/yellow_black_contrast.rb +12 -0
- data/examples/yellow_orange_contrast.rb +32 -0
- data/exe/color_contrast_calc +4 -0
- data/lib/color_contrast_calc.rb +108 -0
- data/lib/color_contrast_calc/checker.rb +126 -0
- data/lib/color_contrast_calc/color.rb +409 -0
- data/lib/color_contrast_calc/converter.rb +191 -0
- data/lib/color_contrast_calc/data/color_keywords.json +149 -0
- data/lib/color_contrast_calc/shim.rb +32 -0
- data/lib/color_contrast_calc/sorter.rb +227 -0
- data/lib/color_contrast_calc/threshold_finder.rb +276 -0
- data/lib/color_contrast_calc/utils.rb +240 -0
- data/lib/color_contrast_calc/version.rb +5 -0
- metadata +146 -0
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'matrix'
|
4
|
+
|
5
|
+
module ColorContrastCalc
|
6
|
+
##
|
7
|
+
# Collection of modules that implement the main logic of instance
|
8
|
+
# methods of Color, +Color#new_*_color()+.
|
9
|
+
|
10
|
+
module Converter
|
11
|
+
using Shim unless 0.respond_to? :clamp
|
12
|
+
|
13
|
+
##
|
14
|
+
# Values given as an array are passed to a given block, and new values
|
15
|
+
# returned from the block are rounded and clamped between 0 and 255,
|
16
|
+
# so that the resulting values can be treated as an RGB value.
|
17
|
+
#
|
18
|
+
# @param vals [Array<Numeric>] An array of three numbers
|
19
|
+
# @return [Array<Integer>] Value that can possibly be an RGB value
|
20
|
+
|
21
|
+
def self.rgb_map(vals)
|
22
|
+
if block_given?
|
23
|
+
return vals.map do |val|
|
24
|
+
new_val = yield val
|
25
|
+
new_val.round.clamp(0, 255)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
vals.map {|val| val.round.clamp(0, 255) }
|
30
|
+
end
|
31
|
+
|
32
|
+
module Contrast
|
33
|
+
##
|
34
|
+
# Return contrast adjusted RGB value of passed color.
|
35
|
+
#
|
36
|
+
# THe calculation is based on the definition found at
|
37
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-contrast
|
38
|
+
# https://www.w3.org/TR/SVG/filters.html#TransferFunctionElementAttributes
|
39
|
+
# @param rgb [Array<Integer>] The Original RGB value before the adjustment
|
40
|
+
# @param ratio [Float] Adjustment ratio in percentage
|
41
|
+
# @return [Array<Integer>] Contrast adjusted RGB value
|
42
|
+
|
43
|
+
def self.calc_rgb(rgb, ratio = 100)
|
44
|
+
r = ratio.to_f
|
45
|
+
Converter.rgb_map(rgb) {|c| (c * r + 255 * (50 - r / 2)) / 100 }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Brightness
|
50
|
+
##
|
51
|
+
# Return brightness adjusted RGB value of passed color.
|
52
|
+
#
|
53
|
+
# THe calculation is based on the definition found at
|
54
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-brightness
|
55
|
+
# https://www.w3.org/TR/SVG/filters.html#TransferFunctionElementAttributes
|
56
|
+
# @param rgb [Array<Integer>] The Original RGB value before the adjustment
|
57
|
+
# @param ratio [Float] Adjustment ratio in percentage
|
58
|
+
# @return [Array<Integer>] Brightness adjusted RGB value
|
59
|
+
|
60
|
+
def self.calc_rgb(rgb, ratio = 100)
|
61
|
+
r = ratio.to_f
|
62
|
+
Converter.rgb_map(rgb) {|c| c * r / 100 }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module Invert
|
67
|
+
##
|
68
|
+
# Return an inverted RGB value of passed color.
|
69
|
+
#
|
70
|
+
# The calculation is based on the definition found at
|
71
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-invert
|
72
|
+
# https://www.w3.org/TR/filter-effects-1/#invertEquivalent
|
73
|
+
# https://www.w3.org/TR/SVG/filters.html#TransferFunctionElementAttributes
|
74
|
+
# @param rgb [Array<Integer>] The Original RGB value before the inversion
|
75
|
+
# @param ratio [Float] Proportion of the conversion in percentage
|
76
|
+
# @return [Array<Integer>] Inverted RGB value
|
77
|
+
|
78
|
+
def self.calc_rgb(rgb, ratio)
|
79
|
+
r = ratio.to_f
|
80
|
+
rgb.map {|c| ((100 * c - 2 * c * r + 255 * r) / 100).round }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
module HueRotate
|
85
|
+
# @private
|
86
|
+
CONST_PART = Matrix[[0.213, 0.715, 0.072],
|
87
|
+
[0.213, 0.715, 0.072],
|
88
|
+
[0.213, 0.715, 0.072]]
|
89
|
+
|
90
|
+
# @private
|
91
|
+
COS_PART = Matrix[[0.787, -0.715, -0.072],
|
92
|
+
[-0.213, 0.285, -0.072],
|
93
|
+
[-0.213, -0.715, 0.928]]
|
94
|
+
|
95
|
+
# @private
|
96
|
+
SIN_PART = Matrix[[-0.213, -0.715, 0.928],
|
97
|
+
[0.143, 0.140, -0.283],
|
98
|
+
[-0.787, 0.715, 0.072]]
|
99
|
+
|
100
|
+
##
|
101
|
+
# Return a hue rotation applied RGB value of passed color.
|
102
|
+
#
|
103
|
+
# THe calculation is based on the definition found at
|
104
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-hue-rotate
|
105
|
+
# https://www.w3.org/TR/SVG/filters.html#TransferFunctionElementAttributes
|
106
|
+
# @param rgb [Array<Integer>] The Original RGB value before the rotation
|
107
|
+
# @param deg [Float] Degrees of rotation (0 to 360)
|
108
|
+
# @return [Array<Integer>] Hue rotation applied RGB value
|
109
|
+
|
110
|
+
def self.calc_rgb(rgb, deg)
|
111
|
+
Converter.rgb_map((calc_rotation(deg) * Vector[*rgb]).to_a)
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.deg_to_rad(deg)
|
115
|
+
Math::PI * deg / 180
|
116
|
+
end
|
117
|
+
|
118
|
+
private_class_method :deg_to_rad
|
119
|
+
|
120
|
+
def self.calc_rotation(deg)
|
121
|
+
rad = deg_to_rad(deg)
|
122
|
+
cos_part = COS_PART * Math.cos(rad)
|
123
|
+
sin_part = SIN_PART * Math.sin(rad)
|
124
|
+
CONST_PART + cos_part + sin_part
|
125
|
+
end
|
126
|
+
|
127
|
+
private_class_method :calc_rotation
|
128
|
+
end
|
129
|
+
|
130
|
+
module Saturate
|
131
|
+
# @private
|
132
|
+
CONST_PART = HueRotate::CONST_PART
|
133
|
+
# @private
|
134
|
+
SATURATE_PART = HueRotate::COS_PART
|
135
|
+
|
136
|
+
##
|
137
|
+
# Return a saturated RGB value of passed color.
|
138
|
+
#
|
139
|
+
# The calculation is based on the definition found at
|
140
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-saturate
|
141
|
+
# https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement
|
142
|
+
# @param rgb [Array<Integer>] The Original RGB value before the saturation
|
143
|
+
# @param s [Float] Proprtion of the conversion in percentage
|
144
|
+
# @return [Array<Integer>] Saturated RGB value
|
145
|
+
|
146
|
+
def self.calc_rgb(rgb, s)
|
147
|
+
Converter.rgb_map((calc_saturation(s) * Vector[*rgb]).to_a)
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.calc_saturation(s)
|
151
|
+
CONST_PART + SATURATE_PART * (s.to_f / 100)
|
152
|
+
end
|
153
|
+
|
154
|
+
private_class_method :calc_saturation
|
155
|
+
end
|
156
|
+
|
157
|
+
module Grayscale
|
158
|
+
# @private
|
159
|
+
CONST_PART = Matrix[[0.2126, 0.7152, 0.0722],
|
160
|
+
[0.2126, 0.7152, 0.0722],
|
161
|
+
[0.2126, 0.7152, 0.0722]]
|
162
|
+
|
163
|
+
# @private
|
164
|
+
RATIO_PART = Matrix[[0.7874, -0.7152, -0.0722],
|
165
|
+
[-0.2126, 0.2848, -0.0722],
|
166
|
+
[-0.2126, -0.7152, 0.9278]]
|
167
|
+
|
168
|
+
##
|
169
|
+
# Convert passed a passed color to grayscale.
|
170
|
+
#
|
171
|
+
# The calculation is based on the definition found at
|
172
|
+
# https://www.w3.org/TR/filter-effects/#funcdef-grayscale
|
173
|
+
# https://www.w3.org/TR/filter-effects/#grayscaleEquivalent
|
174
|
+
# https://www.w3.org/TR/SVG/filters.html#feColorMatrixElement
|
175
|
+
# @param rgb [Array<Integer>] The Original RGB value before the conversion
|
176
|
+
# @param s [Float] Conversion ratio in percentage
|
177
|
+
# @return [Array<Integer>] RGB value of grayscale color
|
178
|
+
|
179
|
+
def self.calc_rgb(rgb, s)
|
180
|
+
Converter.rgb_map((calc_grayscale(s) * Vector[*rgb]).to_a)
|
181
|
+
end
|
182
|
+
|
183
|
+
def self.calc_grayscale(s)
|
184
|
+
r = 1 - [100, s].min.to_f / 100
|
185
|
+
CONST_PART + RATIO_PART * r
|
186
|
+
end
|
187
|
+
|
188
|
+
private_class_method :calc_grayscale
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
[
|
2
|
+
["aliceblue", "#f0f8ff"],
|
3
|
+
["antiquewhite", "#faebd7"],
|
4
|
+
["aqua", "#00ffff"],
|
5
|
+
["aquamarine", "#7fffd4"],
|
6
|
+
["azure", "#f0ffff"],
|
7
|
+
["beige", "#f5f5dc"],
|
8
|
+
["bisque", "#ffe4c4"],
|
9
|
+
["black", "#000000"],
|
10
|
+
["blanchedalmond", "#ffebcd"],
|
11
|
+
["blue", "#0000ff"],
|
12
|
+
["blueviolet", "#8a2be2"],
|
13
|
+
["brown", "#a52a2a"],
|
14
|
+
["burlywood", "#deb887"],
|
15
|
+
["cadetblue", "#5f9ea0"],
|
16
|
+
["chartreuse", "#7fff00"],
|
17
|
+
["chocolate", "#d2691e"],
|
18
|
+
["coral", "#ff7f50"],
|
19
|
+
["cornflowerblue", "#6495ed"],
|
20
|
+
["cornsilk", "#fff8dc"],
|
21
|
+
["crimson", "#dc143c"],
|
22
|
+
["cyan", "#00ffff"],
|
23
|
+
["darkblue", "#00008b"],
|
24
|
+
["darkcyan", "#008b8b"],
|
25
|
+
["darkgoldenrod", "#b8860b"],
|
26
|
+
["darkgray", "#a9a9a9"],
|
27
|
+
["darkgreen", "#006400"],
|
28
|
+
["darkgrey", "#a9a9a9"],
|
29
|
+
["darkkhaki", "#bdb76b"],
|
30
|
+
["darkmagenta", "#8b008b"],
|
31
|
+
["darkolivegreen", "#556b2f"],
|
32
|
+
["darkorange", "#ff8c00"],
|
33
|
+
["darkorchid", "#9932cc"],
|
34
|
+
["darkred", "#8b0000"],
|
35
|
+
["darksalmon", "#e9967a"],
|
36
|
+
["darkseagreen", "#8fbc8f"],
|
37
|
+
["darkslateblue", "#483d8b"],
|
38
|
+
["darkslategray", "#2f4f4f"],
|
39
|
+
["darkslategrey", "#2f4f4f"],
|
40
|
+
["darkturquoise", "#00ced1"],
|
41
|
+
["darkviolet", "#9400d3"],
|
42
|
+
["deeppink", "#ff1493"],
|
43
|
+
["deepskyblue", "#00bfff"],
|
44
|
+
["dimgray", "#696969"],
|
45
|
+
["dimgrey", "#696969"],
|
46
|
+
["dodgerblue", "#1e90ff"],
|
47
|
+
["firebrick", "#b22222"],
|
48
|
+
["floralwhite", "#fffaf0"],
|
49
|
+
["forestgreen", "#228b22"],
|
50
|
+
["fuchsia", "#ff00ff"],
|
51
|
+
["gainsboro", "#dcdcdc"],
|
52
|
+
["ghostwhite", "#f8f8ff"],
|
53
|
+
["gold", "#ffd700"],
|
54
|
+
["goldenrod", "#daa520"],
|
55
|
+
["gray", "#808080"],
|
56
|
+
["green", "#008000"],
|
57
|
+
["greenyellow", "#adff2f"],
|
58
|
+
["grey", "#808080"],
|
59
|
+
["honeydew", "#f0fff0"],
|
60
|
+
["hotpink", "#ff69b4"],
|
61
|
+
["indianred", "#cd5c5c"],
|
62
|
+
["indigo", "#4b0082"],
|
63
|
+
["ivory", "#fffff0"],
|
64
|
+
["khaki", "#f0e68c"],
|
65
|
+
["lavender", "#e6e6fa"],
|
66
|
+
["lavenderblush", "#fff0f5"],
|
67
|
+
["lawngreen", "#7cfc00"],
|
68
|
+
["lemonchiffon", "#fffacd"],
|
69
|
+
["lightblue", "#add8e6"],
|
70
|
+
["lightcoral", "#f08080"],
|
71
|
+
["lightcyan", "#e0ffff"],
|
72
|
+
["lightgoldenrodyellow", "#fafad2"],
|
73
|
+
["lightgray", "#d3d3d3"],
|
74
|
+
["lightgreen", "#90ee90"],
|
75
|
+
["lightgrey", "#d3d3d3"],
|
76
|
+
["lightpink", "#ffb6c1"],
|
77
|
+
["lightsalmon", "#ffa07a"],
|
78
|
+
["lightseagreen", "#20b2aa"],
|
79
|
+
["lightskyblue", "#87cefa"],
|
80
|
+
["lightslategray", "#778899"],
|
81
|
+
["lightslategrey", "#778899"],
|
82
|
+
["lightsteelblue", "#b0c4de"],
|
83
|
+
["lightyellow", "#ffffe0"],
|
84
|
+
["lime", "#00ff00"],
|
85
|
+
["limegreen", "#32cd32"],
|
86
|
+
["linen", "#faf0e6"],
|
87
|
+
["magenta", "#ff00ff"],
|
88
|
+
["maroon", "#800000"],
|
89
|
+
["mediumaquamarine", "#66cdaa"],
|
90
|
+
["mediumblue", "#0000cd"],
|
91
|
+
["mediumorchid", "#ba55d3"],
|
92
|
+
["mediumpurple", "#9370db"],
|
93
|
+
["mediumseagreen", "#3cb371"],
|
94
|
+
["mediumslateblue", "#7b68ee"],
|
95
|
+
["mediumspringgreen", "#00fa9a"],
|
96
|
+
["mediumturquoise", "#48d1cc"],
|
97
|
+
["mediumvioletred", "#c71585"],
|
98
|
+
["midnightblue", "#191970"],
|
99
|
+
["mintcream", "#f5fffa"],
|
100
|
+
["mistyrose", "#ffe4e1"],
|
101
|
+
["moccasin", "#ffe4b5"],
|
102
|
+
["navajowhite", "#ffdead"],
|
103
|
+
["navy", "#000080"],
|
104
|
+
["oldlace", "#fdf5e6"],
|
105
|
+
["olive", "#808000"],
|
106
|
+
["olivedrab", "#6b8e23"],
|
107
|
+
["orange", "#ffa500"],
|
108
|
+
["orangered", "#ff4500"],
|
109
|
+
["orchid", "#da70d6"],
|
110
|
+
["palegoldenrod", "#eee8aa"],
|
111
|
+
["palegreen", "#98fb98"],
|
112
|
+
["paleturquoise", "#afeeee"],
|
113
|
+
["palevioletred", "#db7093"],
|
114
|
+
["papayawhip", "#ffefd5"],
|
115
|
+
["peachpuff", "#ffdab9"],
|
116
|
+
["peru", "#cd853f"],
|
117
|
+
["pink", "#ffc0cb"],
|
118
|
+
["plum", "#dda0dd"],
|
119
|
+
["powderblue", "#b0e0e6"],
|
120
|
+
["purple", "#800080"],
|
121
|
+
["red", "#ff0000"],
|
122
|
+
["rosybrown", "#bc8f8f"],
|
123
|
+
["royalblue", "#4169e1"],
|
124
|
+
["saddlebrown", "#8b4513"],
|
125
|
+
["salmon", "#fa8072"],
|
126
|
+
["sandybrown", "#f4a460"],
|
127
|
+
["seagreen", "#2e8b57"],
|
128
|
+
["seashell", "#fff5ee"],
|
129
|
+
["sienna", "#a0522d"],
|
130
|
+
["silver", "#c0c0c0"],
|
131
|
+
["skyblue", "#87ceeb"],
|
132
|
+
["slateblue", "#6a5acd"],
|
133
|
+
["slategray", "#708090"],
|
134
|
+
["slategrey", "#708090"],
|
135
|
+
["snow", "#fffafa"],
|
136
|
+
["springgreen", "#00ff7f"],
|
137
|
+
["steelblue", "#4682b4"],
|
138
|
+
["tan", "#d2b48c"],
|
139
|
+
["teal", "#008080"],
|
140
|
+
["thistle", "#d8bfd8"],
|
141
|
+
["tomato", "#ff6347"],
|
142
|
+
["turquoise", "#40e0d0"],
|
143
|
+
["violet", "#ee82ee"],
|
144
|
+
["wheat", "#f5deb3"],
|
145
|
+
["white", "#ffffff"],
|
146
|
+
["whitesmoke", "#f5f5f5"],
|
147
|
+
["yellow", "#ffff00"],
|
148
|
+
["yellowgreen", "#9acd32"]
|
149
|
+
]
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColorContrastCalc
|
4
|
+
##
|
5
|
+
# Provide methods that are not availabe in older versions of Ruby.
|
6
|
+
|
7
|
+
module Shim
|
8
|
+
refine Regexp do
|
9
|
+
##
|
10
|
+
# Regexp.match?() is available for Ruby >= 2.4,
|
11
|
+
# and the following implementation does not satisfy
|
12
|
+
# the full specification of the original method.
|
13
|
+
|
14
|
+
def match?(str)
|
15
|
+
self === str
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
refine Numeric do
|
20
|
+
##
|
21
|
+
# Comparable#clamp() is available for Ruby >= 2.4,
|
22
|
+
# and the following implementation does not satisfy
|
23
|
+
# the full specification of the original method.
|
24
|
+
|
25
|
+
def clamp(lower_bound, upper_bound)
|
26
|
+
return lower_bound if self < lower_bound
|
27
|
+
return upper_bound if self > upper_bound
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,227 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'color_contrast_calc/color'
|
4
|
+
|
5
|
+
module ColorContrastCalc
|
6
|
+
##
|
7
|
+
# Provide two methods sort() and compile_compare_function()
|
8
|
+
#
|
9
|
+
# The other methods defined in this module should not be considered
|
10
|
+
# as stable interfaces.
|
11
|
+
|
12
|
+
module Sorter
|
13
|
+
using Shim unless //.respond_to? :match?
|
14
|
+
|
15
|
+
# @private The visitiblity ot this module is not changed just
|
16
|
+
# because it is used in unit tests.
|
17
|
+
|
18
|
+
module ColorComponent
|
19
|
+
RGB = 'rgb'.chars
|
20
|
+
HSL = 'hsl'.chars
|
21
|
+
end
|
22
|
+
|
23
|
+
module CompFunc
|
24
|
+
# @private
|
25
|
+
ASCEND = proc {|x, y| x <=> y }
|
26
|
+
# @private
|
27
|
+
DESCEND = proc {|x, y| y <=> x }
|
28
|
+
end
|
29
|
+
|
30
|
+
private_constant :CompFunc
|
31
|
+
|
32
|
+
##
|
33
|
+
# Constants used as a second argeument of Sorter.compile_compare_function()
|
34
|
+
#
|
35
|
+
# The constants COLOR, COMPONENTS and HEX are expected to be used as a
|
36
|
+
# second argument of Sorter.compile_compare_function()
|
37
|
+
|
38
|
+
module KeyTypes
|
39
|
+
# The function returned by Sorter.compile_compare_function() expects
|
40
|
+
# instances of Color as key values when this constants is specified.
|
41
|
+
COLOR = :color
|
42
|
+
# The function returned by Sorter.compile_compare_function() expects
|
43
|
+
# RGB or HSL values as key values when this constants is specified.
|
44
|
+
COMPONENTS = :components
|
45
|
+
# The function returned by Sorter.compile_compare_function() expects
|
46
|
+
# hex color codes as key values when this constants is specified.
|
47
|
+
HEX = :hex
|
48
|
+
# @private
|
49
|
+
CLASS_TO_TYPE = {
|
50
|
+
Color => COLOR,
|
51
|
+
Array => COMPONENTS,
|
52
|
+
String => HEX
|
53
|
+
}.freeze
|
54
|
+
|
55
|
+
##
|
56
|
+
# Returns COLOR, COMPONENTS or HEX when a possible key value is passed.
|
57
|
+
#
|
58
|
+
# @param color [Color, Array<Numeric>, String] Possible key value
|
59
|
+
# @param key_mapper [Proc] Function which retrieves a key value from
|
60
|
+
# +color+, that means <tt>key_mapper[color]</tt> returns a key value
|
61
|
+
# @return [:color, :components, :hex] Symbol that represents a key type
|
62
|
+
|
63
|
+
def self.guess(color, key_mapper = nil)
|
64
|
+
key = key_mapper ? key_mapper[color] : color
|
65
|
+
CLASS_TO_TYPE[key.class]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private shorthands for Utils.hex_to_rgb() and .hex_to_hsl()
|
70
|
+
HEX_TO_COMPONENTS = {
|
71
|
+
rgb: Utils.method(:hex_to_rgb),
|
72
|
+
hsl: Utils.method(:hex_to_hsl)
|
73
|
+
}.freeze
|
74
|
+
|
75
|
+
##
|
76
|
+
# Sort colors in the order specified by +color_order+.
|
77
|
+
#
|
78
|
+
# Sort colors given as a list or tuple of Color instances or hex
|
79
|
+
# color codes.
|
80
|
+
#
|
81
|
+
# You can specify sorting order by giving a +color_order+ tring, such
|
82
|
+
# as "HSL" or "RGB". A component of +color_order+ on the left side
|
83
|
+
# has a higher sorting precedence, and an uppercase letter means
|
84
|
+
# descending order.
|
85
|
+
# @param colors [Array<Color>, Array<String>] Array of Color instances
|
86
|
+
# or items from which color hex codes can be retrieved.
|
87
|
+
# @param color_order [String] String such as "HSL", "RGB" or "lsH"
|
88
|
+
# @param key_mapper [Proc, nil] Proc object used to retrive key values
|
89
|
+
# from items to be sorted
|
90
|
+
# @return [Array<Color>, Array<String>] Array of of sorted colors
|
91
|
+
|
92
|
+
def self.sort(colors, color_order = 'hSL', key_mapper = nil)
|
93
|
+
key_type = KeyTypes.guess(colors[0], key_mapper)
|
94
|
+
compare = compile_compare_function(color_order, key_type, key_mapper)
|
95
|
+
|
96
|
+
colors.sort(&compare)
|
97
|
+
end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Return a Proc object to be passed to Array#sort().
|
101
|
+
#
|
102
|
+
# @param color_order [String] String such as "HSL", "RGB" or "lsH"
|
103
|
+
# @param key_type [Symbol] +:color+, +:components+ or +:hex+
|
104
|
+
# @param key_mapper [Proc, nil] Proc object to be used to retrive
|
105
|
+
# key values from items to be sorted.
|
106
|
+
# @return [Proc] Proc object to be passed to Array#sort()
|
107
|
+
|
108
|
+
def self.compile_compare_function(color_order, key_type, key_mapper = nil)
|
109
|
+
case key_type
|
110
|
+
when KeyTypes::COLOR
|
111
|
+
compare = compile_color_compare_function(color_order)
|
112
|
+
when KeyTypes::COMPONENTS
|
113
|
+
compare = compile_components_compare_function(color_order)
|
114
|
+
when KeyTypes::HEX
|
115
|
+
compare = compile_hex_compare_function(color_order)
|
116
|
+
end
|
117
|
+
|
118
|
+
compose_function(compare, key_mapper)
|
119
|
+
end
|
120
|
+
|
121
|
+
# @private
|
122
|
+
|
123
|
+
def self.compose_function(compare_function, key_mapper = nil)
|
124
|
+
return compare_function unless key_mapper
|
125
|
+
|
126
|
+
proc do |color1, color2|
|
127
|
+
compare_function[key_mapper[color1], key_mapper[color2]]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @private
|
132
|
+
|
133
|
+
def self.color_component_pos(color_order, ordered_components)
|
134
|
+
color_order.downcase.chars.map do |component|
|
135
|
+
ordered_components.index(component)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @private
|
140
|
+
|
141
|
+
def self.parse_color_order(color_order)
|
142
|
+
ordered_components = ColorComponent::RGB
|
143
|
+
ordered_components = ColorComponent::HSL if hsl_order?(color_order)
|
144
|
+
pos = color_component_pos(color_order, ordered_components)
|
145
|
+
funcs = []
|
146
|
+
pos.each_with_index do |ci, i|
|
147
|
+
c = color_order[i]
|
148
|
+
funcs[ci] = Utils.uppercase?(c) ? CompFunc::DESCEND : CompFunc::ASCEND
|
149
|
+
end
|
150
|
+
{ pos: pos, funcs: funcs }
|
151
|
+
end
|
152
|
+
|
153
|
+
# @private
|
154
|
+
|
155
|
+
def self.hsl_order?(color_order)
|
156
|
+
/[hsl]{3}/i.match?(color_order)
|
157
|
+
end
|
158
|
+
|
159
|
+
# @private
|
160
|
+
|
161
|
+
def self.compare_color_components(color1, color2, order)
|
162
|
+
funcs = order[:funcs]
|
163
|
+
order[:pos].each do |i|
|
164
|
+
result = funcs[i][color1[i], color2[i]]
|
165
|
+
return result unless result.zero?
|
166
|
+
end
|
167
|
+
|
168
|
+
0
|
169
|
+
end
|
170
|
+
|
171
|
+
# @private
|
172
|
+
|
173
|
+
def self.compile_components_compare_function(color_order)
|
174
|
+
order = parse_color_order(color_order)
|
175
|
+
|
176
|
+
proc do |color1, color2|
|
177
|
+
compare_color_components(color1, color2, order)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# @private
|
182
|
+
|
183
|
+
def self.compile_hex_compare_function(color_order)
|
184
|
+
order = parse_color_order(color_order)
|
185
|
+
converter = HEX_TO_COMPONENTS[:rgb]
|
186
|
+
converter = HEX_TO_COMPONENTS[:hsl] if hsl_order?(color_order)
|
187
|
+
cache = {}
|
188
|
+
|
189
|
+
proc do |hex1, hex2|
|
190
|
+
color1 = hex_to_components(hex1, converter, cache)
|
191
|
+
color2 = hex_to_components(hex2, converter, cache)
|
192
|
+
|
193
|
+
compare_color_components(color1, color2, order)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# @private
|
198
|
+
|
199
|
+
def self.hex_to_components(hex, converter, cache)
|
200
|
+
cached_components = cache[hex]
|
201
|
+
return cached_components if cached_components
|
202
|
+
|
203
|
+
components = converter[hex]
|
204
|
+
cache[hex] = components
|
205
|
+
|
206
|
+
components
|
207
|
+
end
|
208
|
+
|
209
|
+
private_class_method :hex_to_components
|
210
|
+
|
211
|
+
# @private
|
212
|
+
|
213
|
+
def self.compile_color_compare_function(color_order)
|
214
|
+
order = parse_color_order(color_order)
|
215
|
+
|
216
|
+
if hsl_order?(color_order)
|
217
|
+
proc do |color1, color2|
|
218
|
+
compare_color_components(color1.hsl, color2.hsl, order)
|
219
|
+
end
|
220
|
+
else
|
221
|
+
proc do |color1, color2|
|
222
|
+
compare_color_components(color1.rgb, color2.rgb, order)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|