decolmor 1.0.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 +7 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/NEWS.md +4 -0
- data/README.md +133 -0
- data/Rakefile +14 -0
- data/decolmor.gemspec +40 -0
- data/lib/decolmor/main.rb +276 -0
- data/lib/decolmor/version.rb +5 -0
- data/lib/decolmor.rb +19 -0
- data/spec/decolmor_spec.rb +420 -0
- data/spec/factories/alpha.rb +15 -0
- data/spec/factories/colors.rb +79 -0
- data/spec/spec_helper.rb +53 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1633188a81e5b270b4e7ba878d9b72df23a864ea98e299428cef3cfb0948b906
|
4
|
+
data.tar.gz: 5db8fee23dd5652b66943a715058ae04ea47bdbaaf3ceeed6711407e23fc5fff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b9cf4ffd31d01c26bd2d78337ef316bcb81bd7ad06bc7471a5ccc4738e90103a6443008517a289889a0e0c9cc94e6e0387b188e08508f4ae01f312cf90c5ecd2
|
7
|
+
data.tar.gz: 602694cadb39dbef406d503ca75e632a650e571710a30a1f1a7935b8aec425b6583e03148f1bc5a07f8128cd63e6b478b0bea5b442f9be1a7a34bd8833ce391d
|
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 ChildrenofkoRn
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NEWS.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Decolmor
|
2
|
+
[](http://badge.fury.io/rb/decolmor)
|
3
|
+
[](https://app.travis-ci.com/ChildrenofkoRn/decolmor)
|
4
|
+
[](https://codecov.io/gh/ChildrenofkoRn/decolmor)
|
5
|
+
|
6
|
+
Gem for converting the color spaces from/to: HEX/RGB/HSL/HSV/HSB/CMYK
|
7
|
+
The Alpha channel (transparency) is supported.
|
8
|
+
There is also a simple RGB generator.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
Add the following line to Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'decolmor'
|
15
|
+
```
|
16
|
+
and run `bundle install` from your shell.
|
17
|
+
|
18
|
+
To install the gem manually from your shell, run:
|
19
|
+
|
20
|
+
```shell
|
21
|
+
gem install decolmor
|
22
|
+
```
|
23
|
+
### Supported Rubies
|
24
|
+
- 2.4
|
25
|
+
- 2.5
|
26
|
+
- 2.6
|
27
|
+
- 2.7
|
28
|
+
- 3.0
|
29
|
+
|
30
|
+
## Using
|
31
|
+
```ruby
|
32
|
+
require 'decolmor'
|
33
|
+
|
34
|
+
rgb = [29, 128, 86]
|
35
|
+
Decolmor::rgb_to_hsb(rgb)
|
36
|
+
=> [154.5, 77.3, 50.2]
|
37
|
+
```
|
38
|
+
## Rounding for HSL/HSV/HSB/CMYK
|
39
|
+
By default, rounding 1 is used to convert to HSL/HSV/HSB/CMYK.
|
40
|
+
This is enough to loselessly convert RGB -> HSL/HSV/HSB/CMYK -> RGB:
|
41
|
+
```ruby
|
42
|
+
rgb = [224, 23, 131]
|
43
|
+
hsl = Decolmor::rgb_to_hsl(rgb) # => [327.8, 81.4, 48.4]
|
44
|
+
hsv = Decolmor::rgb_to_hsv(rgb) # => [327.8, 89.7, 87.8]
|
45
|
+
Decolmor::hsv_to_rgb(hsv) # => [224, 23, 131]
|
46
|
+
Decolmor::hsl_to_rgb(hsl) # => [224, 23, 131]
|
47
|
+
```
|
48
|
+
If you convert between HSL <==> HSV (HSB) with a rounding of 2, you can get more accurate results.
|
49
|
+
You can change rounding globally:
|
50
|
+
```ruby
|
51
|
+
Decolmor::hsx_round = 2
|
52
|
+
Decolmor::rgb_to_hsl(rgb) # => [154.55, 63.06, 30.78]
|
53
|
+
Decolmor::hsx_round # => 2
|
54
|
+
```
|
55
|
+
You can also specify rounding as a second argument when calling the method:
|
56
|
+
```ruby
|
57
|
+
Decolmor::rgb_to_hsl(rgb, 3) # => [154.545, 63.057, 30.784]
|
58
|
+
```
|
59
|
+
In this case, the global rounding will be ignored.
|
60
|
+
If you need to get integers, use 0.
|
61
|
+
|
62
|
+
## Alpha channel
|
63
|
+
When converting from HEX to RGBA Alpha channel is converted to a value from the range `0..1` with rounding 5:
|
64
|
+
```ruby
|
65
|
+
Decolmor::hex_to_rgb('#19988BB8') # => [25, 152, 139, 0.72157]
|
66
|
+
```
|
67
|
+
Consequently, when converting to HEX from RGBA, Alpha from the range `0..1` is assumed.
|
68
|
+
In other cases (conversions between RGB/HSL/HSV/HSB/CMYK) Alpha channel remains unchanged.
|
69
|
+
|
70
|
+
## HSV or HSB
|
71
|
+
HSB is an alternative name for HSV, it is the same thing.
|
72
|
+
However, for convenience, aliasing methods are made for HSB from HSV.
|
73
|
+
```ruby
|
74
|
+
rgb = [255, 109, 55]
|
75
|
+
Decolmor::rgb_to_hsv(rgb) # => [16.2, 78.4, 100.0]
|
76
|
+
Decolmor::rgb_to_hsb(rgb) # => [16.2, 78.4, 100.0]
|
77
|
+
```
|
78
|
+
## HSL/HSV/HSB to RGB conversion
|
79
|
+
HSL/HSV/HSB to RGB conversion has two implementations, the gem includes both:
|
80
|
+
- hsl_to_rgb
|
81
|
+
- hsv_to_rgb
|
82
|
+
- hsb_to_rgb
|
83
|
+
|
84
|
+
or
|
85
|
+
- hsl_to_rgb_alt
|
86
|
+
- hsv_to_rgb_alt
|
87
|
+
- hsb_to_rgb_alt
|
88
|
+
|
89
|
+
The results of the two implementations are identical, but the alternative versions (postfix `_alt`) are ~1.6X slower.
|
90
|
+
|
91
|
+
## Attention for CMYK !
|
92
|
+
Unfortunately, there is no simple formula for linear RGB to/from CMYK conversion.
|
93
|
+
This implementation is a simplified/dirty/simulation.
|
94
|
+
CMYK is used for printing and the correct conversion will be non-linear, based on the color profile for the particular printing device.
|
95
|
+
Therefore, the CMYK conversion results will not match Adobe products.
|
96
|
+
**BUT:**
|
97
|
+
Conversion to HEX/RGB/HSL/HSV/HSB is simple and is described by formulas.
|
98
|
+
Read more: https://en.wikipedia.org/wiki/HSL_and_HSV
|
99
|
+
The results when rounded to an integer will be the same as when using graphics editors, such as CorelDRAW or Adobe Photoshop.
|
100
|
+
|
101
|
+
## Supported Methods
|
102
|
+
- Setter global rounding for conversion to HSL/HSV/HSB/CMYK
|
103
|
+
- Decolmor::hsx_round =
|
104
|
+
- HEX <==> RGB(A)
|
105
|
+
- hex_to_rgb
|
106
|
+
- rgb_to_hex
|
107
|
+
- Simple generator RGB, you can set any channel(s)
|
108
|
+
- new_rgb
|
109
|
+
- RGB(A) to HSL/HSV/HSB
|
110
|
+
- rgb_to_hsl
|
111
|
+
- rgb_to_hsv
|
112
|
+
- rgb_to_hsb
|
113
|
+
- HSL/HSV/HSB to RGB(A)
|
114
|
+
- hsl_to_rgb
|
115
|
+
- hsv_to_rgb
|
116
|
+
- hsb_to_rgb
|
117
|
+
- Alternative implementation HSL/HSV/HSB to RGB(A) (~1.6X slower)
|
118
|
+
- hsl_to_rgb_alt
|
119
|
+
- hsv_to_rgb_alt
|
120
|
+
- hsb_to_rgb_alt
|
121
|
+
- HSL <==> HSV (HSB)
|
122
|
+
- hsl_to_hsv
|
123
|
+
- hsl_to_hsb
|
124
|
+
- hsv_to_hsl
|
125
|
+
- hsb_to_hsl
|
126
|
+
- RGB(A) <==> CMYK
|
127
|
+
- rgb_to_cmyk
|
128
|
+
- cmyk_to_rgb
|
129
|
+
|
130
|
+
## License
|
131
|
+
MIT License
|
132
|
+
Copyright (c) 2021 ChildrenofkoRn
|
133
|
+
[LICENSE](https://github.com/ChildrenofkoRn/decolmor/blob/master/LICENSE)
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'bundler/setup'
|
6
|
+
require "bundler/gem_tasks"
|
7
|
+
rescue LoadError
|
8
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rspec/core/rake_task'
|
12
|
+
RSpec::Core::RakeTask.new(:spec)
|
13
|
+
|
14
|
+
task default: :spec
|
data/decolmor.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('lib/decolmor/version', __dir__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'decolmor'
|
5
|
+
spec.version = Decolmor::VERSION
|
6
|
+
spec.licenses = ['MIT']
|
7
|
+
spec.summary = "Converter color spaces from/to: HEX/RGB/HSL/HSV/HSB/CMYK"
|
8
|
+
spec.description = "Gem for converting color spaces from/to: HEX/RGB/HSL/HSV/HSB/CMYK\n" \
|
9
|
+
"The Alpha channel (transparency) is supported.\n" \
|
10
|
+
"There is also a simple RGB generator."
|
11
|
+
spec.authors = ["ChildrenofkoRn"]
|
12
|
+
spec.email = 'Rick-ROR@ya.ru'
|
13
|
+
spec.homepage = 'https://github.com/ChildrenofkoRn/decolmor'
|
14
|
+
spec.require_paths = ['lib']
|
15
|
+
spec.files = Dir['lib/**/*'] +
|
16
|
+
%w(README.md CHANGELOG.md NEWS.md LICENSE
|
17
|
+
decolmor.gemspec Gemfile Rakefile)
|
18
|
+
spec.test_files = Dir['spec/**/*'] + ['.rspec']
|
19
|
+
spec.extra_rdoc_files = %w(README.md LICENSE)
|
20
|
+
|
21
|
+
spec.platform = Gem::Platform::RUBY
|
22
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
|
23
|
+
|
24
|
+
if spec.respond_to?(:metadata)
|
25
|
+
spec.metadata = {
|
26
|
+
"homepage_uri" => spec.homepage.to_s,
|
27
|
+
"news_uri" => "#{spec.homepage}/blob/master/NEWS.md",
|
28
|
+
"changelog_uri" => "#{spec.homepage}/blob/master/CHANGELOG.md",
|
29
|
+
"documentation_uri" => "#{spec.homepage}/blob/master/README.md",
|
30
|
+
"bug_tracker_uri" => "#{spec.homepage}/issues",
|
31
|
+
"source_code_uri" => spec.homepage.to_s
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
spec.add_development_dependency 'bundler', '>= 1.17', '< 3.0'
|
36
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
37
|
+
spec.add_development_dependency 'codecov', '~> 0.2'
|
38
|
+
spec.add_development_dependency 'rspec', '~> 3.8'
|
39
|
+
spec.add_development_dependency 'factory_bot', '>= 5.1', '< 7.0'
|
40
|
+
end
|
@@ -0,0 +1,276 @@
|
|
1
|
+
module Decolmor
|
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
|
8
|
+
end
|
9
|
+
|
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
|
15
|
+
|
16
|
+
#=======================================================================
|
17
|
+
|
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
|
24
|
+
|
25
|
+
#========= RGB(A) to HSL/HSV/HSB =======================================
|
26
|
+
|
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 }
|
30
|
+
|
31
|
+
# calculation intermediate values
|
32
|
+
cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
|
33
|
+
|
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)
|
38
|
+
|
39
|
+
# scaling values to fill 0..100 interval
|
40
|
+
saturation *= 100
|
41
|
+
lightness *= 100
|
42
|
+
|
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
|
47
|
+
|
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 }
|
51
|
+
|
52
|
+
# calculation intermediate values
|
53
|
+
_cmin, cmax, chroma = get_min_max_chroma(red, green, blue)
|
54
|
+
|
55
|
+
# calculation HSV values
|
56
|
+
hue = get_hue(red, green, blue)
|
57
|
+
saturation = chroma == 0 ? 0 : chroma / cmax
|
58
|
+
value = cmax
|
59
|
+
|
60
|
+
# scaling values into range 0..100
|
61
|
+
saturation *= 100
|
62
|
+
value *= 100
|
63
|
+
|
64
|
+
# rounding
|
65
|
+
hsv = [hue, saturation, value].map { |x| x.round(rounding) }
|
66
|
+
alpha.nil? ? hsv : hsv + [alpha * 255]
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
self.singleton_class.send(:alias_method, :rgb_to_hsb, :rgb_to_hsv)
|
71
|
+
|
72
|
+
#========= HSL/HSV/HSB to RGB(A) =======================================
|
73
|
+
|
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
|
79
|
+
|
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
|
85
|
+
end
|
86
|
+
|
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
|
92
|
+
|
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
|
98
|
+
|
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
|
104
|
+
|
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
|
110
|
+
|
111
|
+
self.singleton_class.send(:alias_method, :hsb_to_rgb, :hsv_to_rgb)
|
112
|
+
|
113
|
+
#========= Alternative implementation HSL/HSV/HSB to RGB(A) ============
|
114
|
+
|
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
|
120
|
+
|
121
|
+
# calculation chroma & intermediate values
|
122
|
+
chroma = (1 - (2 * lightness - 1).abs) * saturation
|
123
|
+
hue /= 60
|
124
|
+
x = chroma * (1 - (hue % 2 - 1).abs)
|
125
|
+
|
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
|
143
|
+
|
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
|
149
|
+
|
150
|
+
# calculation chroma & intermediate values
|
151
|
+
chroma = value * saturation
|
152
|
+
hue /= 60
|
153
|
+
x = chroma * (1 - (hue % 2 - 1).abs)
|
154
|
+
|
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
|
172
|
+
|
173
|
+
self.singleton_class.send(:alias_method, :hsb_to_rgb_alt, :hsv_to_rgb_alt)
|
174
|
+
|
175
|
+
#========= HSL <==> HSV (HSB) ==========================================
|
176
|
+
|
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
|
182
|
+
|
183
|
+
# calculation value & saturation HSV
|
184
|
+
value = lightness + saturation * [lightness, 1 - lightness].min
|
185
|
+
saturation_hsv = lightness == 0 ? 0 : 2 * (1 - lightness / value)
|
186
|
+
|
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
|
191
|
+
|
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
|
212
|
+
|
213
|
+
self.singleton_class.send(:alias_method, :hsb_to_hsl, :hsv_to_hsl)
|
214
|
+
|
215
|
+
#========= RGB(A) <==> CMYK ============================================
|
216
|
+
|
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)
|
223
|
+
end
|
224
|
+
|
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
|
230
|
+
|
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)
|
235
|
+
end
|
236
|
+
|
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
|
241
|
+
|
242
|
+
private
|
243
|
+
|
244
|
+
#========= helper methods for RGB to HSL/HSB/HSV =======================
|
245
|
+
|
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
|
252
|
+
|
253
|
+
[cmin, cmax, chroma]
|
254
|
+
end
|
255
|
+
|
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
|
275
|
+
end
|
276
|
+
end
|
data/lib/decolmor.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'decolmor/main'
|
2
|
+
require 'decolmor/version'
|
3
|
+
|
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
|
+
end
|
@@ -0,0 +1,420 @@
|
|
1
|
+
load_class(__FILE__)
|
2
|
+
|
3
|
+
RSpec.describe Decolmor do
|
4
|
+
describe 'you can set rounding globally for convert to HSL/HSV/HSB/CMYK' do
|
5
|
+
after(:each) do
|
6
|
+
Decolmor.hsx_round = 1
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:colors) { FactoryBot.build(:colors_map, round: 2) }
|
10
|
+
|
11
|
+
it ".hsx_round by default 1" do
|
12
|
+
expect( Decolmor::hsx_round ).to eq 1
|
13
|
+
end
|
14
|
+
|
15
|
+
it ".hsx_round= changes hsx_round" do
|
16
|
+
expect { Decolmor::hsx_round = 2 }.to change { Decolmor.hsx_round }.from(1).to(2)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
context 'HEX <==> RGB(A)' do
|
22
|
+
describe ".hex_to_rgb" do
|
23
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
24
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
25
|
+
|
26
|
+
it "HEX w prefix # to RGB" do
|
27
|
+
colors.each_pair do |hex, values|
|
28
|
+
expect( Decolmor::hex_to_rgb(hex) ).to eq values[:rgb]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "HEX w/o prefix # to RGB" do
|
33
|
+
colors.each_pair do |hex, values|
|
34
|
+
hex = hex.delete('#')
|
35
|
+
expect( Decolmor::hex_to_rgb(hex) ).to eq values[:rgb]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "HEX w alpha channel and prefix # to RGBA" do
|
40
|
+
docs "alpha into range 0..1 and rounding 5"
|
41
|
+
color = colors.keys.sample
|
42
|
+
alphas.each_pair do |hex_alpha, alpha|
|
43
|
+
hex = format('%s%s', color, hex_alpha)
|
44
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
45
|
+
expect( Decolmor::hex_to_rgb(hex) ).to eq rgba
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "HEX w alpha channel and w/o prefix # to RGBA" do
|
50
|
+
color = colors.keys.sample
|
51
|
+
|
52
|
+
alphas.each_pair do |hex_alpha, alpha|
|
53
|
+
hex = format('%s%s', color, hex_alpha).delete('#')
|
54
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
55
|
+
expect( Decolmor::hex_to_rgb(hex) ).to eq rgba
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe ".rgb_to_hex" do
|
61
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
62
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
63
|
+
|
64
|
+
it "RGB converts to HEX" do
|
65
|
+
colors.each_pair do |hex, values|
|
66
|
+
expect( Decolmor::rgb_to_hex(values[:rgb]) ).to eq hex
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "RGBA converts to HEX w alpha" do
|
71
|
+
color = colors.keys.sample
|
72
|
+
|
73
|
+
alphas.each_pair do |hex, alpha|
|
74
|
+
hex = format('%s%s', color, hex)
|
75
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
76
|
+
expect( Decolmor::rgb_to_hex(rgba) ).to eq hex
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
context 'simple generator RGB, you can set any channel(s)' do
|
84
|
+
describe ".new_rgb" do
|
85
|
+
it "generate RGB with values into range 0..255" do
|
86
|
+
100.times do
|
87
|
+
expect( Decolmor::new_rgb ).to all( be_between(0, 255) )
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "w params generate RGB with established values" do
|
92
|
+
docs "alpha isn't generated, but you can set it"
|
93
|
+
color = {red: 72, green: 209, blue: 204, alpha: 244}
|
94
|
+
# set and check each channel separately
|
95
|
+
color.each_with_index do |(key, value), index|
|
96
|
+
expect( Decolmor::new_rgb(**{key => value})[index] ).to eq value
|
97
|
+
end
|
98
|
+
# set all channels
|
99
|
+
expect( Decolmor::new_rgb(**color) ).to eq color.values
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
context 'RGB(A) to HSL/HSV/HSB' do
|
106
|
+
describe ".rgb_to_hsl" do
|
107
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
108
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
109
|
+
|
110
|
+
it "RGB converts to HSL" do
|
111
|
+
colors.each_pair do |hex, values|
|
112
|
+
expect( Decolmor::rgb_to_hsl(values[:rgb]) ).to eq values[:hsl]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "alpha channel pass to HSL unchanged" do
|
117
|
+
color = colors.keys.sample
|
118
|
+
alphas.each_pair do |hex_, alpha|
|
119
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
120
|
+
expect( Decolmor::rgb_to_hsl(rgba).last ).to eq alpha[:rgb]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
it "you can set rounding for resulting HSL values (default = 1)" do
|
125
|
+
docs "round 1 enough for a lossless conversion RGB -> HSL/HSV/HSB -> RGB"
|
126
|
+
colors.each_pair do |hex, values|
|
127
|
+
expect( Decolmor::rgb_to_hsl(values[:rgb], 0) ).to eq values[:hsl].map(&:round)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it "setting rounding doesn't affect alpha channel" do
|
132
|
+
color = colors.keys.sample
|
133
|
+
alphas.each_pair do |hex_, alpha|
|
134
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
135
|
+
# alpha shouldn't be rounded because its range is 0..1
|
136
|
+
# if that did happen, we would get 0 or 1 instead of the normal value
|
137
|
+
expect( Decolmor::rgb_to_hsl(rgba, 0).last ).to eq alpha[:rgb]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe ".rgb_to_hsv" do
|
143
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
144
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
145
|
+
let(:colors_round_2) { FactoryBot.build(:colors_map, round: 2) }
|
146
|
+
|
147
|
+
it "RGB converts to HSV" do
|
148
|
+
colors.each_pair do |hex, values|
|
149
|
+
expect( Decolmor::rgb_to_hsv(values[:rgb]) ).to eq values[:hsv]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "alpha channel pass to HSV unchanged" do
|
154
|
+
color = colors.keys.sample
|
155
|
+
alphas.each_pair do |hex_, alpha|
|
156
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
157
|
+
expect( Decolmor::rgb_to_hsv(rgba).last ).to eq alpha[:rgb]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it "you can set rounding for resulting HSV values (default = 1)" do
|
162
|
+
colors_round_2.each_pair do |hex, values|
|
163
|
+
expect( Decolmor::rgb_to_hsv(values[:rgb], 0) ).to eq values[:hsv].map(&:round)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "setting rounding doesn't affect alpha channel" do
|
168
|
+
color = colors.keys.sample
|
169
|
+
alphas.each_pair do |hex_, alpha|
|
170
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
171
|
+
expect( Decolmor::rgb_to_hsv(rgba, 0).last ).to eq alpha[:rgb]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
describe ".rgb_to_hsb" do
|
177
|
+
it "alias .rgb_to_hsv" do
|
178
|
+
expect( subject.method(:rgb_to_hsb ).original_name).to eq(:rgb_to_hsv)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
context 'HSL/HSV/HSB to RGB(A)' do
|
185
|
+
describe ".hsl_to_rgb" do
|
186
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
187
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
188
|
+
|
189
|
+
it "HSL converts to RGB" do
|
190
|
+
colors.each_pair do |hex, values|
|
191
|
+
expect( Decolmor::hsl_to_rgb(values[:hsl]) ).to eq values[:rgb]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it "alpha channel pass to RGB unchanged" do
|
196
|
+
color = colors.keys.sample
|
197
|
+
alphas.each_pair do |hex_, values|
|
198
|
+
hsla = colors[color][:hsl] + [values[:rgb]]
|
199
|
+
expect( Decolmor::hsl_to_rgb(hsla).last ).to eq values[:rgb]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe ".hsv_to_rgb" do
|
205
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
206
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
207
|
+
|
208
|
+
it "HSV converts to RGB" do
|
209
|
+
colors.each_pair do |hex, values|
|
210
|
+
expect( Decolmor::hsv_to_rgb(values[:hsv]) ).to eq values[:rgb]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it "alpha channel pass to RGB unchanged" do
|
215
|
+
color = colors.keys.sample
|
216
|
+
alphas.each_pair do |hex_, values|
|
217
|
+
hsva = colors[color][:hsv] + [values[:rgb]]
|
218
|
+
expect( Decolmor::hsv_to_rgb(hsva).last ).to eq values[:rgb]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe ".hsb_to_rgb" do
|
224
|
+
it "alias .hsv_to_rgb" do
|
225
|
+
expect( subject.method(:hsb_to_rgb ).original_name).to eq(:hsv_to_rgb)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
context 'Alternative implementation HSL/HSV/HSB to RGB(A)' do
|
232
|
+
describe ".hsl_to_rgb_alt" do
|
233
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
234
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
235
|
+
|
236
|
+
it "HSL converts to RGB" do
|
237
|
+
colors.each_pair do |hex, values|
|
238
|
+
expect( Decolmor::hsl_to_rgb_alt(values[:hsl]) ).to eq values[:rgb]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
it "alpha channel pass to RGB unchanged" do
|
243
|
+
color = colors.keys.sample
|
244
|
+
alphas.each_pair do |hex_, values|
|
245
|
+
hsla = colors[color][:hsl] + [values[:rgb]]
|
246
|
+
expect( Decolmor::hsl_to_rgb_alt(hsla).last ).to eq values[:rgb]
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
describe ".hsv_to_rgb_alt" do
|
252
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
253
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
254
|
+
|
255
|
+
it "HSV converts to RGB" do
|
256
|
+
colors.each_pair do |hex, values|
|
257
|
+
expect( Decolmor::hsv_to_rgb_alt(values[:hsv]) ).to eq values[:rgb]
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
it "alpha channel pass to RGB unchanged" do
|
262
|
+
color = colors.keys.sample
|
263
|
+
alphas.each_pair do |hex_, values|
|
264
|
+
hsva = colors[color][:hsv] + [values[:rgb]]
|
265
|
+
expect( Decolmor::hsv_to_rgb_alt(hsva).last ).to eq values[:rgb]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe ".hsb_to_rgb_alt" do
|
271
|
+
it "alias .hsv_to_rgb_alt" do
|
272
|
+
expect( subject.method(:hsb_to_rgb_alt ).original_name).to eq(:hsv_to_rgb_alt)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
|
278
|
+
context 'HSL <==> HSV (HSB)' do
|
279
|
+
describe ".hsl_to_hsv" do
|
280
|
+
# as for lossless conversion need to use float value with 2 decimal places
|
281
|
+
let(:colors) { FactoryBot.build(:colors_map, round: 2) }
|
282
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
283
|
+
|
284
|
+
it "HSL converts to HSV" do
|
285
|
+
colors.each_pair do |hex_, values|
|
286
|
+
hsv = values[:hsv].map { |value| value.round(1) }
|
287
|
+
expect( Decolmor::hsl_to_hsv(values[:hsl]) ).to eq hsv
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it "alpha channel pass to HSV unchanged" do
|
292
|
+
color = colors.keys.sample
|
293
|
+
alphas.each_pair do |hex_, alpha|
|
294
|
+
hsla = colors[color][:hsl] + [alpha[:rgb]]
|
295
|
+
expect( Decolmor::hsl_to_hsv(hsla).last ).to eq alpha[:rgb]
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it "you can set rounding for resulting HSV values (default = 1)" do
|
300
|
+
colors.each_pair do |hex, values|
|
301
|
+
expect( Decolmor::hsl_to_hsv(values[:hsl], 0) ).to eq values[:hsv].map(&:round)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
it "setting rounding doesn't affect alpha channel" do
|
306
|
+
color = colors.keys.sample
|
307
|
+
alphas.each_pair do |hex_, alpha|
|
308
|
+
hsla = colors[color][:hsl] + [alpha[:rgb]]
|
309
|
+
expect( Decolmor::hsl_to_hsv(hsla, 0).last ).to eq alpha[:rgb]
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
describe ".hsl_to_hsb" do
|
315
|
+
it "alias .hsl_to_hsv" do
|
316
|
+
expect( subject.method(:hsl_to_hsb).original_name ).to eq(:hsl_to_hsv)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe ".hsv_to_hsl" do
|
321
|
+
# as for lossless conversion need to use float value with 2 decimal places
|
322
|
+
let(:colors) { FactoryBot.build(:colors_map, round: 2) }
|
323
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
324
|
+
|
325
|
+
it "HSV converts to HSL" do
|
326
|
+
colors.each_pair do |hex_, values|
|
327
|
+
hsl = values[:hsl].map { |value| value.round(1) }
|
328
|
+
expect( Decolmor::hsv_to_hsl(values[:hsv]) ).to eq hsl
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
it "alpha channel pass to HSL unchanged" do
|
333
|
+
color = colors.keys.sample
|
334
|
+
alphas.each_pair do |hex_, alpha|
|
335
|
+
hsva = colors[color][:hsv] + [alpha[:rgb]]
|
336
|
+
expect( Decolmor::hsv_to_hsl(hsva).last ).to eq alpha[:rgb]
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
it "you can set rounding for resulting HSL values (default = 1)" do
|
341
|
+
colors.each_pair do |hex, values|
|
342
|
+
expect( Decolmor::hsv_to_hsl(values[:hsv], 0) ).to eq values[:hsl].map(&:round)
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
it "setting rounding doesn't affect alpha channel" do
|
347
|
+
color = colors.keys.sample
|
348
|
+
alphas.each_pair do |hex_, alpha|
|
349
|
+
hsva = colors[color][:hsv] + [alpha[:rgb]]
|
350
|
+
expect( Decolmor::hsv_to_hsl(hsva, 0).last ).to eq alpha[:rgb]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe ".hsb_to_hsl" do
|
356
|
+
it "alias .hsv_to_hsl" do
|
357
|
+
expect( subject.method(:hsb_to_hsl).original_name ).to eq(:hsv_to_hsl)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
|
363
|
+
context 'RGB(A) <==> CMYK' do
|
364
|
+
describe ".rgb_to_cmyk" do
|
365
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
366
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
367
|
+
|
368
|
+
it "RGB converts to CMYK" do
|
369
|
+
colors.each_pair do |hex_, values|
|
370
|
+
|
371
|
+
cmyk = values[:cmyk].map {|arr| arr.round(1) }
|
372
|
+
expect( Decolmor::rgb_to_cmyk(values[:rgb]) ).to eq cmyk
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
it "alpha channel pass to HSL unchanged" do
|
377
|
+
color = colors.keys.sample
|
378
|
+
alphas.each_pair do |hex_, alpha|
|
379
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
380
|
+
expect( Decolmor::rgb_to_cmyk(rgba).last ).to eq alpha[:rgb]
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
it "you can set rounding for resulting CMYK values (default = 1)" do
|
385
|
+
colors.each_pair do |hex, values|
|
386
|
+
expect( Decolmor::hsv_to_hsl(values[:hsv], 0) ).to eq values[:hsl].map(&:round)
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
it "setting rounding doesn't affect alpha channel" do
|
391
|
+
color = colors.keys.sample
|
392
|
+
alphas.each_pair do |hex_, alpha|
|
393
|
+
rgba = colors[color][:rgb] + [alpha[:rgb]]
|
394
|
+
expect( Decolmor::rgb_to_cmyk(rgba, 0).last ).to eq alpha[:rgb]
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe ".cmyk_to_rgb" do
|
400
|
+
let(:colors) { FactoryBot.build(:colors_map) }
|
401
|
+
let(:alphas) { FactoryBot.build(:alpha) }
|
402
|
+
|
403
|
+
it "CMYK converts to RGB" do
|
404
|
+
colors.each_pair do |hex_, values|
|
405
|
+
expect( Decolmor::cmyk_to_rgb(values[:cmyk]) ).to eq values[:rgb]
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
it "alpha channel pass to RGB unchanged" do
|
410
|
+
color = colors.keys.sample
|
411
|
+
alphas.each_pair do |hex_, values|
|
412
|
+
cmyka = colors[color][:cmyk] + [values[:rgb]]
|
413
|
+
expect( Decolmor::cmyk_to_rgb(cmyka).last ).to eq values[:rgb]
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
end
|
420
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :alpha, class: Hash do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
samples = [0, 1, 64, 128, 191, 254, 255]
|
6
|
+
|
7
|
+
values = samples.each_with_object(Hash.new) do |value, hash|
|
8
|
+
hex = "%02X" % value
|
9
|
+
range_01 = (value / 255.to_f).round(5)
|
10
|
+
hash[hex] = {rgb: range_01, rgb_255: value}
|
11
|
+
end
|
12
|
+
|
13
|
+
initialize_with { values }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :colors_map, class: Hash do
|
3
|
+
skip_create
|
4
|
+
|
5
|
+
colors_map = {
|
6
|
+
# black
|
7
|
+
"#000000" => {rgb: [ 0, 0, 0], hsl: [ 0.000, 0.000, 0.000],
|
8
|
+
cmyk: [ 0.000, 0.000, 0.000, 100.000], hsv: [ 0.000, 0.000, 0.000],},
|
9
|
+
# white
|
10
|
+
"#FFFFFF" => {rgb: [255, 255, 255], hsl: [ 0.000, 0.000, 100.000],
|
11
|
+
cmyk: [ 0.000, 0.000, 0.000, 0.000], hsv: [ 0.000, 0.000, 100.000],},
|
12
|
+
# red
|
13
|
+
"#FF0000" => {rgb: [255, 0, 0], hsl: [ 0.000, 100.000, 50.000],
|
14
|
+
cmyk: [ 0.000, 100.000, 100.000, 0.000], hsv: [ 0.000, 100.000, 100.000],},
|
15
|
+
# lime
|
16
|
+
"#00FF00" => {rgb: [ 0, 255, 0], hsl: [120.000, 100.000, 50.000],
|
17
|
+
cmyk: [100.000, 0.000, 100.000, 0.000], hsv: [120.000, 100.000, 100.000],},
|
18
|
+
# blue
|
19
|
+
"#0000FF" => {rgb: [ 0, 0, 255], hsl: [240.000, 100.000, 50.000],
|
20
|
+
cmyk: [100.000, 100.000, 0.000, 0.000], hsv: [240.000, 100.000, 100.000],},
|
21
|
+
# yellow
|
22
|
+
"#FFFF00" => {rgb: [255, 255, 0], hsl: [ 60.000, 100.000, 50.000],
|
23
|
+
cmyk: [ 0.000, 0.000, 100.000, 0.000], hsv: [ 60.000, 100.000, 100.000],},
|
24
|
+
# cyan / aqua
|
25
|
+
"#00FFFF" => {rgb: [ 0, 255, 255], hsl: [180.000, 100.000, 50.000],
|
26
|
+
cmyk: [100.000, 0.000, 0.000, 0.000], hsv: [180.000, 100.000, 100.000],},
|
27
|
+
# magenta / fuchsia
|
28
|
+
"#FF00FF" => {rgb: [255, 0, 255], hsl: [300.000, 100.000, 50.000],
|
29
|
+
cmyk: [ 0.000, 100.000, 0.000, 0.000], hsv: [300.000, 100.000, 100.000],},
|
30
|
+
# silver
|
31
|
+
"#C0C0C0" => {rgb: [192, 192, 192], hsl: [ 0.000, 0.000, 75.294],
|
32
|
+
cmyk: [ 0.000, 0.000, 0.000, 24.706], hsv: [ 0.000, 0.000, 75.294],},
|
33
|
+
# gray
|
34
|
+
"#808080" => {rgb: [128, 128, 128], hsl: [ 0.000, 0.000, 50.196],
|
35
|
+
cmyk: [ 0.000, 0.000, 0.000, 49.804], hsv: [ 0.000, 0.000, 50.196],},
|
36
|
+
# maroon
|
37
|
+
"#800000" => {rgb: [128, 0, 0], hsl: [ 0.000, 100.000, 25.098],
|
38
|
+
cmyk: [ 0.000, 100.000, 100.000, 49.804], hsv: [ 0.000, 100.000, 50.196],},
|
39
|
+
# olive
|
40
|
+
"#808000" => {rgb: [128, 128, 0], hsl: [ 60.000, 100.000, 25.098],
|
41
|
+
cmyk: [ 0.000, 0.000, 100.000, 49.804], hsv: [ 60.000, 100.000, 50.196],},
|
42
|
+
# green
|
43
|
+
"#008000" => {rgb: [ 0, 128, 0], hsl: [120.000, 100.000, 25.098],
|
44
|
+
cmyk: [100.000, 0.000, 100.000, 49.804], hsv: [120.000, 100.000, 50.196],},
|
45
|
+
# purple
|
46
|
+
"#800080" => {rgb: [128, 0, 128], hsl: [300.000, 100.000, 25.098],
|
47
|
+
cmyk: [ 0.000, 100.000, 0.000, 49.804], hsv: [300.000, 100.000, 50.196],},
|
48
|
+
# teal
|
49
|
+
"#008080" => {rgb: [ 0, 128, 128], hsl: [180.000, 100.000, 25.098],
|
50
|
+
cmyk: [100.000, 0.000, 0.000, 49.804], hsv: [180.000, 100.000, 50.196],},
|
51
|
+
# navy
|
52
|
+
"#000080" => {rgb: [ 0, 0, 128], hsl: [240.000, 100.000, 25.098],
|
53
|
+
cmyk: [100.000, 100.000, 0.000, 49.804], hsv: [240.000, 100.000, 50.196],},
|
54
|
+
|
55
|
+
# some random colors
|
56
|
+
"#E01783" => {rgb: [224, 23, 131], hsl: [327.761, 81.377, 48.431],
|
57
|
+
cmyk: [ 0.000, 89.732, 41.518, 12.157], hsv: [327.761, 89.732, 87.843],},
|
58
|
+
|
59
|
+
"#1D8056" => {rgb: [ 29, 128, 86], hsl: [154.545, 63.057, 30.784],
|
60
|
+
cmyk: [ 77.344, 0.000, 32.812, 49.804], hsv: [154.545, 77.344, 50.196],},
|
61
|
+
|
62
|
+
"#229765" => {rgb: [ 34, 151, 101], hsl: [154.359, 63.243, 36.275],
|
63
|
+
cmyk: [ 77.483, 0.000, 33.113, 40.784], hsv: [154.359, 77.483, 59.216],},
|
64
|
+
|
65
|
+
"#239393" => {rgb: [ 35, 147, 147], hsl: [180.000, 61.538, 35.686],
|
66
|
+
cmyk: [ 76.190, 0.000, 0.000, 42.353], hsv: [180.000, 76.190, 57.647],},
|
67
|
+
}
|
68
|
+
|
69
|
+
transient do
|
70
|
+
round { 1 }
|
71
|
+
end
|
72
|
+
|
73
|
+
initialize_with do
|
74
|
+
colors_map.transform_values do |value|
|
75
|
+
value.transform_values { |arr| arr.map { |num| num.round(round)} }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter 'spec'
|
5
|
+
add_filter 'lib/decolmor/version.rb'
|
6
|
+
|
7
|
+
if ENV['CI']
|
8
|
+
require 'codecov'
|
9
|
+
formatter SimpleCov::Formatter::Codecov
|
10
|
+
else
|
11
|
+
formatter SimpleCov::Formatter::MultiFormatter
|
12
|
+
.new([SimpleCov::Formatter::HTMLFormatter])
|
13
|
+
end
|
14
|
+
|
15
|
+
track_files "**/*.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
require 'rspec'
|
20
|
+
require 'factory_bot'
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
|
24
|
+
config.expose_dsl_globally = true
|
25
|
+
|
26
|
+
# Use color in STDOUT
|
27
|
+
config.color_mode = :on
|
28
|
+
|
29
|
+
# Use color not only in STDOUT but also in pagers and files
|
30
|
+
config.tty = true
|
31
|
+
|
32
|
+
# Use the specified formatter
|
33
|
+
config.formatter = :documentation # :progress, :html, :textmate
|
34
|
+
|
35
|
+
config.include FactoryBot::Syntax::Methods
|
36
|
+
|
37
|
+
config.before do
|
38
|
+
FactoryBot.reload
|
39
|
+
end
|
40
|
+
|
41
|
+
config.before(:suite) do
|
42
|
+
FactoryBot.find_definitions
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_class(file)
|
47
|
+
klass = File.basename(file).gsub('_spec','')
|
48
|
+
require File.expand_path("lib/#{klass}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def docs(message, level=0)
|
52
|
+
RSpec.configuration.reporter.message "#{' ' * level}# #{message}"
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: decolmor
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ChildrenofkoRn
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.17'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rake
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '13.0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '13.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: codecov
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.2'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.2'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.8'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.8'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: factory_bot
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '5.1'
|
82
|
+
- - "<"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '7.0'
|
85
|
+
type: :development
|
86
|
+
prerelease: false
|
87
|
+
version_requirements: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '5.1'
|
92
|
+
- - "<"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '7.0'
|
95
|
+
description: |-
|
96
|
+
Gem for converting color spaces from/to: HEX/RGB/HSL/HSV/HSB/CMYK
|
97
|
+
The Alpha channel (transparency) is supported.
|
98
|
+
There is also a simple RGB generator.
|
99
|
+
email: Rick-ROR@ya.ru
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files:
|
103
|
+
- README.md
|
104
|
+
- LICENSE
|
105
|
+
files:
|
106
|
+
- ".rspec"
|
107
|
+
- CHANGELOG.md
|
108
|
+
- Gemfile
|
109
|
+
- LICENSE
|
110
|
+
- NEWS.md
|
111
|
+
- README.md
|
112
|
+
- Rakefile
|
113
|
+
- decolmor.gemspec
|
114
|
+
- lib/decolmor.rb
|
115
|
+
- lib/decolmor/main.rb
|
116
|
+
- lib/decolmor/version.rb
|
117
|
+
- spec/decolmor_spec.rb
|
118
|
+
- spec/factories/alpha.rb
|
119
|
+
- spec/factories/colors.rb
|
120
|
+
- spec/spec_helper.rb
|
121
|
+
homepage: https://github.com/ChildrenofkoRn/decolmor
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata:
|
125
|
+
homepage_uri: https://github.com/ChildrenofkoRn/decolmor
|
126
|
+
news_uri: https://github.com/ChildrenofkoRn/decolmor/blob/master/NEWS.md
|
127
|
+
changelog_uri: https://github.com/ChildrenofkoRn/decolmor/blob/master/CHANGELOG.md
|
128
|
+
documentation_uri: https://github.com/ChildrenofkoRn/decolmor/blob/master/README.md
|
129
|
+
bug_tracker_uri: https://github.com/ChildrenofkoRn/decolmor/issues
|
130
|
+
source_code_uri: https://github.com/ChildrenofkoRn/decolmor
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 2.4.0
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
requirements: []
|
146
|
+
rubygems_version: 3.0.9
|
147
|
+
signing_key:
|
148
|
+
specification_version: 4
|
149
|
+
summary: 'Converter color spaces from/to: HEX/RGB/HSL/HSV/HSB/CMYK'
|
150
|
+
test_files:
|
151
|
+
- spec/decolmor_spec.rb
|
152
|
+
- spec/factories/alpha.rb
|
153
|
+
- spec/factories/colors.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- ".rspec"
|