inker 0.1.0 → 0.2.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 +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.
|