decolmor 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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