decolmor 1.0.0 → 1.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.
data/lib/decolmor/main.rb CHANGED
@@ -1,276 +1,302 @@
1
1
  module Decolmor
2
2
 
3
- #========= HEX <==> RGB(A) =============================================
4
-
5
- def self.hex_to_rgb(hex)
6
- rgb = hex.gsub('#','').scan(/../).map(&:hex).map(&:to_i)
7
- rgb.size == 4 ? rgb + [(rgb.delete_at(3) / 255.to_f).round(5)] : rgb
3
+ def self.included(base)
4
+ base.extend ClassMethods
8
5
  end
9
6
 
10
- def self.rgb_to_hex(rgb)
11
- template = rgb.size == 3 ? "#%02X%02X%02X" : "#%02X%02X%02X%02X"
12
- rgb = rgb[0..2] + [(rgb[3] * 255).round] if rgb.size == 4
13
- template % rgb
14
- end
7
+ #========= Set default rounding for HSL/HSV/HSB/CMYK conversion ========
15
8
 
16
- #=======================================================================
9
+ # round 1 enough for lossless conversion RGB -> HSL/HSV/HSB -> RGB
10
+ # for lossless conversion HSL <==> HSV (HSB) better to use round 2
11
+ #
12
+ HSX_ROUND = 1
17
13
 
18
- # simple generator RGB, you can set any channel(s)
19
- def self.new_rgb(red: nil, green: nil, blue: nil, alpha: nil)
20
- range = 0..255
21
- rgb = [red, green, blue].map { |channel| channel || rand(range) }
22
- alpha.nil? ? rgb : rgb + [alpha]
23
- end
14
+ module ClassMethods
24
15
 
25
- #========= RGB(A) to HSL/HSV/HSB =======================================
16
+ attr_writer :hsx_round
26
17
 
27
- def self.rgb_to_hsl(rgb_arr, rounding = hsx_round)
28
- # scaling RGB values into range 0..1
29
- red, green, blue, alpha = rgb_arr.map { |color| color / 255.to_f }
18
+ def hsx_round
19
+ @hsx_round ||= HSX_ROUND
20
+ end
30
21
 
31
- # calculation intermediate values
32
- cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
22
+ #========= HEX <==> RGB(A) =============================================
23
+
24
+ def hex_to_rgb(hex, alpha_round = 3, alpha_255: false)
25
+ hex = hex.gsub('#','')
26
+ hex = if [3, 4].include? hex.length
27
+ hex.chars.map{ |char| char * 2 }
28
+ else
29
+ hex.scan(/../)
30
+ end
31
+ rgb = hex.map(&:hex)
32
+ if rgb.size == 4
33
+ rgb[3] = (rgb[3] / 255.to_f).round(alpha_round) unless alpha_255
34
+ end
35
+
36
+ rgb
37
+ end
33
38
 
34
- # calculation HSL values
35
- hue = get_hue(red, green, blue)
36
- lightness = (cmax + cmin) / 2
37
- saturation = chroma == 0 ? 0 : chroma / (1 - (2 * lightness - 1).abs)
39
+ def rgb_to_hex(rgb, alpha_255: false)
40
+ if rgb.size == 3
41
+ "#%02X%02X%02X" % rgb
42
+ else
43
+ rgb[3] = (rgb[3] * 255).round unless alpha_255
44
+ "#%02X%02X%02X%02X" % rgb
45
+ end
46
+ end
38
47
 
39
- # scaling values to fill 0..100 interval
40
- saturation *= 100
41
- lightness *= 100
48
+ #=======================================================================
42
49
 
43
- # rounding, drop Alpha if not set (nil)
44
- hsl = [hue, saturation, lightness].map { |x| x.round(rounding) }
45
- alpha.nil? ? hsl : hsl + [alpha * 255]
46
- end
50
+ # simple generator RGB, you can set any channel(s)
51
+ def new_rgb(red: nil, green: nil, blue: nil, alpha: nil)
52
+ range = 0..255
53
+ rgb = [red, green, blue].map { |channel| channel || rand(range) }
54
+ alpha.nil? ? rgb : rgb + [alpha]
55
+ end
47
56
 
48
- def self.rgb_to_hsv(rgb_arr, rounding = hsx_round)
49
- # scaling RGB values into range 0..1
50
- red, green, blue, alpha = rgb_arr.map { |color| color / 255.to_f }
57
+ #========= RGB(A) to HSL/HSV/HSB =======================================
51
58
 
52
- # calculation intermediate values
53
- _cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
59
+ def rgb_to_hsl(rgb_arr, rounding = hsx_round)
60
+ # scaling RGB values into range 0..1
61
+ red, green, blue, alpha = rgb_arr.map { |color| color / 255.to_f }
54
62
 
55
- # calculation HSV values
56
- hue = get_hue(red, green, blue)
57
- saturation = chroma == 0 ? 0 : chroma / cmax
58
- value = cmax
63
+ # calculation intermediate values
64
+ cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
59
65
 
60
- # scaling values into range 0..100
61
- saturation *= 100
62
- value *= 100
66
+ # calculation HSL values
67
+ hue = get_hue(red, green, blue)
68
+ lightness = (cmax + cmin) / 2
69
+ saturation = chroma == 0 ? 0 : chroma / (1 - (2 * lightness - 1).abs)
63
70
 
64
- # rounding
65
- hsv = [hue, saturation, value].map { |x| x.round(rounding) }
66
- alpha.nil? ? hsv : hsv + [alpha * 255]
67
- end
71
+ # scaling values to fill 0..100 interval
72
+ saturation *= 100
73
+ lightness *= 100
68
74
 
75
+ # rounding, drop Alpha if not set (nil)
76
+ hsl = [hue, saturation, lightness].map { |x| x.round(rounding) }
77
+ alpha.nil? ? hsl : hsl + [alpha * 255]
78
+ end
69
79
 
70
- self.singleton_class.send(:alias_method, :rgb_to_hsb, :rgb_to_hsv)
80
+ def rgb_to_hsv(rgb_arr, rounding = hsx_round)
81
+ # scaling RGB values into range 0..1
82
+ red, green, blue, alpha = rgb_arr.map { |color| color / 255.to_f }
71
83
 
72
- #========= HSL/HSV/HSB to RGB(A) =======================================
84
+ # calculation intermediate values
85
+ _cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
73
86
 
74
- def self.hsl_to_rgb(hsl_arr)
75
- hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
76
- # scaling values into range 0..1
77
- saturation /= 100
78
- lightness /= 100
87
+ # calculation HSV values
88
+ hue = get_hue(red, green, blue)
89
+ saturation = chroma == 0 ? 0 : chroma / cmax
90
+ value = cmax
79
91
 
80
- # calculation intermediate values
81
- a = saturation * [lightness, 1 - lightness].min
82
- converter = proc do |n|
83
- k = (n + hue / 30) % 12
84
- lightness - a * [-1, [k - 3, 9 - k, 1].min].max
92
+ # scaling values into range 0..100
93
+ saturation *= 100
94
+ value *= 100
95
+
96
+ # rounding
97
+ hsv = [hue, saturation, value].map { |x| x.round(rounding) }
98
+ alpha.nil? ? hsv : hsv + [alpha * 255]
85
99
  end
86
100
 
87
- # calculation rgb & scaling into range 0..255
88
- rgb = [0, 8, 4]
89
- rgb.map! { |channel| (converter.call(channel) * 255).round }
90
- alpha.nil? ? rgb : rgb + [alpha]
91
- end
101
+ alias_method :rgb_to_hsb, :rgb_to_hsv
92
102
 
93
- def self.hsv_to_rgb(hsv_arr)
94
- hue, saturation, value, alpha = hsv_arr.map(&:to_f)
95
- # scaling values into range 0..1
96
- saturation /= 100
97
- value /= 100
103
+ #========= HSL/HSV/HSB to RGB(A) =======================================
98
104
 
99
- # calculation intermediate values
100
- converter = proc do |n|
101
- k = (n + hue / 60) % 6
102
- value - value * saturation * [0, [k, 4 - k, 1].min].max
103
- end
105
+ def hsl_to_rgb(hsl_arr)
106
+ hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
107
+ # scaling values into range 0..1
108
+ saturation /= 100
109
+ lightness /= 100
104
110
 
105
- # calculation rgb & scaling into range 0..255
106
- rgb = [5, 3, 1]
107
- rgb.map! { |channel| (converter.call(channel) * 255).round }
108
- alpha.nil? ? rgb : rgb + [alpha]
109
- end
111
+ # calculation intermediate values
112
+ a = saturation * [lightness, 1 - lightness].min
110
113
 
111
- self.singleton_class.send(:alias_method, :hsb_to_rgb, :hsv_to_rgb)
114
+ # calculation rgb & scaling into range 0..255
115
+ rgb = [0, 8, 4]
116
+ rgb.map! do |channel|
117
+ k = (channel + hue / 30) % 12
118
+ channel = lightness - a * [-1, [k - 3, 9 - k, 1].min].max
119
+ (channel * 255).round
120
+ end
121
+ alpha.nil? ? rgb : rgb + [alpha]
122
+ end
112
123
 
113
- #========= Alternative implementation HSL/HSV/HSB to RGB(A) ============
124
+ def hsv_to_rgb(hsv_arr)
125
+ hue, saturation, value, alpha = hsv_arr.map(&:to_f)
126
+ # scaling values into range 0..1
127
+ saturation /= 100
128
+ value /= 100
129
+
130
+ # calculation rgb & scaling into range 0..255
131
+ rgb = [5, 3, 1]
132
+ rgb.map! do |channel|
133
+ k = (channel + hue / 60) % 6
134
+ channel = value - value * saturation * [0, [k, 4 - k, 1].min].max
135
+ (channel * 255).round
136
+ end
137
+ alpha.nil? ? rgb : rgb + [alpha]
138
+ end
114
139
 
115
- def self.hsl_to_rgb_alt(hsl_arr)
116
- hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
117
- # scaling values into range 0..1
118
- saturation /= 100
119
- lightness /= 100
140
+ alias_method :hsb_to_rgb, :hsv_to_rgb
120
141
 
121
- # calculation chroma & intermediate values
122
- chroma = (1 - (2 * lightness - 1).abs) * saturation
123
- hue /= 60
124
- x = chroma * (1 - (hue % 2 - 1).abs)
142
+ #========= Alternative implementation HSL/HSV/HSB to RGB(A) ============
125
143
 
126
- # possible RGB points
127
- points = [[chroma, x, 0],
128
- [x, chroma, 0],
129
- [0, chroma, x],
130
- [0, x, chroma],
131
- [x, 0, chroma],
132
- [chroma, 0, x]]
133
- # point selection based on entering HUE input in range
134
- point = points.each_with_index.detect { |rgb_, n| (n..n + 1).include? hue }&.first
135
- # if point == nil (hue undefined)
136
- rgb = point || [0, 0, 0]
137
-
138
- # calculation rgb & scaling into range 0..255
139
- m = lightness - chroma / 2
140
- rgb.map! { |channel| ((channel + m) * 255).round }
141
- alpha.nil? ? rgb : rgb + [alpha]
142
- end
144
+ def hsl_to_rgb_alt(hsl_arr)
145
+ hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
146
+ # scaling values into range 0..1
147
+ saturation /= 100
148
+ lightness /= 100
143
149
 
144
- def self.hsv_to_rgb_alt(hsv_arr)
145
- hue, saturation, value, alpha = hsv_arr.map(&:to_f)
146
- # scaling values into range 0..1
147
- saturation /= 100
148
- value /= 100
150
+ # calculation chroma & intermediate values
151
+ chroma = (1 - (2 * lightness - 1).abs) * saturation
152
+ hue /= 60
153
+ x = chroma * (1 - (hue % 2 - 1).abs)
154
+ point = get_rgb_point(hue, chroma, x)
149
155
 
150
- # calculation chroma & intermediate values
151
- chroma = value * saturation
152
- hue /= 60
153
- x = chroma * (1 - (hue % 2 - 1).abs)
156
+ # calculation rgb & scaling into range 0..255
157
+ m = lightness - chroma / 2
158
+ rgb = point.map { |channel| ((channel + m) * 255).round }
159
+ alpha.nil? ? rgb : rgb + [alpha]
160
+ end
154
161
 
155
- # possible RGB points
156
- points = [[chroma, x, 0],
157
- [x, chroma, 0],
158
- [0, chroma, x],
159
- [0, x, chroma],
160
- [x, 0, chroma],
161
- [chroma, 0, x]]
162
- # point selection based on entering HUE input in range
163
- point = points.each_with_index.detect { |rgb_, n| (n * (1 / 100.000)...n + 1).include? hue }&.first
164
- # if point == nil (hue undefined)
165
- rgb = point || [0, 0, 0]
166
-
167
- # calculation rgb & scaling into range 0..255
168
- m = value - chroma
169
- rgb.map! { |channel| ((channel + m) * 255).round }
170
- alpha.nil? ? rgb : rgb + [alpha]
171
- end
162
+ def hsv_to_rgb_alt(hsv_arr)
163
+ hue, saturation, value, alpha = hsv_arr.map(&:to_f)
164
+ # scaling values into range 0..1
165
+ saturation /= 100
166
+ value /= 100
167
+
168
+ # calculation chroma & intermediate values
169
+ chroma = value * saturation
170
+ hue /= 60
171
+ x = chroma * (1 - (hue % 2 - 1).abs)
172
+ point = get_rgb_point(hue, chroma, x)
173
+
174
+ # calculation rgb & scaling into range 0..255
175
+ m = value - chroma
176
+ rgb = point.map { |channel| ((channel + m) * 255).round }
177
+ alpha.nil? ? rgb : rgb + [alpha]
178
+ end
172
179
 
173
- self.singleton_class.send(:alias_method, :hsb_to_rgb_alt, :hsv_to_rgb_alt)
180
+ alias_method :hsb_to_rgb_alt, :hsv_to_rgb_alt
174
181
 
175
- #========= HSL <==> HSV (HSB) ==========================================
182
+ #========= HSL <==> HSV (HSB) ==========================================
176
183
 
177
- def self.hsl_to_hsv(hsl_arr, rounding = hsx_round)
178
- hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
179
- # scaling values into range 0..1
180
- saturation /= 100
181
- lightness /= 100
184
+ def hsl_to_hsv(hsl_arr, rounding = hsx_round)
185
+ hue, saturation, lightness, alpha = hsl_arr.map(&:to_f)
186
+ # scaling values into range 0..1
187
+ saturation /= 100
188
+ lightness /= 100
182
189
 
183
- # calculation value & saturation HSV
184
- value = lightness + saturation * [lightness, 1 - lightness].min
185
- saturation_hsv = lightness == 0 ? 0 : 2 * (1 - lightness / value)
190
+ # calculation value & saturation HSV
191
+ value = lightness + saturation * [lightness, 1 - lightness].min
192
+ saturation_hsv = lightness == 0 ? 0 : 2 * (1 - lightness / value)
186
193
 
187
- # scaling HSV values & rounding
188
- hsv = [hue, saturation_hsv * 100, value * 100].map { |x| x.round(rounding) }
189
- alpha.nil? ? hsv : hsv + [alpha]
190
- end
194
+ # scaling HSV values & rounding
195
+ hsv = [hue, saturation_hsv * 100, value * 100].map { |x| x.round(rounding) }
196
+ alpha.nil? ? hsv : hsv + [alpha]
197
+ end
191
198
 
192
- self.singleton_class.send(:alias_method, :hsl_to_hsb, :hsl_to_hsv)
193
-
194
- def self.hsv_to_hsl(hsv_arr, rounding = hsx_round)
195
- hue, saturation, value, alpha = hsv_arr.map(&:to_f)
196
- # scaling values into range 0..1
197
- saturation /= 100
198
- value /= 100
199
-
200
- # calculation lightness & saturation HSL
201
- lightness = value * (1 - saturation / 2)
202
- saturation_hsl = if [0, 1].any? { |v| v == lightness }
203
- 0
204
- else
205
- (value - lightness) / [lightness, 1 - lightness].min
206
- end
207
-
208
- # scaling HSL values & rounding
209
- hsl = [hue, saturation_hsl * 100, lightness * 100].map { |x| x.round(rounding) }
210
- alpha.nil? ? hsl : hsl + [alpha]
211
- end
199
+ alias_method :hsl_to_hsb, :hsl_to_hsv
200
+
201
+ def hsv_to_hsl(hsv_arr, rounding = hsx_round)
202
+ hue, saturation, value, alpha = hsv_arr.map(&:to_f)
203
+ # scaling values into range 0..1
204
+ saturation /= 100
205
+ value /= 100
206
+
207
+ # calculation lightness & saturation HSL
208
+ lightness = value * (1 - saturation / 2)
209
+ saturation_hsl = if [0, 1].any? { |v| v == lightness }
210
+ 0
211
+ else
212
+ (value - lightness) / [lightness, 1 - lightness].min
213
+ end
214
+
215
+ # scaling HSL values & rounding
216
+ hsl = [hue, saturation_hsl * 100, lightness * 100].map { |x| x.round(rounding) }
217
+ alpha.nil? ? hsl : hsl + [alpha]
218
+ end
219
+
220
+ alias_method :hsb_to_hsl, :hsv_to_hsl
212
221
 
213
- self.singleton_class.send(:alias_method, :hsb_to_hsl, :hsv_to_hsl)
222
+ #========= RGB(A) <==> CMYK ============================================
214
223
 
215
- #========= RGB(A) <==> CMYK ============================================
224
+ def rgb_to_cmyk(rgb_arr, rounding = hsx_round)
225
+ # scaling RGB values into range 0..1
226
+ rgb = rgb_arr[0..2].map { |color| color / 255.to_f }
227
+ k = 1 - rgb.max
228
+ converter = proc do |color|
229
+ (1 - k) == 0 ? 0 : (1 - color - k) / (1 - k)
230
+ end
216
231
 
217
- def self.rgb_to_cmyk(rgb_arr, rounding = hsx_round)
218
- # scaling RGB values into range 0..1
219
- rgb = rgb_arr[0..2].map { |color| color / 255.to_f }
220
- k = 1 - rgb.max
221
- converter = proc do |color|
222
- (1 - k) == 0 ? 0 : (1 - color - k) / (1 - k)
232
+ # calculation CMYK & scaling into percentages & rounding
233
+ c, m, y = rgb.map { |color| converter.call(color) || 0 }
234
+ cmyk = [c, m, y, k].map { |x| (x * 100).round(rounding) }
235
+ rgb_arr.size == 4 ? cmyk + [rgb_arr.last] : cmyk
223
236
  end
224
237
 
225
- # calculation CMYK & scaling into percentages & rounding
226
- c, m, y = rgb.map { |color| converter.call(color) || 0 }
227
- cmyk = [c, m, y, k].map { |x| (x * 100).round(rounding) }
228
- rgb_arr.size == 4 ? cmyk + [rgb_arr.last] : cmyk
229
- end
238
+ def cmyk_to_rgb(cmyk_arr)
239
+ c, m, y, k = cmyk_arr[0..3].map { |color| color / 100.to_f }
240
+ converter = proc do |channel|
241
+ 255 * (1 - channel) * (1 - k)
242
+ end
230
243
 
231
- def self.cmyk_to_rgb(cmyk_arr)
232
- c, m, y, k = cmyk_arr[0..3].map { |color| color / 100.to_f }
233
- converter = proc do |channel|
234
- 255 * (1 - channel) * (1 - k)
244
+ # calculation RGB & rounding
245
+ rgb = [c, m, y].map { |channel| converter.call(channel).round }
246
+ cmyk_arr.size == 5 ? rgb + [cmyk_arr.last] : rgb
235
247
  end
236
248
 
237
- # calculation RGB & rounding
238
- rgb = [c, m, y].map { |channel| converter.call(channel).round }
239
- cmyk_arr.size == 5 ? rgb + [cmyk_arr.last] : rgb
240
- end
249
+ private
241
250
 
242
- private
251
+ #========= helper methods for RGB to HSL/HSB/HSV =======================
243
252
 
244
- #========= helper methods for RGB to HSL/HSB/HSV =======================
253
+ # find greatest and smallest channel values and chroma from RGB
254
+ def get_min_max_chroma(red, green, blue)
255
+ cmin = [red, green, blue].min
256
+ cmax = [red, green, blue].max
257
+ # calculation chroma
258
+ chroma = cmax - cmin
245
259
 
246
- # find greatest and smallest channel values and chroma from RGB
247
- def self.get_min_max_chroma(red, green, blue)
248
- cmin = [red, green, blue].min
249
- cmax = [red, green, blue].max
250
- # calculation chroma
251
- chroma = cmax - cmin
260
+ [cmin, cmax, chroma]
261
+ end
252
262
 
253
- [cmin, cmax, chroma]
254
- end
263
+ # calculation HUE from RGB
264
+ def get_hue(red, green, blue)
265
+ _cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
266
+
267
+ hue = if chroma == 0
268
+ 0
269
+ elsif cmax == red
270
+ # red is max
271
+ ((green - blue) / chroma) % 6
272
+ elsif cmax == green
273
+ # green is max
274
+ (blue - red) / chroma + 2
275
+ else
276
+ # blue is max
277
+ (red - green) / chroma + 4
278
+ end
279
+ hue * 60
280
+
281
+ # HUE will never leave the 0..360 range when RGB is within 0..255
282
+ # make negative HUEs positive
283
+ # 0 <= hue ? hue : hue + 360
284
+ end
255
285
 
256
- # calculation HUE from RGB
257
- def self.get_hue(red, green, blue)
258
- _cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
259
-
260
- hue = if chroma == 0
261
- 0
262
- elsif cmax == red
263
- # red is max
264
- ((green - blue) / chroma) % 6
265
- elsif cmax == green
266
- # green is max
267
- (blue - red) / chroma + 2
268
- else
269
- # blue is max
270
- (red - green) / chroma + 4
271
- end
272
- hue *= 60
273
- # make negative HUEs positive behind 360°
274
- 0 <= hue ? hue : hue + 360
286
+ # possible RGB points
287
+ # point selection based on entering HUE input in range
288
+ def get_rgb_point(hue, chroma, x)
289
+ case hue
290
+ when 0...1 then [chroma, x, 0]
291
+ when 1...2 then [x, chroma, 0]
292
+ when 2...3 then [0, chroma, x]
293
+ when 3...4 then [0, x, chroma]
294
+ when 4...5 then [x, 0, chroma]
295
+ when 5...6 then [chroma, 0, x]
296
+ else [0, 0, 0]
297
+ end
298
+ end
275
299
  end
300
+
301
+ extend ClassMethods
276
302
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Decolmor
4
- VERSION = '1.0.0'
4
+ VERSION = '1.2.0'
5
5
  end
data/lib/decolmor.rb CHANGED
@@ -2,18 +2,4 @@ require 'decolmor/main'
2
2
  require 'decolmor/version'
3
3
 
4
4
  module Decolmor
5
- #========= Set default rounding for HSL/HSV/HSB/CMYK conversion ========
6
-
7
- # round 1 enough for lossless conversion RGB -> HSL/HSV/HSB -> RGB
8
- # for lossless conversion HSL <==> HSV (HSB) better to use round 2
9
- #
10
- HSX_ROUND = 1
11
-
12
- class << self
13
- attr_writer :hsx_round
14
-
15
- def hsx_round
16
- @hsx_round ||= HSX_ROUND
17
- end
18
- end
19
5
  end