color-maker 0.0.1
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/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +4 -0
- data/Rakefile +13 -0
- data/color-maker.gemspec +27 -0
- data/lib/color-maker.rb +10 -0
- data/lib/color/array.rb +30 -0
- data/lib/color/hash.rb +51 -0
- data/lib/color/maker.rb +241 -0
- data/lib/color/maker/support.rb +235 -0
- data/lib/color/maker/version.rb +5 -0
- data/lib/color/string.rb +30 -0
- data/lib/colors.yml +148 -0
- data/tags +7561 -0
- data/test/array_test.rb +53 -0
- data/test/hash_test.rb +50 -0
- data/test/maker_test.rb +108 -0
- data/test/string_test.rb +24 -0
- data/test/support_test.rb +74 -0
- data/test/test_helper.rb +3 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 448854ab6021a69144db3a5959dc5c889c685282
|
4
|
+
data.tar.gz: 69051f1096a95ae6da1706f405ac1ff16910dcfc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 842806eaa65b94d907e731240c78865096c08a92791ee325bff9cf869a9f08c9f6470260666c52a531e4345c21b3c2381659d0a8c68a8f09fc6e49ac9d9472fc
|
7
|
+
data.tar.gz: 5bfc948e3739ba22ca027ee88724cdf0db8edc86210996de60d1c31eff6c12a2ce1a388867aaf92d1cb3a5df5473eabcbbfbd34bb6bea38d5452fbe5acf85b3b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Viorel Craescu
|
2
|
+
|
3
|
+
MIT License
|
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
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/color-maker.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'color/maker/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "color-maker"
|
8
|
+
spec.version = Color::Maker::VERSION
|
9
|
+
spec.authors = ["Viorel Craescu"]
|
10
|
+
spec.email = ["viorel@craescu.com"]
|
11
|
+
spec.summary = %q{Color generator}
|
12
|
+
spec.description = %q{Color generator}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "color", "~> 1.7"
|
22
|
+
spec.add_dependency "activesupport"
|
23
|
+
spec.add_development_dependency "yard"
|
24
|
+
spec.add_development_dependency "minitest"
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
end
|
data/lib/color-maker.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "color/maker/version"
|
2
|
+
require 'color/hash'
|
3
|
+
require 'color/string'
|
4
|
+
require 'color/array'
|
5
|
+
require 'color/maker'
|
6
|
+
require 'color/maker/support'
|
7
|
+
require 'active_support/core_ext/hash/keys'
|
8
|
+
require 'active_support/core_ext/object/try'
|
9
|
+
require 'color'
|
10
|
+
require 'yaml'
|
data/lib/color/array.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Monkey patch Array class with color conversion utility methods
|
2
|
+
class Array
|
3
|
+
# Converts an array to color
|
4
|
+
#
|
5
|
+
# @param [Symbol] format color format (:rgb, :hsv, :hsl)
|
6
|
+
# @return [Color::RGB]
|
7
|
+
# @example
|
8
|
+
# [150, 200, 100].to_color(:rgb) #=> Color::RGB
|
9
|
+
# [200, 0.9, 0.3].to_color(:hsv) #=> Color::RGB
|
10
|
+
# [150, 0.3, 0.1].to_color(:hsl) #=> Color::RGB
|
11
|
+
def to_color(format = :rgb)
|
12
|
+
format = format.to_sym
|
13
|
+
if format == :rgb
|
14
|
+
r, g, b = self
|
15
|
+
return Color::Maker::Support::rgb_to_color(r: r.to_i, g: g.to_i, b: b.to_i)
|
16
|
+
end
|
17
|
+
|
18
|
+
if format == :hsv
|
19
|
+
h, s, v = self
|
20
|
+
return Color::Maker::Support::hsv_to_color(h: h.to_f, s: s.to_f, v: v.to_f)
|
21
|
+
end
|
22
|
+
|
23
|
+
if format == :hsl
|
24
|
+
h, s, l = self
|
25
|
+
return Color::Maker::Support::hsl_to_color(h: h.to_f, s: s.to_f, l: l.to_f)
|
26
|
+
end
|
27
|
+
|
28
|
+
raise "Unknown color format: #{format}"
|
29
|
+
end
|
30
|
+
end
|
data/lib/color/hash.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Monkey patch Hash class with color conversion utility methods
|
2
|
+
class Hash
|
3
|
+
# Convert an Hash to color
|
4
|
+
#
|
5
|
+
# @param [Symbol] format color format (:rgb, :hsv, :hsl)
|
6
|
+
# @return [Color::RGB]
|
7
|
+
# @example
|
8
|
+
# { r: 150, g: 100, b: 200 }.to_colour(:rgb) #=> Color::RGB
|
9
|
+
# { red: 150, green: 100, blue: 200 }.to_colour(:rgb) #=> Color::RGB
|
10
|
+
# { h: 150, s: 100, v: 200 }.to_colour(:hsv) #=> Color::RGB
|
11
|
+
# { hue: 150, saturation: 100, value: 200 }.to_colour(:hsv) #=> Color::RGB
|
12
|
+
# { hue: 150, saturation: 100, lightness: 200 }.to_colour(:hsl) #=> Color::RGB
|
13
|
+
# { h: 150, s: 100, l: 200 }.to_colour(:hsl) #=> Color::RGB
|
14
|
+
def to_color(format = :rgb)
|
15
|
+
format = format.to_sym
|
16
|
+
if format == :rgb
|
17
|
+
normalized = Color::Maker::Support.normalize_color_keys(self, format: :rgb)
|
18
|
+
r, g, b = normalized[:r].to_i, normalized[:g].to_i, normalized[:b].to_i
|
19
|
+
return Color::Maker::Support::rgb_to_color(r: r, g: g, b: b)
|
20
|
+
end
|
21
|
+
|
22
|
+
if format == :hsv
|
23
|
+
normalized = Color::Maker::Support.normalize_color_keys(self, format: :hsv)
|
24
|
+
h, s, v = normalized[:h].to_f, normalized[:s].to_f, normalized[:v].to_f
|
25
|
+
return Color::Maker::Support::hsv_to_color(h: h, s: s, v: v)
|
26
|
+
end
|
27
|
+
|
28
|
+
if format == :hsl
|
29
|
+
normalized = Color::Maker::Support.normalize_color_keys(self, format: :hsl)
|
30
|
+
h, s, l = normalized[:h].to_f, normalized[:s].to_f, normalized[:l].to_f
|
31
|
+
return Color::Maker::Support::hsl_to_color(h: h, s: s, l: l)
|
32
|
+
end
|
33
|
+
|
34
|
+
raise "Unknow color format: #{format}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Replace keys
|
38
|
+
#
|
39
|
+
# @param [Symbol, Array, String] needle The value being searched for, otherwise known
|
40
|
+
# as the needle. An array may be used to designate multiple needles.
|
41
|
+
# @param [Symbol, String] replace The replacement value that replaces found search values.
|
42
|
+
# @example
|
43
|
+
# { one: 1, two: 2 }.replace_key!([:one, :two], :three) #=> { three: 2 }
|
44
|
+
# { one: 1, two: 2 }.replace_key!(:one, :three) #=> { three: 1, two: 2 }
|
45
|
+
def replace_key!(needle, replace)
|
46
|
+
needle = [needle] unless needle.is_a?(Enumerable)
|
47
|
+
needle.each { |key| self[replace] = self.delete(key) if self.key?(key) }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
data/lib/color/maker.rb
ADDED
@@ -0,0 +1,241 @@
|
|
1
|
+
module Color
|
2
|
+
# Create random pleasing colors as well as color schemes based on a given color.
|
3
|
+
class Maker
|
4
|
+
PHI = 0.618033988749895
|
5
|
+
|
6
|
+
# Value generator, Random class instance
|
7
|
+
attr_accessor :generator
|
8
|
+
|
9
|
+
@colors_path = File.join(File.dirname(__FILE__), '..', 'colors.yml')
|
10
|
+
|
11
|
+
# @!attribute [r]
|
12
|
+
# Default color make options
|
13
|
+
@defaults = {
|
14
|
+
hue: nil,
|
15
|
+
saturation: nil,
|
16
|
+
value: nil,
|
17
|
+
base_color: nil,
|
18
|
+
greyscale: false,
|
19
|
+
grayscale: false,
|
20
|
+
golden: true,
|
21
|
+
random: false,
|
22
|
+
count: 1,
|
23
|
+
seed: false
|
24
|
+
}
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# @!macro color_maker_defaults
|
28
|
+
# @option options [Symbol] :hue (nil) By setting the hue, you determine the
|
29
|
+
# color. (0..360)
|
30
|
+
# @option options [Symbol] :saturation (nil) By setting the saturation, you
|
31
|
+
# determine the distance from gray. (0..1.0)
|
32
|
+
# @option options [Symbol] :value (nil) By setting the value, you determine
|
33
|
+
# the balance between black and white. (0..1.0)
|
34
|
+
# @option options [Symbol] :base_color (nil) Setting a base_color (e.g. "pink")
|
35
|
+
# will create a random color within the HSV range of the chosen color.
|
36
|
+
# Will recognize any of the 146 standard HTML colors, it has a very
|
37
|
+
# good memory.
|
38
|
+
# @option options [Symbol] :greyscale (false) for the brits - Setting either
|
39
|
+
# greyscale or grayscale (but we all know which one is correct) to true
|
40
|
+
# will cause all of the colors you generate to be within the grey or gray
|
41
|
+
# range. This is effectively the same as setting your saturation to 0.
|
42
|
+
# @option options [Symbol] :grayscale (false) for the yanks - Setting either
|
43
|
+
# greyscale or grayscale (but we all know which one is correct) to true
|
44
|
+
# will cause all of the colors you generate to be within the grey or gray
|
45
|
+
# range. This is effectively the same as setting your saturation to 0.
|
46
|
+
# @option options [Symbol] :golden (true) Setting golden to true randomizes
|
47
|
+
# your hue (overrides hue setting) and makes you a spectacular color
|
48
|
+
# based on the golden ratio. It's so good, it's the default. Make sure to
|
49
|
+
# turn it off if you want to have more control over your generated colors.
|
50
|
+
# @option options [Symbol] :random (false) It will completely randomize the
|
51
|
+
# hue, saturation, and value of the colors it makes.
|
52
|
+
# @option options [Symbol] :count (1) Setting count to higher than 1 will
|
53
|
+
# return an array full of the colors. If you set it to 1, you'll just get
|
54
|
+
# the one color! It makes a sort of sense if you think about it.
|
55
|
+
|
56
|
+
# @!macro color_maker_defaults_rgb
|
57
|
+
# @option options [Symbol] :red (nil) red value for the color to start with (0..255)
|
58
|
+
# @option options [Symbol] :green (nil) green value for the color to start with (0..255)
|
59
|
+
# @option options [Symbol] :blue (nil) blue value for the color to start with (0..255)
|
60
|
+
|
61
|
+
# @!macro color_maker_defaults_hsl
|
62
|
+
# @option options [Symbol] :lightness (nil) the luminosity of the colour (0..1.0)
|
63
|
+
|
64
|
+
# @!macro color_maker_defaults
|
65
|
+
attr_reader :defaults
|
66
|
+
|
67
|
+
# Memorized colors
|
68
|
+
#
|
69
|
+
# @return [Hash]
|
70
|
+
# @example
|
71
|
+
# Color::Maker.colors(:aquamarine) #=> 7FFFD4
|
72
|
+
def colors
|
73
|
+
return @colors if @colors
|
74
|
+
@hex_colors = ::YAML.load_file(@colors_path)
|
75
|
+
@hex_colors.symbolize_keys!
|
76
|
+
@colors = Hash.new do |h, k|
|
77
|
+
if @hex_colors[k]
|
78
|
+
h[k] = Color::RGB.by_hex(@hex_colors[k])
|
79
|
+
else
|
80
|
+
h[k] = Color::RGB.by_name(k)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Return a Color::RGB object
|
86
|
+
#
|
87
|
+
# @param name [Symbol] css color name or from above list
|
88
|
+
# @return Color::RGB
|
89
|
+
# @example
|
90
|
+
# Color::Maker.by_name(:aquamarine) #=> Color::RGB
|
91
|
+
# Color::Maker.by_name(:blue) #=> Color::RGB
|
92
|
+
# Color::Maker.by_name(:aliceblue) #=> Color::RGB
|
93
|
+
# {include:file:lib/colors.yml}
|
94
|
+
def by_name(name)
|
95
|
+
self.colors[name.downcase.to_sym]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# @!macro color_maker_defaults
|
100
|
+
# @!macro color_maker_defaults_rgb
|
101
|
+
# @!macro color_maker_defaults_hsl
|
102
|
+
def initialize(options = {})
|
103
|
+
options = normalize_options(options)
|
104
|
+
@options = self.class.defaults.merge(options)
|
105
|
+
@generator = Random.new(options[:seed]) rescue Random.new
|
106
|
+
end
|
107
|
+
|
108
|
+
# Generates a new color
|
109
|
+
# @param [Hash] options the options to generate the color
|
110
|
+
# @!macro color_maker_defaults
|
111
|
+
# @!macro color_maker_defaults_rgb
|
112
|
+
# @!macro color_maker_defaults_hsl
|
113
|
+
# @return Color::RGB
|
114
|
+
def make(options = {})
|
115
|
+
generator = self.generator
|
116
|
+
generator = Random.new(options[:seed]) if options[:seed]
|
117
|
+
|
118
|
+
options = normalize_options(options)
|
119
|
+
options = self.class.defaults.merge(@options.merge(options))
|
120
|
+
|
121
|
+
base_color = self.class.by_name(options[:base_color]) if options[:base_color]
|
122
|
+
colors = []
|
123
|
+
options[:count].times do |i|
|
124
|
+
if base_color
|
125
|
+
base_color = base_color.to_hsl
|
126
|
+
base_color = Support::hsl_to_hsv(h: base_color.hue, s: base_color.s, l: base_color.l)
|
127
|
+
|
128
|
+
hue = generator.rand((base_color[:h] - 5)..(base_color[:h] + 5))
|
129
|
+
saturation = generator.rand(0.4..0.85)
|
130
|
+
value = generator.rand(0.4..0.85)
|
131
|
+
colors << [hue, saturation, value].to_color(:hsv)
|
132
|
+
else
|
133
|
+
hue = make_hue(options, generator)
|
134
|
+
saturation = make_saturation(options, generator)
|
135
|
+
value = make_value(options, generator)
|
136
|
+
|
137
|
+
colors << [hue, saturation, value].to_color(:hsv)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
colors = colors[0] if colors.size == 1
|
142
|
+
colors
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
private
|
147
|
+
def normalize_options(options)
|
148
|
+
normalized = {}
|
149
|
+
|
150
|
+
variants = { hue: [:h], saturation: [:s], value: [:v],
|
151
|
+
red: [:r], green: [:g], blue: [:b],
|
152
|
+
lightness: [:l, :luminosity, :light],
|
153
|
+
greyscale: [:grayscale, :gray] }
|
154
|
+
|
155
|
+
variants.each do |key, keys|
|
156
|
+
found_key = keys.find { |k| options[k] } || key
|
157
|
+
normalized[key] ||= options[found_key] if options[found_key]
|
158
|
+
options.delete(found_key) if normalized[key]
|
159
|
+
end
|
160
|
+
|
161
|
+
options.each { |key, value| normalized[key] = value unless normalized.key?(key) }
|
162
|
+
normalized.merge(extract_color(normalized))
|
163
|
+
normalized
|
164
|
+
end
|
165
|
+
|
166
|
+
def extract_color(options)
|
167
|
+
formats = { :hsv => [:hue, :saturation, :value],
|
168
|
+
:rgb => [:red, :green, :blue ],
|
169
|
+
:hsl => [:hue, :saturation, :lightness] }
|
170
|
+
|
171
|
+
format = formats.map do |key, keys|
|
172
|
+
[key, keys.count { |k| options[k] } ]
|
173
|
+
end
|
174
|
+
format = format.max_by { |v| v.last }.first
|
175
|
+
|
176
|
+
color = {}
|
177
|
+
formats[format].each { |value| color[value] = options[value].to_f }
|
178
|
+
|
179
|
+
case format
|
180
|
+
when :rgb
|
181
|
+
color = Support.rgb_to_hsv(color)
|
182
|
+
when :hsl
|
183
|
+
color = Support.hsl_to_hsv(color)
|
184
|
+
end
|
185
|
+
|
186
|
+
color
|
187
|
+
end
|
188
|
+
|
189
|
+
def make_hue(options = {}, generator = nil)
|
190
|
+
generator ||= self.generator
|
191
|
+
|
192
|
+
if options[:greyscale]
|
193
|
+
hue = 0
|
194
|
+
elsif options[:golden]
|
195
|
+
random_hue = generator.rand(0..360)
|
196
|
+
hue = (random_hue + (random_hue / PHI)) % 360
|
197
|
+
elsif options[:hue].nil? || options[:random]
|
198
|
+
hue = random_hue
|
199
|
+
else
|
200
|
+
hue = clamp(options[:hue], 0, 360)
|
201
|
+
end
|
202
|
+
hue
|
203
|
+
end
|
204
|
+
|
205
|
+
def make_saturation(options = {}, generator = nil)
|
206
|
+
generator ||= self.generator
|
207
|
+
|
208
|
+
if options[:greyscale]
|
209
|
+
saturation = 0
|
210
|
+
elsif options[:random]
|
211
|
+
saturation = generator.rand(0..1.0)
|
212
|
+
elsif options[:saturation].nil?
|
213
|
+
saturation = 0.4
|
214
|
+
else
|
215
|
+
saturation = clamp(options[:saturation], 0, 1)
|
216
|
+
end
|
217
|
+
|
218
|
+
saturation
|
219
|
+
end
|
220
|
+
|
221
|
+
def make_value(options = {}, generator = nil)
|
222
|
+
generator ||= self.generator
|
223
|
+
|
224
|
+
if options[:random]
|
225
|
+
value = self.generator.rand(0..1.0)
|
226
|
+
elsif options[:greyscale]
|
227
|
+
value = self.generator.rand(0.15..0.75)
|
228
|
+
elsif options[:value].nil?
|
229
|
+
value = 0.75
|
230
|
+
else
|
231
|
+
value = clamp(options[:value], 0, 1)
|
232
|
+
end
|
233
|
+
|
234
|
+
value
|
235
|
+
end
|
236
|
+
|
237
|
+
def clamp(num, min, max)
|
238
|
+
[min, [num, max].min].max
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
@@ -0,0 +1,235 @@
|
|
1
|
+
module Color
|
2
|
+
class Maker
|
3
|
+
# Utilities methods
|
4
|
+
module Support
|
5
|
+
class << self
|
6
|
+
# Convert color from HSV to RGB format
|
7
|
+
#
|
8
|
+
# @param [Hash] hsv
|
9
|
+
# @return [Hash] color in RGB format
|
10
|
+
# @example
|
11
|
+
# Color::Maker::Support.hsv_to_rgb({ h: 12, s: 0.9, v: 0.2 }) #=> { r: 51, g: 14, b: 5 }
|
12
|
+
# Color::Maker::Support.hsv_to_rgb({ hue: 12, saturation: 0.9, value: 0.2 }) #=> { r: 51, g: 14, b: 5 }
|
13
|
+
def hsv_to_rgb(hsv)
|
14
|
+
hsv = normalize_color_keys(hsv, format: :hsv)
|
15
|
+
h, s, v = hsv[:h].to_f / 360.0, hsv[:s].to_f, hsv[:v].to_f
|
16
|
+
i = (h * 6).floor
|
17
|
+
f = h * 6 - i
|
18
|
+
p = v * (1 - s)
|
19
|
+
q = v * (1 - f * s)
|
20
|
+
t = v * (1 - (1 - f) * s)
|
21
|
+
case i % 6
|
22
|
+
when 0
|
23
|
+
r, g, b = v, t, p
|
24
|
+
when 1
|
25
|
+
r, g, b = q, v, p
|
26
|
+
when 2
|
27
|
+
r, g, b = p, v, t
|
28
|
+
when 3
|
29
|
+
r, g, b = p, q, v
|
30
|
+
when 4
|
31
|
+
r, g, b = t, p, v
|
32
|
+
else
|
33
|
+
r, g, b = v, p, q
|
34
|
+
end
|
35
|
+
|
36
|
+
r, g, b = (r * 255).round(0), (g * 255).round(0), (b * 255).round(0)
|
37
|
+
{ r: r, g: g, b: b }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Convert color from RGB to HSV
|
41
|
+
#
|
42
|
+
# @param [Hash] rgb
|
43
|
+
# @return [Hash] color in HSV format
|
44
|
+
# @example
|
45
|
+
# Color::Maker::Support.rgb_to_hsv({ r: 15, g: 193, b: 17 }) #=> { h: 120.7, s: 0.922, v: 0.757 }
|
46
|
+
# Color::Maker::Support.rgb_to_hsv({ red: 15, green: 193, blue: 17 }) #=> { h: 120.7, s: 0.922, v: 0.757 }
|
47
|
+
def rgb_to_hsv(rgb)
|
48
|
+
rgb = normalize_color_keys(rgb, format: :rgb)
|
49
|
+
r, g, b = rgb[:r].to_f / 255.0, rgb[:g].to_f / 255.0, rgb[:b].to_f / 255.0
|
50
|
+
min, max = [r, g, b].min, [r, g, b].max
|
51
|
+
delta = max - min
|
52
|
+
v = max
|
53
|
+
|
54
|
+
return { h: 0, s: 0, v: v.round(3) } if max == 0 || delta == 0
|
55
|
+
|
56
|
+
s = delta / max
|
57
|
+
h = 4 + (r - g) / delta
|
58
|
+
|
59
|
+
if r == max
|
60
|
+
h = (g - b) / delta
|
61
|
+
elsif g == max
|
62
|
+
h = 2 + (b - r) / delta
|
63
|
+
end
|
64
|
+
|
65
|
+
h *= 60
|
66
|
+
h += 360 if h < 0
|
67
|
+
{ h: h.round(1), s: s.round(3), v: v.round(3) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# Convert color from RGB to HSL
|
71
|
+
#
|
72
|
+
# @param [Hash] rgb
|
73
|
+
# @return [Hash] color in HSL format
|
74
|
+
# @example
|
75
|
+
# Color::Maker::Support.rgb_to_hsl({ r: 15, g: 193, b: 17 }) #=> { h: 120.7, s: 0.922, v: 0.757 }
|
76
|
+
# Color::Maker::Support.rgb_to_hsl({ red: 15, green: 193, blue: 17 }) #=> { h: 120.7, s: 0.922, v: 0.757 }
|
77
|
+
def rgb_to_hsl(rgb)
|
78
|
+
rgb = normalize_color_keys(rgb, format: :rgb)
|
79
|
+
r, g, b = rgb[:r].to_f / 255.0, rgb[:g].to_f / 255.0, rgb[:b].to_f / 255.0
|
80
|
+
min, max = [r, g, b].min, [r, g, b].max
|
81
|
+
delta = max - min
|
82
|
+
|
83
|
+
l = (min + max) / 2.0
|
84
|
+
return { h: 0, s: 0, l: l } if delta == 0
|
85
|
+
|
86
|
+
s = l < 0.5 ? delta / (max + min) : delta / (2 - max - min)
|
87
|
+
|
88
|
+
case(max)
|
89
|
+
when r
|
90
|
+
h = (g - b) / delta + (g < b ? 6 : 0)
|
91
|
+
when g
|
92
|
+
h = (b - r) / delta + 2
|
93
|
+
when b
|
94
|
+
h = (r - g) / delta + 4
|
95
|
+
end
|
96
|
+
|
97
|
+
h *= 60
|
98
|
+
|
99
|
+
{ h: h.round(1), s: s.round(3), l: l.round(3) }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Convert color from HSL to RGB
|
103
|
+
#
|
104
|
+
# @param [Hash] hsl
|
105
|
+
# @return [Hash] color in RGB format
|
106
|
+
# @example
|
107
|
+
# Color::Maker::Support.hsl_to_rgb({ h: 120.7, s: 0.856, l: 0.408 }) #=> { r: 15, g: 193, b: 17 }
|
108
|
+
# Color::Maker::Support.hsl_to_rgb({ hue: 120.7, saturation: 0.856, lightness: 0.408 }) #=> { r: 15, g: 193, b: 17 }
|
109
|
+
def hsl_to_rgb(hsl)
|
110
|
+
hsl = normalize_color_keys(hsl, format: :hsl)
|
111
|
+
h, s, l = hsl[:h].to_f / 360.0, hsl[:s].to_f, hsl[:l].to_f
|
112
|
+
return { r: l, g: l, b: l } if s == 0
|
113
|
+
|
114
|
+
q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
115
|
+
p = 2 * l - q
|
116
|
+
|
117
|
+
r = hue_to_rgb(p: p, q: q, t: h + 1 / 3.0) * 255
|
118
|
+
g = hue_to_rgb(p: p, q: q, t: h) * 255
|
119
|
+
b = hue_to_rgb(p: p, q: q, t: h - 1 / 3.0) * 255
|
120
|
+
|
121
|
+
{ r: r.round(0), g: g.round(0), b: b.round(0) }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Convert color from HSV to HSL
|
125
|
+
#
|
126
|
+
# @param [Hash] hsv
|
127
|
+
# @return [Hash] color in HSL format
|
128
|
+
# @example
|
129
|
+
# Color::Maker::Support.hsv_to_hsl({ h: 120.7, s: 0.922, v: 0.757 }) #=> { h: 120.7, s: 0.856, v: 0.408 })
|
130
|
+
# Color::Maker::Support.hsv_to_hsl({ hue: 120.7, saturation: 0.922, value: 0.757 }) #=> { h: 120.7, s: 0.856, v: 0.408 })
|
131
|
+
def hsv_to_hsl(hsv)
|
132
|
+
rgb = hsv_to_rgb(hsv)
|
133
|
+
rgb_to_hsl(rgb)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Convert color from HSL to HSV
|
137
|
+
#
|
138
|
+
# @param [Hash] hsl
|
139
|
+
# @return [Hash] color in HSL format
|
140
|
+
# @example
|
141
|
+
# Color::Maker::Support.hsl_to_hsv({ h: 120.7, s: 0.856, l: 0.408 }) #=> { h: 120.7, s: 0.922, v: 0.757 })
|
142
|
+
# Color::Maker::Support.hsl_to_hsv({ hue: 120.7, saturation: 0.856, lightness: 0.408 }) #=> { h: 120.7, s: 0.922, v: 0.757 })
|
143
|
+
def hsl_to_hsv(hsl)
|
144
|
+
rgb = hsl_to_rgb(hsl)
|
145
|
+
rgb_to_hsv(rgb)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Convert color from HSL to Color::RGB
|
149
|
+
#
|
150
|
+
# @param [Hash] hsl
|
151
|
+
# @return [Color::RGB] color
|
152
|
+
# @example
|
153
|
+
# Color::Maker::Support.hsl_to_color({ h: 120.7, s: 0.856, l: 0.408 }) #=> Color::RGB
|
154
|
+
# Color::Maker::Support.hsl_to_color({ hue: 120.7, saturation: 0.856, lightness: 0.408 }) #=> Color::RGB
|
155
|
+
def hsl_to_color(hsl)
|
156
|
+
rgb = hsl_to_rgb(hsl)
|
157
|
+
Color::RGB.new(rgb[:r], rgb[:g], rgb[:b])
|
158
|
+
end
|
159
|
+
|
160
|
+
# Convert color from RGB to Color::RGB
|
161
|
+
#
|
162
|
+
# @param [Hash] rgb
|
163
|
+
# @return [Color::RGB] color
|
164
|
+
# @example
|
165
|
+
# Color::Maker::Support.rgb_to_color({ r: 100, g: 150, b: 250 }) #=> Color::RGB
|
166
|
+
# Color::Maker::Support.rgb_to_color({ red: 100, green: 150, blue: 250 }) #=> Color::RGB
|
167
|
+
def rgb_to_color(rgb)
|
168
|
+
rgb = normalize_color_keys(rgb, format: :rgb)
|
169
|
+
Color::RGB.new(rgb[:r], rgb[:g], rgb[:b])
|
170
|
+
end
|
171
|
+
|
172
|
+
# Convert color from HSV to Color::RGB
|
173
|
+
#
|
174
|
+
# @param [Hash] hsv
|
175
|
+
# @return [Color::RGB] color
|
176
|
+
def hsv_to_color(hsv)
|
177
|
+
rgb = hsv_to_rgb(hsv)
|
178
|
+
rgb_to_color(rgb)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Convert color from HEX to Color::RGB
|
182
|
+
#
|
183
|
+
# @param [Hash] hex
|
184
|
+
# @return [Color::RGB] color
|
185
|
+
def hex_to_color(hex)
|
186
|
+
hex = hex.sub(/0x/, '')
|
187
|
+
Color::RGB.by_hex(hex)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Normalize color keys
|
191
|
+
#
|
192
|
+
# @param [Hash] hash values in HSV, RGB or HSL format
|
193
|
+
# @param [Hash] options ({ format: :rgb, short: true })
|
194
|
+
# @return [Hash] color keys normalized
|
195
|
+
# @example
|
196
|
+
# Color::Maker::Support.normalize_color_keys({ r: 100, blue: 100, g: 100 }) #=> { r: 100, b: 100, g: 10 }
|
197
|
+
# Color::Maker::Support.normalize_color_keys({ r: 100, blue: 100, g: 100 }, short: false) #=> { red: 100, blue: 100, green: 10 }
|
198
|
+
def normalize_color_keys(hash, options = {})
|
199
|
+
default_options = { format: :rgb, short: true }
|
200
|
+
options = default_options.merge(options)
|
201
|
+
|
202
|
+
format = options[:format].to_sym
|
203
|
+
|
204
|
+
keys = {
|
205
|
+
rgb: { [:r, :red] => [:r, :red], [:b, :blue] => [:b, :blue], [:g, :green] => [:g, :green] },
|
206
|
+
hsv: { [:h, :hue] => [:h, :hue], [:s, :saturation] => [:s, :saturation], [:v, :value] => [:v, :value] },
|
207
|
+
hsl: { [:h, :hue] => [:h, :hue], [:s, :saturation] => [:s, :saturation], [:l, :light] => [:l, :lightness, :luminosity, :light] }
|
208
|
+
}
|
209
|
+
|
210
|
+
return unless keys[format]
|
211
|
+
|
212
|
+
normalized = hash.dup
|
213
|
+
keys[format].each do |key, variants|
|
214
|
+
default = options[:short] ? key.first : key.last
|
215
|
+
normalized.replace_key!(variants, default)
|
216
|
+
end
|
217
|
+
|
218
|
+
normalized
|
219
|
+
end
|
220
|
+
|
221
|
+
private
|
222
|
+
def hue_to_rgb(hue)
|
223
|
+
p, q, t = hue[:p].to_f, hue[:q].to_f, hue[:t].to_f
|
224
|
+
t += 1 if t < 0
|
225
|
+
t -= 1 if t > 1
|
226
|
+
return p + (q - p) * 6 * t if t < (1 / 6.0)
|
227
|
+
return q if t < (1 / 2.0)
|
228
|
+
return p + (q - p) * (2 / 3.0 - t) * 6 if t < (2 / 3.0)
|
229
|
+
p
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|