inker 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +20 -16
- data/lib/inker/color/serializers.rb +13 -17
- data/lib/inker/color/tools.rb +132 -93
- data/lib/inker/color.rb +95 -36
- data/lib/inker/version.rb +3 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7370c38d26fedfe8febdbde4adf0ac33839d79407f5241720805c2c2046dc1fe
|
4
|
+
data.tar.gz: ea6515cc1a86c2bdc5496c0887dee380c375013f4fcec864ca58fb91b3f7c4c5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77fde4a81b7b6309f35689f9fb4fef1534de80a0bf6668d5707b07ab36803298f91e3aa7af9b0dd4bd3a34e302d562e459c2321a13209bcc78bedf2adb0af926
|
7
|
+
data.tar.gz: a1bee3f124ebfd3f0cfa606fe7274cdce7d8e08bfba1166f9a3b2c8e98d821eaab50bdb153cab252a5f3599eef1a2caa2a0ab56a89de3a39f3c65c3ec05bb684
|
data/README.md
CHANGED
@@ -95,22 +95,24 @@ Inker implements some useful features for getting useful color info an color man
|
|
95
95
|
|
96
96
|
### Instance methods
|
97
97
|
|
98
|
-
| Method name
|
99
|
-
|
100
|
-
| `#red`
|
101
|
-
| `#red=`
|
102
|
-
| `#green`
|
103
|
-
| `#green=`
|
104
|
-
| `#blue`
|
105
|
-
| `#blue=`
|
106
|
-
| `#alpha`
|
107
|
-
| `#alpha=`
|
108
|
-
| `#brightness`
|
109
|
-
| `#dark?`
|
110
|
-
| `#light?`
|
111
|
-
| `#lightness`
|
112
|
-
| `#saturation`
|
113
|
-
| `#hue`
|
98
|
+
| Method name | Description |
|
99
|
+
|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
|
100
|
+
| `#red` | Returns the value of red component in range `0-255` |
|
101
|
+
| `#red=` | Allows to set a new value for red component in range `0-255` |
|
102
|
+
| `#green` | Returns the value of green component in range `0-255` |
|
103
|
+
| `#green=` | Allows to set a new value for green component in range `0-255` |
|
104
|
+
| `#blue` | Returns the value of blue component in range `0-255` |
|
105
|
+
| `#blue=` | Allows to set a new value for blue component in range `0-255` |
|
106
|
+
| `#alpha` | Returns the value of alpha component in range `0.0-1.0` |
|
107
|
+
| `#alpha=` | Allows to set a new value for alpha component in range `0.0-1.0` |
|
108
|
+
| `#brightness` | Returns the brightness of the color in range `0-255` |
|
109
|
+
| `#dark?` | Returns a boolean (`true` when color is dark) |
|
110
|
+
| `#light?` | Returns a boolean (`true` when color is light) |
|
111
|
+
| `#lightness` | Returns the lightness of the color in range `0.0-1.0` |
|
112
|
+
| `#saturation` | Returns the saturation of the color in range `0.0-1.0` |
|
113
|
+
| `#hue` | Retursn the value of HUE component of the color in range `0-360` |
|
114
|
+
| `#contrast_ratio(other_color)` | Calculates the contrast ratio between current color end `other_color` [1-21]. A good contrast should return a value between `4.5` and `21`. |
|
115
|
+
| `#overlay(other_color)` | Calculates the result of the overlay between current color end `other_color` |
|
114
116
|
|
115
117
|
### Class methods
|
116
118
|
|
@@ -122,6 +124,8 @@ Inker implements some useful features for getting useful color info an color man
|
|
122
124
|
| `Inker::Color.from_hsla(Integer, Float, Float, Float)` | Same as `Inker::Color.from_hsl`, but has also the alpha component |
|
123
125
|
| `Inker::Color.from_custom_string(String, options)` | Generate a new `Inker::Color` from a custom string, by getting HEX characters from MD5 digest of input string. By setting `:position` option you can change the index of target HEX chars that are used for HEX color generation. (default `position: [0, 29, 14, 30, 28, 31]`) |
|
124
126
|
| `Inker::Color.random` | Generate a random `Inker::Color` instance |
|
127
|
+
| `Inker::Color.contrast_ratio(color1, color2)` | Calculates the contrast ratio between two colors and returns a value in range `[1-21]`. Colors could be specified as `Inker::Color` instances or strings. |
|
128
|
+
| `Inker::Color.overlay(color1, color)` | Calculates the result of the overlay between two colors and returns a new `Inker::Color` instance. |
|
125
129
|
|
126
130
|
## Development
|
127
131
|
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Inker
|
2
4
|
class Color
|
3
5
|
# This module implements the methods which can be used to serialize
|
4
6
|
# a color as string in different formats.
|
5
7
|
module Serializers
|
6
|
-
|
7
8
|
# Convert color to a HEX color string.
|
8
9
|
#
|
9
10
|
# @param force_alpha [Boolean] indicates if alpha channel should be included in HEX color string
|
@@ -11,59 +12,54 @@ module Inker
|
|
11
12
|
# @return [String] a HEX color string
|
12
13
|
def hex(force_alpha: false)
|
13
14
|
result = hex6
|
14
|
-
result += (alpha * 255).to_i.to_s(16).rjust(2,
|
15
|
+
result += (alpha * 255).to_i.to_s(16).rjust(2, '0') if alpha < 1 || force_alpha
|
15
16
|
|
16
|
-
|
17
|
+
result
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
20
|
# Convert color to a HEX color string without alpha channel.
|
21
21
|
#
|
22
22
|
# @return [String] a HEX color string without alpha channel
|
23
23
|
def hex6
|
24
24
|
result = "#"
|
25
|
-
result += red.to_s(16).rjust(2,
|
26
|
-
result += green.to_s(16).rjust(2,
|
27
|
-
result += blue.to_s(16).rjust(2,
|
25
|
+
result += red.to_s(16).rjust(2, '0')
|
26
|
+
result += green.to_s(16).rjust(2, '0')
|
27
|
+
result += blue.to_s(16).rjust(2, '0')
|
28
28
|
|
29
|
-
|
29
|
+
result
|
30
30
|
end
|
31
31
|
|
32
|
-
|
33
32
|
# Convert color to RGB color string.
|
34
33
|
#
|
35
34
|
# @return [String] a RGB color string
|
36
35
|
def rgb
|
37
|
-
|
36
|
+
"rgb(#{red}, #{green}, #{blue})"
|
38
37
|
end
|
39
38
|
|
40
|
-
|
41
39
|
# Convert color to RGBA color string.
|
42
40
|
#
|
43
41
|
# @param alpha_precision [Integer] indicates the precision of alpha value
|
44
42
|
#
|
45
43
|
# @return [String] a RGBA color string
|
46
44
|
def rgba(alpha_precision: 2)
|
47
|
-
|
45
|
+
"rgba(#{red}, #{green}, #{blue}, #{alpha.round(alpha_precision)})"
|
48
46
|
end
|
49
47
|
|
50
|
-
|
51
48
|
# Convert color to HSL color string.
|
52
49
|
#
|
53
50
|
# @return [String] a HSL color string
|
54
51
|
def hsl
|
55
|
-
|
52
|
+
"hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%)"
|
56
53
|
end
|
57
54
|
|
58
|
-
|
59
55
|
# Convert color to HSL color string.
|
60
56
|
#
|
61
57
|
# @param alpha_precision [Integer] indicates the precision of alpha value
|
62
58
|
#
|
63
59
|
# @return [String] a HSL color string
|
64
60
|
def hsla(alpha_precision: 2)
|
65
|
-
|
61
|
+
"hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%, #{alpha.round(alpha_precision)})"
|
66
62
|
end
|
67
63
|
end
|
68
64
|
end
|
69
|
-
end
|
65
|
+
end
|
data/lib/inker/color/tools.rb
CHANGED
@@ -1,22 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Inker
|
2
4
|
class Color
|
3
5
|
# Tools module implements a set of methods useful for color parsing and
|
4
6
|
# for getting useful info about color.
|
5
7
|
module Tools
|
6
8
|
# Regular expression for HEX colors matching
|
7
|
-
HEX_REGEX = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})
|
9
|
+
HEX_REGEX = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/.freeze
|
8
10
|
|
9
11
|
# Regular expression for RGB colors matching
|
10
|
-
RGB_REGEX = /^rgb\((\d+,){2}\d+\)
|
12
|
+
RGB_REGEX = /^rgb\((\d+,){2}\d+\)$/.freeze
|
11
13
|
|
12
14
|
# Regular expression for RGBA colors matching
|
13
|
-
RGBA_REGEX = /^rgba\((\d+,){3}\d+(.\d+)?\)
|
15
|
+
RGBA_REGEX = /^rgba\((\d+,){3}\d+(.\d+)?\)$/.freeze
|
14
16
|
|
15
17
|
# Regular expression for HSL colors matching
|
16
|
-
HSL_REGEX = /^hsl\((\d+)(,\d+%|,\d+(.\d+)){2}\)
|
18
|
+
HSL_REGEX = /^hsl\((\d+)(,\d+%|,\d+(.\d+)){2}\)$/.freeze
|
17
19
|
|
18
20
|
# Regular expression for HSLA colors matching
|
19
|
-
HSLA_REGEX = /^hsla\((\d+)(,\d+%|,\d+(.\d+)){2},\d+(.\d+)?\)
|
21
|
+
HSLA_REGEX = /^hsla\((\d+)(,\d+%|,\d+(.\d+)){2},\d+(.\d+)?\)$/.freeze
|
20
22
|
|
21
23
|
# Calculate the brightness of a color.
|
22
24
|
#
|
@@ -29,7 +31,6 @@ module Inker
|
|
29
31
|
Math.sqrt(0.299 * red**2 + 0.587 * green**2 + 0.114 * blue**2).round
|
30
32
|
end
|
31
33
|
|
32
|
-
|
33
34
|
# Calculate the lightness of a color from RGB components.
|
34
35
|
#
|
35
36
|
# @param red [Integer] the value of red component [0-255]
|
@@ -40,10 +41,9 @@ module Inker
|
|
40
41
|
def lightness(red, green, blue)
|
41
42
|
min, max = [red, green, blue].minmax
|
42
43
|
|
43
|
-
|
44
|
+
(min + max) / (2.0 * 255)
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
47
|
# Calculate the saturation of a color from RGB components.
|
48
48
|
#
|
49
49
|
# @param red [Integer] the value of red component [0-255]
|
@@ -53,15 +53,14 @@ module Inker
|
|
53
53
|
# @return [Float] a value in range 0.0-1.0 which indicates the saturation of the color
|
54
54
|
def saturation(red, green, blue)
|
55
55
|
# return 0 for black and white colors
|
56
|
-
return 0 if red == green
|
56
|
+
return 0 if red == green && red == blue && (red.zero? || red == 255)
|
57
57
|
|
58
58
|
lightness = lightness(red, green, blue)
|
59
59
|
min, max = [red / 255.0, green / 255.0, blue / 255.0].minmax
|
60
60
|
|
61
|
-
|
61
|
+
lightness < 0.5 ? (max - min) / (max + min) : (max - min) / (2.0 - max - min)
|
62
62
|
end
|
63
63
|
|
64
|
-
|
65
64
|
# Calculate the HUE value of a color from RGB components.
|
66
65
|
#
|
67
66
|
# @param red [Integer] the value of red component [0-255]
|
@@ -73,15 +72,34 @@ module Inker
|
|
73
72
|
min, max = [red, green, blue].minmax
|
74
73
|
|
75
74
|
numerator = (max - min).to_f
|
76
|
-
return 0 if numerator
|
75
|
+
return 0 if numerator.zero?
|
76
|
+
|
77
|
+
hue = case max
|
78
|
+
when red then (green - blue) / numerator
|
79
|
+
when green then 2 + (blue - red) / numerator
|
80
|
+
when blue then 4 + (red - green) / numerator
|
81
|
+
end
|
82
|
+
|
83
|
+
hue *= 60
|
77
84
|
|
78
|
-
hue
|
79
|
-
(green == max) ? 2 + (blue - red) / numerator :
|
80
|
-
4 + (red - green) / numerator
|
81
|
-
hue = hue * 60
|
82
|
-
return (hue < 0 ? hue + 360 : hue).round
|
85
|
+
(hue.negative? ? hue + 360 : hue).round
|
83
86
|
end
|
84
87
|
|
88
|
+
# Calculate the luminance of a color from RGB components.
|
89
|
+
#
|
90
|
+
# @param red [Integer] the value of red component [0-255]
|
91
|
+
# @param green [Integer] the value of green component [0-255]
|
92
|
+
# @param blue [Integer] the value of blue component [0-255]
|
93
|
+
#
|
94
|
+
# @return [Float] a value in range 0.0-1.0 which indicates the luminance of the color
|
95
|
+
def luminance(red, green, blue)
|
96
|
+
components = [red, green, blue].map do |c|
|
97
|
+
value = c / 255.0
|
98
|
+
value <= 0.03928 ? value / 12.92 : ((value + 0.055) / 1.055)**2.4
|
99
|
+
end
|
100
|
+
|
101
|
+
0.2126 * components[0] + 0.7152 * components[1] + 0.0722 * components[2]
|
102
|
+
end
|
85
103
|
|
86
104
|
# Get RGB values from a color in HSL format.
|
87
105
|
#
|
@@ -91,83 +109,70 @@ module Inker
|
|
91
109
|
#
|
92
110
|
# @return [Hash] a `Hash` which contains the values of RGB components
|
93
111
|
def hsl_to_rgb(hue, saturation, lightness)
|
94
|
-
|
95
|
-
|
96
|
-
# There's no saturation, so it's a gray scale color, which
|
97
|
-
# depends only on brightness
|
98
|
-
brightness = (lightness * 255).round
|
99
|
-
|
100
|
-
# All RGB components are equal to brightness
|
101
|
-
result = {
|
102
|
-
red: brightness,
|
103
|
-
green: brightness,
|
104
|
-
blue: brightness
|
105
|
-
}
|
106
|
-
else
|
107
|
-
q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation
|
108
|
-
p = 2 * lightness - q
|
109
|
-
norm_hue = hue / 360.0
|
110
|
-
|
111
|
-
result = {
|
112
|
-
red: (hue_to_rgb(p, q, norm_hue + 1.0/3.0) * 255).round,
|
113
|
-
green: (hue_to_rgb(p, q, norm_hue) * 255).round,
|
114
|
-
blue: (hue_to_rgb(p, q, norm_hue - 1.0/3.0) * 255).round
|
115
|
-
}
|
116
|
-
end
|
112
|
+
c, x, m = hue_convert_params(hue, saturation, lightness)
|
113
|
+
weights = hsl_hue_weights(hue, c, x)
|
117
114
|
|
118
|
-
|
115
|
+
{
|
116
|
+
red: ((weights[0] + m) * 255).round,
|
117
|
+
green: ((weights[1] + m) * 255).round,
|
118
|
+
blue: ((weights[2] + m) * 255).round
|
119
|
+
}
|
119
120
|
end
|
120
121
|
|
121
|
-
|
122
122
|
# Returns a `Boolean` which indicates if color is in HEX format
|
123
123
|
#
|
124
124
|
# @param color_str [String] a color string
|
125
125
|
#
|
126
126
|
# @return [Boolean] `true` when color is in HEX format
|
127
|
-
def
|
127
|
+
def hex?(color_str)
|
128
128
|
!!(color_str.to_s.downcase.strip =~ HEX_REGEX)
|
129
129
|
end
|
130
130
|
|
131
|
+
alias is_hex? hex?
|
131
132
|
|
132
133
|
# Returns a `Boolean` which indicates if color is in RGB format
|
133
134
|
#
|
134
135
|
# @param color_str [String] a color string
|
135
136
|
#
|
136
137
|
# @return [Boolean] `true` when color is in RGB format
|
137
|
-
def
|
138
|
+
def rgb?(color_str)
|
138
139
|
!!(color_str.to_s.downcase.gsub(/\s+/, '') =~ RGB_REGEX)
|
139
140
|
end
|
140
141
|
|
142
|
+
alias is_rgb? rgb?
|
141
143
|
|
142
144
|
# Returns a `Boolean` which indicates if color is in RGBA format
|
143
145
|
#
|
144
146
|
# @param color_str [String] a color string
|
145
147
|
#
|
146
148
|
# @return [Boolean] `true` when color is in RGBA format
|
147
|
-
def
|
149
|
+
def rgba?(color_str)
|
148
150
|
!!(color_str.to_s.downcase.gsub(/\s+/, '') =~ RGBA_REGEX)
|
149
151
|
end
|
150
152
|
|
153
|
+
alias is_rgba? rgba?
|
151
154
|
|
152
155
|
# Returns a `Boolean` which indicates if color is in HSL format
|
153
156
|
#
|
154
157
|
# @param color_str [String] a color string
|
155
158
|
#
|
156
159
|
# @return [Boolean] `true` when color is in HSL format
|
157
|
-
def
|
160
|
+
def hsl?(color_str)
|
158
161
|
!!(color_str.to_s.downcase.gsub(/\s+/, '') =~ HSL_REGEX)
|
159
162
|
end
|
160
163
|
|
164
|
+
alias is_hsl? hsl?
|
161
165
|
|
162
166
|
# Returns a `Boolean` which indicates if color is in HSLA format
|
163
167
|
#
|
164
168
|
# @param color_str [String] a color string
|
165
169
|
#
|
166
170
|
# @return [Boolean] `true` when color is in HSLA format
|
167
|
-
def
|
171
|
+
def hsla?(color_str)
|
168
172
|
!!(color_str.to_s.downcase.gsub(/\s+/, '') =~ HSLA_REGEX)
|
169
173
|
end
|
170
174
|
|
175
|
+
alias is_hsla? hsla?
|
171
176
|
|
172
177
|
# Generate a random `Inker::Color`.
|
173
178
|
#
|
@@ -175,14 +180,13 @@ module Inker
|
|
175
180
|
#
|
176
181
|
# @return [Inker::Color] a random color
|
177
182
|
def random(with_alpha: false)
|
178
|
-
prefix = with_alpha ?
|
183
|
+
prefix = with_alpha ? 'rgba' : 'rgb'
|
179
184
|
values = (1..3).map{ (rand * 255).round }
|
180
185
|
values << rand.round(2) if with_alpha
|
181
186
|
|
182
|
-
Inker.color("#{prefix}(#{values.join(
|
187
|
+
Inker.color("#{prefix}(#{values.join(',')})")
|
183
188
|
end
|
184
189
|
|
185
|
-
|
186
190
|
# A helper for `Inker::Color` generation from RGB components.
|
187
191
|
#
|
188
192
|
# @param red [Integer] the value of red component [0-255]
|
@@ -194,7 +198,6 @@ module Inker
|
|
194
198
|
Inker.color("rgb(#{red}, #{green}, #{blue})")
|
195
199
|
end
|
196
200
|
|
197
|
-
|
198
201
|
# A helper for `Inker::Color` generation from RGBA components.
|
199
202
|
#
|
200
203
|
# @param red [Integer] the value of red component [0-255]
|
@@ -207,7 +210,6 @@ module Inker
|
|
207
210
|
Inker.color("rgba(#{red}, #{green}, #{blue}, #{alpha})")
|
208
211
|
end
|
209
212
|
|
210
|
-
|
211
213
|
# A helper for `Inker::Color` generation from HSL components.
|
212
214
|
#
|
213
215
|
# @param hue [Integer] the value of HUE component [0-360]
|
@@ -219,7 +221,6 @@ module Inker
|
|
219
221
|
Inker.color("hsl(#{hue}, #{saturation}, #{lightness})")
|
220
222
|
end
|
221
223
|
|
222
|
-
|
223
224
|
# A helper for `Inker::Color` generation from HSLA components.
|
224
225
|
#
|
225
226
|
# @param hue [Integer] the value of HUE component [0-360]
|
@@ -232,7 +233,6 @@ module Inker
|
|
232
233
|
Inker.color("hsla(#{hue}, #{saturation}, #{lightness}, #{alpha})")
|
233
234
|
end
|
234
235
|
|
235
|
-
|
236
236
|
# Use MD5 digest of the string to get hex values from specified positions (by default `[0, 29, 14, 30, 28, 31]`)
|
237
237
|
# in order to obtain a color in HEX format which represents the specified string.
|
238
238
|
#
|
@@ -243,10 +243,9 @@ module Inker
|
|
243
243
|
# @return [Inker::Color] a `Inker::Color` object which represents the color associated to input string
|
244
244
|
def from_custom_string(custom_string, positions: [0, 29, 14, 30, 28, 31])
|
245
245
|
digest = Digest::MD5.hexdigest(custom_string.to_s)
|
246
|
-
Inker.color("##{positions.map{|p| digest[p]}.join}")
|
246
|
+
Inker.color("##{positions.map { |p| digest[p] }.join}")
|
247
247
|
end
|
248
248
|
|
249
|
-
|
250
249
|
# Parse a color string an return it's RGBA components as a hash.
|
251
250
|
#
|
252
251
|
# @example
|
@@ -264,19 +263,19 @@ module Inker
|
|
264
263
|
|
265
264
|
# Try to guess the format of color string and parse it by
|
266
265
|
# using the apropriate algorithm
|
267
|
-
if
|
266
|
+
if hex?(input)
|
268
267
|
# Parse the string as HEX color
|
269
268
|
result = parse_hex(input)
|
270
|
-
elsif
|
269
|
+
elsif rgb?(input)
|
271
270
|
# Parse the string as RGB color
|
272
271
|
result = parse_rgb(input)
|
273
|
-
elsif
|
272
|
+
elsif rgba?(input)
|
274
273
|
# Parse the string as RGBA color
|
275
274
|
result = parse_rgb(input, is_rgba: true)
|
276
|
-
elsif
|
275
|
+
elsif hsl?(input)
|
277
276
|
# Parse the string as HSL color
|
278
277
|
result = parse_hsl(input)
|
279
|
-
elsif
|
278
|
+
elsif hsla?(input)
|
280
279
|
# Parse the string as HSLA color
|
281
280
|
result = parse_hsl(input, is_hsla: true)
|
282
281
|
else
|
@@ -290,9 +289,29 @@ module Inker
|
|
290
289
|
end
|
291
290
|
|
292
291
|
# If we didn't have any match, throw an ArgumentError error
|
293
|
-
raise ArgumentError
|
292
|
+
raise ArgumentError, "Unknown color format: #{color_str.to_s.strip.inspect}" if result.nil?
|
293
|
+
|
294
|
+
result
|
295
|
+
end
|
296
|
+
|
297
|
+
# Calculates the contrast ratio between two colors.
|
298
|
+
#
|
299
|
+
# @param color1 [Inker::Color|String] the first color.
|
300
|
+
# @param color2 [Inker::Color|String] the second color.
|
301
|
+
#
|
302
|
+
# @return [Float] the contrast ratio between the two colors [1.0-21.0].
|
303
|
+
def contrast_ratio(color1, color2)
|
304
|
+
color1.to_color.contrast_ratio(color2)
|
305
|
+
end
|
294
306
|
|
295
|
-
|
307
|
+
# Calculates the result of overlaying two colors.
|
308
|
+
#
|
309
|
+
# @param color1 [Inker::Color|String] the first color.
|
310
|
+
# @param color2 [Inker::Color|String] the second color.
|
311
|
+
#
|
312
|
+
# @return [Inker::Color] the result of overlaying the two colors.
|
313
|
+
def overlay(color1, color2)
|
314
|
+
color1.to_color.overlay(color2)
|
296
315
|
end
|
297
316
|
|
298
317
|
private
|
@@ -307,25 +326,18 @@ module Inker
|
|
307
326
|
input = color_str.gsub(/^#/, '')
|
308
327
|
|
309
328
|
# Convert to HEX6 when color is in HEX3 format
|
310
|
-
input = input.chars.map{|x| x * 2 }.join if input.length == 3
|
329
|
+
input = input.chars.map { |x| x * 2 }.join if input.length == 3
|
311
330
|
|
312
331
|
# Get RGB components
|
313
|
-
|
314
|
-
red:
|
332
|
+
{
|
333
|
+
red: Integer(input[0..1], 16),
|
315
334
|
green: Integer(input[2..3], 16),
|
316
|
-
blue:
|
317
|
-
alpha
|
335
|
+
blue: Integer(input[4..5], 16),
|
336
|
+
# When color is in HEX8 format, get also alpha channel value
|
337
|
+
alpha: input.length == 8 ? Integer(input[6..7], 16) / 255.0 : 1.0
|
318
338
|
}
|
319
|
-
|
320
|
-
# When color is in HEX8 format, get also alpha channel value
|
321
|
-
if input.length == 8
|
322
|
-
result[:alpha] = Integer(input[6..7], 16) / 255.0
|
323
|
-
end
|
324
|
-
|
325
|
-
return result
|
326
339
|
end
|
327
340
|
|
328
|
-
|
329
341
|
# Parse color string as RGB(A) color.
|
330
342
|
#
|
331
343
|
# @param color_str [String] input RGB(A) color string
|
@@ -333,17 +345,16 @@ module Inker
|
|
333
345
|
#
|
334
346
|
# @return [Hash] a `Hash` which contains RGBA components of parsed color
|
335
347
|
def parse_rgb(color_str, is_rgba: false)
|
336
|
-
components = color_str.gsub(/(^rgb(a)?\(|\)$)/,
|
348
|
+
components = color_str.gsub(/(^rgb(a)?\(|\)$)/, '').split(',')
|
337
349
|
|
338
|
-
|
339
|
-
red:
|
350
|
+
{
|
351
|
+
red: components.shift.to_i,
|
340
352
|
green: components.shift.to_i,
|
341
|
-
blue:
|
353
|
+
blue: components.shift.to_i,
|
342
354
|
alpha: (is_rgba ? components.shift.to_f : 1.0)
|
343
355
|
}
|
344
356
|
end
|
345
357
|
|
346
|
-
|
347
358
|
# Parse color string as HSL(A) color.
|
348
359
|
#
|
349
360
|
# @param color_str [String] input HSL(A) color string
|
@@ -351,23 +362,22 @@ module Inker
|
|
351
362
|
#
|
352
363
|
# @return [Hash] a `Hash` which contains RGBA components of parsed color
|
353
364
|
def parse_hsl(color_str, is_hsla: false)
|
354
|
-
components = color_str.gsub(/(^hsl(a)?\(|\)$)/,
|
365
|
+
components = color_str.gsub(/(^hsl(a)?\(|\)$)/, '').split(',')
|
355
366
|
|
356
|
-
hue = components.shift.to_i
|
367
|
+
hue = components.shift.to_i % 360
|
357
368
|
|
358
369
|
saturation = components.shift
|
359
|
-
saturation = saturation.include?(
|
370
|
+
saturation = saturation.include?('%') ? saturation.to_f / 100 : saturation.to_f
|
360
371
|
|
361
372
|
lightness = components.shift
|
362
|
-
lightness = lightness.include?(
|
373
|
+
lightness = lightness.include?('%') ? lightness.to_f / 100 : lightness.to_f
|
363
374
|
|
364
375
|
result = hsl_to_rgb(hue, saturation, lightness)
|
365
376
|
result[:alpha] = is_hsla ? components.shift.to_f : 1.0
|
366
377
|
|
367
|
-
|
378
|
+
result
|
368
379
|
end
|
369
380
|
|
370
|
-
|
371
381
|
# A helper function which allows to calculate the RGB component value from HSL color.
|
372
382
|
#
|
373
383
|
# @param p [Float] `2 * lightness -q`
|
@@ -376,14 +386,43 @@ module Inker
|
|
376
386
|
#
|
377
387
|
# @return [Float] a value which represents a RGB component in range 0.0-1.0
|
378
388
|
def hue_to_rgb(p, q, t)
|
379
|
-
t += 1 if t
|
389
|
+
t += 1 if t.negative?
|
380
390
|
t -= 1 if t > 1
|
381
391
|
|
382
|
-
return p + (q - p) * 6 * t
|
383
|
-
return q
|
384
|
-
return p + (q - p) * (2/3 - t) * 6 if t < 2.0 / 3.0
|
385
|
-
|
392
|
+
return p + (q - p) * 6 * t if t < 1.0 / 6.0
|
393
|
+
return q if t < 1.0 / 2.0
|
394
|
+
return p + (q - p) * (2 / 3 - t) * 6 if t < 2.0 / 3.0
|
395
|
+
|
396
|
+
p
|
397
|
+
end
|
398
|
+
|
399
|
+
# Calculate HUE to RGB conversion params, with will be used to calculate RGB components in
|
400
|
+
# combination with the `hsl_hue_weights` function.
|
401
|
+
#
|
402
|
+
# @param hue [Integer] hue component of HSL color [0-359]
|
403
|
+
# @param saturation [Float] saturation component of HSL color [0.0-1.0]
|
404
|
+
# @param lightness [Float] lightness component of HSL color [0.0-1.0]
|
405
|
+
#
|
406
|
+
# @return [Array<Float>] an array which contains the `c`, `x` and `m` values.
|
407
|
+
def hue_convert_params(hue, saturation, lightness)
|
408
|
+
c = (1 - (2 * lightness - 1).abs) * saturation
|
409
|
+
x = c * (1 - ((hue / 60.0) % 2 - 1).abs)
|
410
|
+
m = lightness - c / 2.0
|
411
|
+
|
412
|
+
[c, x, m]
|
413
|
+
end
|
414
|
+
|
415
|
+
# A helper function
|
416
|
+
def hsl_hue_weights(hue, c, x)
|
417
|
+
case hue
|
418
|
+
when 0..59 then [c, x, 0]
|
419
|
+
when 60..119 then [x, c, 0]
|
420
|
+
when 120..179 then [0, c, x]
|
421
|
+
when 180..239 then [0, x, c]
|
422
|
+
when 240..299 then [x, 0, c]
|
423
|
+
when 300..359 then [c, 0, x]
|
424
|
+
end
|
386
425
|
end
|
387
426
|
end
|
388
427
|
end
|
389
|
-
end
|
428
|
+
end
|
data/lib/inker/color.rb
CHANGED
@@ -16,62 +16,87 @@ module Inker
|
|
16
16
|
#
|
17
17
|
# @param color_str [String] a color string
|
18
18
|
def initialize(color_str)
|
19
|
-
|
19
|
+
input = color_str.to_s.downcase.gsub(/\s+/, '')
|
20
20
|
|
21
|
-
Color.parse_color(
|
21
|
+
Color.parse_color(input).tap do |color|
|
22
22
|
@red = color[:red]
|
23
23
|
@green = color[:green]
|
24
24
|
@blue = color[:blue]
|
25
25
|
@alpha = color[:alpha]
|
26
26
|
end
|
27
27
|
|
28
|
-
validate_color!
|
28
|
+
validate_color!(input)
|
29
29
|
end
|
30
30
|
|
31
|
-
def ==(
|
32
|
-
|
33
|
-
self.green == color.green and
|
34
|
-
self.blue == color.blue and
|
35
|
-
self.alpha == color.alpha
|
31
|
+
def ==(other)
|
32
|
+
red == other.red && green == other.green && blue == other.blue && alpha == other.alpha
|
36
33
|
end
|
37
34
|
|
35
|
+
# Get RGBA color components.
|
36
|
+
# @return [Array<Integer>] an array of RGBA color components [red, green, blue, alpha].
|
37
|
+
def components
|
38
|
+
[red, green, blue, alpha]
|
39
|
+
end
|
40
|
+
|
41
|
+
alias to_a components
|
42
|
+
|
43
|
+
# Get a RGBA component by index (0 => red, 1 => green, 2 => blue, 3 => alpha).
|
44
|
+
def [](key)
|
45
|
+
components[key]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Set a RGBA component by index (0 => red, 1 => green, 2 => blue, 3 => alpha).
|
49
|
+
def []=(key, value)
|
50
|
+
component_name = %i[red green blue alpha][key]
|
51
|
+
send("#{component_name}=", value)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Iterate over RGBA color components.
|
55
|
+
def each(&block)
|
56
|
+
components.each(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Iterate over RGBA color components with index.
|
60
|
+
def each_with_index(&block)
|
61
|
+
components.each_with_index(&block)
|
62
|
+
end
|
38
63
|
|
39
64
|
# Set the value of red component.
|
40
65
|
#
|
41
66
|
# @param value [Integer] the value of red component [0-255]
|
42
67
|
def red=(value)
|
43
|
-
raise ArgumentError
|
68
|
+
raise ArgumentError, "Invalid value: #{value.inspect}" if value.to_i.negative? || value.to_i > 255
|
69
|
+
|
44
70
|
@red = value
|
45
71
|
end
|
46
72
|
|
47
|
-
|
48
73
|
# Set the value of green component.
|
49
74
|
#
|
50
75
|
# @param value [Integer] the value of green component [0-255]
|
51
76
|
def green=(value)
|
52
|
-
raise ArgumentError
|
77
|
+
raise ArgumentError, "Invalid value: #{value.inspect}" if value.to_i.negative? || value.to_i > 255
|
78
|
+
|
53
79
|
@green = value
|
54
80
|
end
|
55
81
|
|
56
|
-
|
57
82
|
# Set the value of blue component.
|
58
83
|
#
|
59
84
|
# @param value [Integer] the value of blue component [0-255]
|
60
85
|
def blue=(value)
|
61
|
-
raise ArgumentError
|
86
|
+
raise ArgumentError, "Invalid value: #{value.inspect}" if value.to_i.negative? || value.to_i > 255
|
87
|
+
|
62
88
|
@blue = value
|
63
89
|
end
|
64
90
|
|
65
|
-
|
66
91
|
# Set the value of alpha component.
|
67
92
|
#
|
68
93
|
# @param value [Float] the value of alpha component [0.0-1.0]
|
69
94
|
def alpha=(value)
|
70
|
-
raise ArgumentError
|
95
|
+
raise ArgumentError, "Invalid value: #{value.inspect}" if value.to_f.negative? || value.to_f > 1
|
96
|
+
|
71
97
|
@alpha = value
|
72
98
|
end
|
73
99
|
|
74
|
-
|
75
100
|
# Calculate the brightness of a color.
|
76
101
|
#
|
77
102
|
# @return [Integer] a value between 0-255 which indicates the brightness of the color
|
@@ -79,7 +104,6 @@ module Inker
|
|
79
104
|
Color.brightness(@red, @green, @blue)
|
80
105
|
end
|
81
106
|
|
82
|
-
|
83
107
|
# Calculate the lightness of a color.
|
84
108
|
#
|
85
109
|
# @return [Float] a value between 0.0-1.0 which indicates the lightness of the color
|
@@ -87,7 +111,6 @@ module Inker
|
|
87
111
|
Color.lightness(@red, @green, @blue)
|
88
112
|
end
|
89
113
|
|
90
|
-
|
91
114
|
# Calculate the saturation of a color.
|
92
115
|
#
|
93
116
|
# @return [Float] a value between 0.0-1.0 which indicates the saturation of the color
|
@@ -95,7 +118,6 @@ module Inker
|
|
95
118
|
Color.saturation(@red, @green, @blue)
|
96
119
|
end
|
97
120
|
|
98
|
-
|
99
121
|
# Calculate the HUE of a color.
|
100
122
|
#
|
101
123
|
# @return [Integer] a value between 0-360 which indicates the HUE of the color
|
@@ -116,6 +138,39 @@ module Inker
|
|
116
138
|
!dark?
|
117
139
|
end
|
118
140
|
|
141
|
+
# Returns the luminance of the color.
|
142
|
+
# @return [Float] a value between 0.0-1.0 which indicates the luminance of the color
|
143
|
+
def luminance
|
144
|
+
Color.luminance(@red, @green, @blue)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Calculates the result of 2 colors overlay.
|
148
|
+
# @return [Inker::Color] a new instance of {Inker::Color} which represents the result of the overlay operation.
|
149
|
+
def overlay(color)
|
150
|
+
result = dup
|
151
|
+
other = color.to_color
|
152
|
+
|
153
|
+
return result if alpha >= 1
|
154
|
+
|
155
|
+
alpha_weight = other.alpha * (1 - alpha)
|
156
|
+
|
157
|
+
# Calculate the result of the overlay operation.
|
158
|
+
3.times do |i|
|
159
|
+
result[i] = (self[i] * alpha + other[i] * alpha_weight).round
|
160
|
+
end
|
161
|
+
|
162
|
+
result.alpha = alpha + alpha_weight
|
163
|
+
|
164
|
+
result
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns the contrast ratio between two colors.
|
168
|
+
# @param [Float] a value between 0.0-21.0 which indicates the contrast ratio between two colors (higher is better).
|
169
|
+
def contrast_ratio(color)
|
170
|
+
l2, l1 = [luminance, color.to_color.luminance].minmax
|
171
|
+
|
172
|
+
(l1 + 0.05) / (l2 + 0.05)
|
173
|
+
end
|
119
174
|
|
120
175
|
# Convert color to string in the specified format.
|
121
176
|
#
|
@@ -124,29 +179,33 @@ module Inker
|
|
124
179
|
# @return [String] a string representation of the color
|
125
180
|
def to_s(format = 'hex')
|
126
181
|
case format.to_s.strip.downcase
|
127
|
-
when 'hex6' then
|
128
|
-
when 'rgb' then
|
129
|
-
when 'rgba' then
|
130
|
-
when 'hsl' then
|
131
|
-
when 'hsla' then
|
182
|
+
when 'hex6' then hex6
|
183
|
+
when 'rgb' then rgb
|
184
|
+
when 'rgba' then rgba
|
185
|
+
when 'hsl' then hsl
|
186
|
+
when 'hsla' then hsla
|
132
187
|
else
|
133
|
-
|
188
|
+
hex
|
134
189
|
end
|
135
190
|
end
|
136
191
|
|
192
|
+
def to_color
|
193
|
+
self
|
194
|
+
end
|
195
|
+
|
137
196
|
private
|
138
197
|
|
139
198
|
# Validates the values of the color.
|
140
|
-
def validate_color!
|
141
|
-
invalid = (@red
|
142
|
-
invalid ||= (@green
|
143
|
-
invalid ||= (@blue
|
144
|
-
invalid ||= (@alpha
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
199
|
+
def validate_color!(input)
|
200
|
+
invalid = (@red.negative? || @red > 255)
|
201
|
+
invalid ||= (@green.negative? || @green > 255)
|
202
|
+
invalid ||= (@blue.negative? || @blue > 255)
|
203
|
+
invalid ||= (@alpha.negative? || @alpha > 1)
|
204
|
+
|
205
|
+
return unless invalid
|
206
|
+
|
207
|
+
raise ArgumentError, "Invalid color: #{input.inspect} " \
|
208
|
+
"(R: #{@red.inspect}, G: #{@green.inspect}, B: #{@blue.inspect}, A: #{@alpha.inspect})"
|
150
209
|
end
|
151
210
|
end
|
152
|
-
end
|
211
|
+
end
|
data/lib/inker/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Groza Sergiu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1
|
19
|
+
version: '2.1'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1
|
26
|
+
version: '2.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,7 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
112
|
- !ruby/object:Gem::Version
|
113
113
|
version: '0'
|
114
114
|
requirements: []
|
115
|
-
rubygems_version: 3.
|
115
|
+
rubygems_version: 3.3.7
|
116
116
|
signing_key:
|
117
117
|
specification_version: 4
|
118
118
|
summary: A Ruby gem for color manipulation.
|