color_contrast_calc 0.1.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 +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
|