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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b10c6175394e05edd4ed0b06d2918ef04afbdcea95622e5750c775190e9ab1be
4
- data.tar.gz: c7624699d33612900f67e7c56a305c25c9e9383bc0b8018027e824f48232712f
3
+ metadata.gz: 7370c38d26fedfe8febdbde4adf0ac33839d79407f5241720805c2c2046dc1fe
4
+ data.tar.gz: ea6515cc1a86c2bdc5496c0887dee380c375013f4fcec864ca58fb91b3f7c4c5
5
5
  SHA512:
6
- metadata.gz: b09ec678085c9d58e157999e41cfdc6edfb6317dfeec283aa9736319897b387e0eca9d27fbe9344f5143b2b4e864e4c64e991144a72be276712856f13b41e8b2
7
- data.tar.gz: 2eb54559b2f4d05cee14c792a1362cb6678e5957e69a776ae87d5ea35f209b1f6ada3c27d4fc9ca3bef82700d57dadbfc8f461dc4d8b61fb00a45c31827c50fc
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 | 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` |
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, "0") if alpha < 1 or force_alpha
15
+ result += (alpha * 255).to_i.to_s(16).rjust(2, '0') if alpha < 1 || force_alpha
15
16
 
16
- return result
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, "0")
26
- result += green.to_s(16).rjust(2, "0")
27
- result += blue.to_s(16).rjust(2, "0")
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
- return result
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
- return "rgb(#{red}, #{green}, #{blue})"
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
- return "rgba(#{red}, #{green}, #{blue}, #{alpha.round(alpha_precision)})"
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
- return "hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%)"
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
- return "hsl(#{hue}, #{(saturation * 100).round}%, #{(lightness * 100).round}%, #{alpha.round(alpha_precision)})"
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
@@ -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
- return (min + max) / (2.0 * 255)
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 and red == blue and (red == 0 or red == 255)
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
- return lightness < 0.5 ? (max - min) / (max + min) : (max - min) / (2.0 - max - min)
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 == 0
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 = (red == max) ? (green - blue) / numerator :
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
- result = nil
95
- if saturation == 0
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
- return result
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 is_hex?(color_str)
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 is_rgb?(color_str)
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 is_rgba?(color_str)
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 is_hsl?(color_str)
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 is_hsla?(color_str)
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 ? "rgba" : "rgb"
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 is_hex?(input)
266
+ if hex?(input)
268
267
  # Parse the string as HEX color
269
268
  result = parse_hex(input)
270
- elsif is_rgb?(input)
269
+ elsif rgb?(input)
271
270
  # Parse the string as RGB color
272
271
  result = parse_rgb(input)
273
- elsif is_rgba?(input)
272
+ elsif rgba?(input)
274
273
  # Parse the string as RGBA color
275
274
  result = parse_rgb(input, is_rgba: true)
276
- elsif is_hsl?(input)
275
+ elsif hsl?(input)
277
276
  # Parse the string as HSL color
278
277
  result = parse_hsl(input)
279
- elsif is_hsla?(input)
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.new("Unknown color format: #{color_str.to_s.strip.inspect}") if result.nil?
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
- return result
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
- result = {
314
- red: Integer(input[0..1], 16),
332
+ {
333
+ red: Integer(input[0..1], 16),
315
334
  green: Integer(input[2..3], 16),
316
- blue: Integer(input[4..5], 16),
317
- alpha: 1.0
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)?\(|\)$)/, "").split(",")
348
+ components = color_str.gsub(/(^rgb(a)?\(|\)$)/, '').split(',')
337
349
 
338
- return {
339
- red: components.shift.to_i,
350
+ {
351
+ red: components.shift.to_i,
340
352
  green: components.shift.to_i,
341
- blue: components.shift.to_i,
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)?\(|\)$)/, "").split(",")
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?("%") ? saturation.to_f / 100 : saturation.to_f
370
+ saturation = saturation.include?('%') ? saturation.to_f / 100 : saturation.to_f
360
371
 
361
372
  lightness = components.shift
362
- lightness = lightness.include?("%") ? lightness.to_f / 100 : lightness.to_f
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
- return result
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 < 0
389
+ t += 1 if t.negative?
380
390
  t -= 1 if t > 1
381
391
 
382
- return p + (q - p) * 6 * t if t < 1.0 / 6.0
383
- return q if t < 1.0 / 2.0
384
- return p + (q - p) * (2/3 - t) * 6 if t < 2.0 / 3.0
385
- return p;
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
- @input = color_str.to_s.downcase.gsub(/\s+/, "")
19
+ input = color_str.to_s.downcase.gsub(/\s+/, '')
20
20
 
21
- Color.parse_color(@input).tap do |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 ==(color)
32
- self.red == color.red and
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.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
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.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
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.new("Invalid value: #{value.inspect}") if value.to_i < 0 or value.to_i > 255
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.new("Invalid value: #{value.inspect}") if value.to_f < 0 or value.to_f > 1
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 self.hex6
128
- when 'rgb' then self.rgb
129
- when 'rgba' then self.rgba
130
- when 'hsl' then self.hsl
131
- when 'hsla' then self.hsla
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
- self.hex
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 < 0 or @red > 255)
142
- invalid ||= (@green < 0 or @green > 255)
143
- invalid ||= (@blue < 0 or @blue > 255)
144
- invalid ||= (@alpha < 0 or @alpha > 1)
145
-
146
- if invalid
147
- raise ArgumentError.new "Invalid color: #{@input.inspect} " \
148
- "(R: #{@red.inspect}, G: #{@green.inspect}, B: #{@blue.inspect}, A: #{@alpha.inspect})"
149
- end
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
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Inker
2
4
  # Inker gem version
3
- VERSION = "0.1.0"
5
+ VERSION = '0.2.0'
4
6
  end
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.1.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: 2020-08-31 00:00:00.000000000 Z
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.17'
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.17'
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.0.8
115
+ rubygems_version: 3.3.7
116
116
  signing_key:
117
117
  specification_version: 4
118
118
  summary: A Ruby gem for color manipulation.