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 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.