chroma 0.0.1.alpha.2 → 0.0.1.alpha.3
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 +4 -4
- data/.yardopts +1 -0
- data/CHANGELOG.md +34 -1
- data/README.md +3 -1
- data/Rakefile +8 -1
- data/lib/chroma.rb +60 -1
- data/lib/chroma/color.rb +50 -1
- data/lib/chroma/color/attributes.rb +31 -0
- data/lib/chroma/color/modifiers.rb +59 -1
- data/lib/chroma/color/serializers.rb +69 -0
- data/lib/chroma/color_modes.rb +36 -9
- data/lib/chroma/converters/base.rb +10 -0
- data/lib/chroma/converters/hsl_converter.rb +7 -0
- data/lib/chroma/converters/hsv_converter.rb +7 -0
- data/lib/chroma/converters/rgb_converter.rb +7 -0
- data/lib/chroma/errors.rb +6 -0
- data/lib/chroma/extensions/string.rb +11 -0
- data/lib/chroma/harmonies.rb +100 -17
- data/lib/chroma/helpers/bounders.rb +19 -1
- data/lib/chroma/palette_builder.rb +14 -0
- data/lib/chroma/rgb_generator.rb +8 -6
- data/lib/chroma/rgb_generator/base.rb +2 -0
- data/lib/chroma/rgb_generator/from_hex_string_values.rb +29 -0
- data/lib/chroma/rgb_generator/from_hsl.rb +4 -0
- data/lib/chroma/rgb_generator/from_hsl_values.rb +7 -0
- data/lib/chroma/rgb_generator/from_hsv.rb +4 -0
- data/lib/chroma/rgb_generator/from_hsv_values.rb +7 -0
- data/lib/chroma/rgb_generator/from_rgb.rb +5 -1
- data/lib/chroma/rgb_generator/from_rgb_values.rb +8 -1
- data/lib/chroma/rgb_generator/from_string.rb +12 -1
- data/lib/chroma/version.rb +1 -1
- data/spec/chroma/define_palette_spec.rb +6 -0
- data/spec/color/palette_spec.rb +173 -0
- data/spec/custom_matchers.rb +13 -0
- data/spec/spec_helper.rb +1 -7
- metadata +8 -2
data/lib/chroma/color_modes.rb
CHANGED
@@ -3,11 +3,36 @@ module Chroma
|
|
3
3
|
class << self
|
4
4
|
private
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
# Builds a new color mode class.
|
7
|
+
#
|
8
|
+
# @param name [String] the class name
|
9
|
+
# @param attrs [Array<Symbol>] the instance attribute names
|
10
|
+
# @!macro [attach] build
|
11
|
+
# @!parse class $1
|
12
|
+
# attr_accessor :$2, :$3, :$4, :a
|
13
|
+
#
|
14
|
+
# # @param $2 [Numeric]
|
15
|
+
# # @param $3 [Numeric]
|
16
|
+
# # @param $4 [Numeric]
|
17
|
+
# # @param a [Numeric]
|
18
|
+
# def initialize(${2-4}, a = 1)
|
19
|
+
# @$2, @$3, @$4, @a = $2, $3, $4, a
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # Returns the values `$2`, `$3`, `$4`, and `a` as an array.
|
23
|
+
# #
|
24
|
+
# # @return [Array<Numeric>]
|
25
|
+
# def to_a
|
26
|
+
# [@$2, @$3, @$4, @a]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# alias_method :to_ary, :to_a
|
30
|
+
# end
|
31
|
+
def build(name, *attrs)
|
32
|
+
class_eval <<-EOS
|
33
|
+
class #{name}
|
34
|
+
attr_accessor #{(attrs + [:a]).map{|attr| ":#{attr}"} * ', '}
|
9
35
|
|
10
|
-
class_eval <<-EOS
|
11
36
|
def initialize(#{attrs * ', '}, a = 1)
|
12
37
|
#{attrs.map{|attr| "@#{attr}"} * ', '}, @a = #{attrs * ', '}, a
|
13
38
|
end
|
@@ -17,13 +42,15 @@ module Chroma
|
|
17
42
|
end
|
18
43
|
|
19
44
|
alias_method :to_ary, :to_a
|
20
|
-
|
21
|
-
|
45
|
+
end
|
46
|
+
EOS
|
22
47
|
end
|
23
48
|
end
|
24
49
|
|
25
|
-
|
26
|
-
|
27
|
-
|
50
|
+
private
|
51
|
+
|
52
|
+
build 'Rgb', :r, :g, :b
|
53
|
+
build 'Hsl', :h, :s, :l
|
54
|
+
build 'Hsv', :h, :s, :v
|
28
55
|
end
|
29
56
|
end
|
@@ -1,20 +1,30 @@
|
|
1
1
|
module Chroma
|
2
2
|
module Converters
|
3
|
+
# Base class for converting one color mode to another.
|
4
|
+
# @abstract
|
3
5
|
class Base
|
4
6
|
include Helpers::Bounders
|
5
7
|
|
8
|
+
# @param input [ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
|
9
|
+
# @return [Base]
|
6
10
|
def initialize(input)
|
7
11
|
@input = input
|
8
12
|
end
|
9
13
|
|
14
|
+
# @param rgb [ColorModes::Rgb]
|
15
|
+
# @return [ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
|
10
16
|
def self.convert_rgb(rgb)
|
11
17
|
new(rgb).convert_rgb
|
12
18
|
end
|
13
19
|
|
20
|
+
# @param hsl [ColorModes::Hsl]
|
21
|
+
# @return [ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
|
14
22
|
def self.convert_hsl(hsl)
|
15
23
|
new(hsl).convert_hsl
|
16
24
|
end
|
17
25
|
|
26
|
+
# @param hsv [ColorModes::Hsv]
|
27
|
+
# @return [ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
|
18
28
|
def self.convert_hsv(hsv)
|
19
29
|
new(hsv).convert_hsv
|
20
30
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Chroma
|
2
2
|
module Converters
|
3
|
+
# Class to convert a color mode to {ColorModes::Hsl}.
|
3
4
|
class HslConverter < Base
|
5
|
+
# Convert rgb to hsl.
|
6
|
+
# @return [ColorModes::Hsl]
|
4
7
|
def convert_rgb
|
5
8
|
r = bound01(@input.r, 255)
|
6
9
|
g = bound01(@input.g, 255)
|
@@ -35,10 +38,14 @@ module Chroma
|
|
35
38
|
ColorModes::Hsl.new(h * 360, s, l, @input.a)
|
36
39
|
end
|
37
40
|
|
41
|
+
# Returns @input because it's the same color mode.
|
42
|
+
# @return [ColorModes::Hsl]
|
38
43
|
def convert_hsl
|
39
44
|
@input
|
40
45
|
end
|
41
46
|
|
47
|
+
# Convert hsv to hsl.
|
48
|
+
# @return [ColorModes::Hsl]
|
42
49
|
def convert_hsv
|
43
50
|
HsvConverter.convert_rgb(RgbConverter.convert_hsl(@input))
|
44
51
|
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Chroma
|
2
2
|
module Converters
|
3
|
+
# Class to convert a color mode to {ColorModes::Hsl}.
|
3
4
|
class HsvConverter < Base
|
5
|
+
# Convert rgb to hsv.
|
6
|
+
# @return [ColorModes::Hsv]
|
4
7
|
def convert_rgb
|
5
8
|
r = bound01(@input.r, 255)
|
6
9
|
g = bound01(@input.g, 255)
|
@@ -29,10 +32,14 @@ module Chroma
|
|
29
32
|
ColorModes::Hsv.new(h * 360, s, v, @input.a)
|
30
33
|
end
|
31
34
|
|
35
|
+
# Convert hsl to hsv.
|
36
|
+
# @return [ColorModes::Hsv]
|
32
37
|
def convert_hsl
|
33
38
|
HslConverter.convert_rgb(RgbConverter.convert_hsv(@input))
|
34
39
|
end
|
35
40
|
|
41
|
+
# Returns @input because it's the same color mode.
|
42
|
+
# @return [ColorModes::Hsv]
|
36
43
|
def convert_hsv
|
37
44
|
@input
|
38
45
|
end
|
@@ -1,10 +1,15 @@
|
|
1
1
|
module Chroma
|
2
2
|
module Converters
|
3
|
+
# Class to convert a color mode to {ColorModes::Rgb}.
|
3
4
|
class RgbConverter < Base
|
5
|
+
# Returns @input because it's the same color mode.
|
6
|
+
# @return [ColorModes::Rgb]
|
4
7
|
def convert_rgb
|
5
8
|
@input
|
6
9
|
end
|
7
10
|
|
11
|
+
# Convert hsl to rgb.
|
12
|
+
# @return [ColorModes::Rgb]
|
8
13
|
def convert_hsl
|
9
14
|
h, s, l = @input
|
10
15
|
|
@@ -25,6 +30,8 @@ module Chroma
|
|
25
30
|
ColorModes::Rgb.new(r, g, b, bound_alpha(@input.a))
|
26
31
|
end
|
27
32
|
|
33
|
+
# Convert hsv to rgb.
|
34
|
+
# @return [ColorModes::Rgb]
|
28
35
|
def convert_hsv
|
29
36
|
h, s, v = @input
|
30
37
|
|
@@ -1,4 +1,15 @@
|
|
1
1
|
class String
|
2
|
+
# Creates {Chroma::Color} directly from a string representing a color.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# 'red'.paint
|
6
|
+
# '#f00'.paint
|
7
|
+
# '#ff0000'.paint
|
8
|
+
# 'rgb(255, 0, 0)'.paint
|
9
|
+
# 'hsl(0, 100%, 50%)'.paint
|
10
|
+
# 'hsv(0, 100%, 100%)'.paint
|
11
|
+
#
|
12
|
+
# @return [Chroma::Color]
|
2
13
|
def paint
|
3
14
|
Chroma.paint(self)
|
4
15
|
end
|
data/lib/chroma/harmonies.rb
CHANGED
@@ -1,55 +1,138 @@
|
|
1
1
|
module Chroma
|
2
|
+
# Class to hold all palette methods.
|
2
3
|
class Harmonies
|
4
|
+
# @param color [Color]
|
3
5
|
def initialize(color)
|
4
6
|
@color = color
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
+
# Generate a complement palette.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# 'red'.paint.palette.complement #=> [red, cyan]
|
13
|
+
# 'red'.paint.palette.complement(as: :name) #=> ['red', 'cyan']
|
14
|
+
# 'red'.paint.palette.complement(as: :hex) #=> ['#ff0000', '#00ffff']
|
15
|
+
#
|
16
|
+
# @param options [Hash]
|
17
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
18
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
19
|
+
def complement(options = {})
|
20
|
+
with_reformat([@color, @color.complement], options[:as])
|
9
21
|
end
|
10
22
|
|
11
|
-
|
12
|
-
|
23
|
+
# Generate a triad palette.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# 'red'.paint.palette.triad #=> [red, lime, blue]
|
27
|
+
# 'red'.paint.palette.triad(as: :name) #=> ['red', 'lime', 'blue']
|
28
|
+
# 'red'.paint.palette.triad(as: :hex) #=> ['#ff0000', '#00ff00', '#0000ff']
|
29
|
+
#
|
30
|
+
# @param options [Hash]
|
31
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
32
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
33
|
+
def triad(options = {})
|
34
|
+
hsl_map([0, 120, 240], options)
|
13
35
|
end
|
14
36
|
|
15
|
-
|
16
|
-
|
37
|
+
# Generate a tetrad palette.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# 'red'.paint.palette.tetrad #=> [red, #80ff00, cyan, #7f00ff]
|
41
|
+
# 'red'.paint.palette.tetrad(as: :name) #=> ['red', '#80ff00', 'cyan', '#7f00ff']
|
42
|
+
# 'red'.paint.palette.tetrad(as: :hex) #=> ['#ff0000', '#80ff00', '#00ffff', '#7f00ff']
|
43
|
+
#
|
44
|
+
# @param options [Hash]
|
45
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
46
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
47
|
+
def tetrad(options = {})
|
48
|
+
hsl_map([0, 90, 180, 270], options)
|
17
49
|
end
|
18
50
|
|
19
|
-
|
20
|
-
|
51
|
+
# Generate a split complement palette.
|
52
|
+
#
|
53
|
+
# @example
|
54
|
+
# 'red'.paint.palette.split_complement #=> [red, #ccff00, #0066ff]
|
55
|
+
# 'red'.paint.palette.split_complement(as: :name) #=> ['red', '#ccff00', '#0066ff']
|
56
|
+
# 'red'.paint.palette.split_complement(as: :hex) #=> ['#ff0000', '#ccff00', '#0066ff']
|
57
|
+
#
|
58
|
+
# @param options [Hash]
|
59
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
60
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
61
|
+
def split_complement(options = {})
|
62
|
+
hsl_map([0, 72, 216], options)
|
21
63
|
end
|
22
64
|
|
23
|
-
|
65
|
+
# Generate an analogous palette.
|
66
|
+
#
|
67
|
+
# @example
|
68
|
+
# 'red'.paint.palette.analogous #=> [red, #ff0066, #ff0033, red, #ff3300, #ff6600]
|
69
|
+
# 'red'.paint.palette.analogous(as: :hex) #=> ['#f00', '#f06', '#f03', '#f00', '#f30', '#f60']
|
70
|
+
# 'red'.paint.palette.analogous(results: 3) #=> [red, #ff001a, #ff1a00]
|
71
|
+
# 'red'.paint.palette.analogous(results: 3, slices: 60) #=> [red, #ff000d, #ff0d00]
|
72
|
+
#
|
73
|
+
# @param options [Hash]
|
74
|
+
# @option options :results [Symbol] (6) number of results to return
|
75
|
+
# @option options :slices [Symbol] (30)
|
76
|
+
# the angle in degrees to slice the hue circle per color
|
77
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
78
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
79
|
+
def analogous(options = {})
|
80
|
+
results = options[:results] || 6
|
81
|
+
slices = options[:slices] || 30
|
82
|
+
|
24
83
|
hsl = @color.hsl
|
25
84
|
part = 360 / slices
|
26
85
|
hsl.h = ((hsl.h - (part * results >> 1)) + 720) % 360
|
27
86
|
|
28
|
-
(results - 1).times.reduce([@color]) do |arr, n|
|
87
|
+
palette = (results - 1).times.reduce([@color]) do |arr, n|
|
29
88
|
hsl.h = (hsl.h + part) % 360
|
30
|
-
arr << Color.new(hsl)
|
89
|
+
arr << Color.new(hsl, @color.format)
|
31
90
|
end
|
91
|
+
|
92
|
+
with_reformat(palette, options[:as])
|
32
93
|
end
|
33
94
|
|
34
|
-
|
95
|
+
# Generate a monochromatic palette.
|
96
|
+
#
|
97
|
+
# @example
|
98
|
+
# 'red'.paint.palette.monochromatic #=> [red, #2a0000, #550000, maroon, #aa0000, #d40000]
|
99
|
+
# 'red'.paint.palette.monochromatic(as: :hex) #=> ['#ff0000', '#2a0000', '#550000', '#800000', '#aa0000', '#d40000']
|
100
|
+
# 'red'.paint.palette.monochromatic(results: 3) #=> [red, #550000, #aa0000]
|
101
|
+
#
|
102
|
+
# @param options [Hash]
|
103
|
+
# @option options :results [Symbol] (6) number of results to return
|
104
|
+
# @option options :as [Symbol] (nil) optional format to output colors as strings
|
105
|
+
# @return [Array<Color>, Array<String>] depending on presence of `options[:as]`
|
106
|
+
def monochromatic(options = {})
|
107
|
+
results = options[:results] || 6
|
108
|
+
|
35
109
|
h, s, v = @color.hsv
|
36
110
|
modification = 1.0 / results
|
37
111
|
|
38
|
-
results.times.map do
|
39
|
-
Color.new(ColorModes::Hsv.new(h, s, v)).tap do
|
112
|
+
palette = results.times.map do
|
113
|
+
Color.new(ColorModes::Hsv.new(h, s, v), @color.format).tap do
|
40
114
|
v = (v + modification) % 1
|
41
115
|
end
|
42
116
|
end
|
117
|
+
|
118
|
+
with_reformat(palette, options[:as])
|
43
119
|
end
|
44
120
|
|
45
121
|
private
|
46
122
|
|
47
|
-
def
|
123
|
+
def with_reformat(palette, as)
|
124
|
+
palette.map! { |color| color.to_s(as) } unless as.nil?
|
125
|
+
palette
|
126
|
+
end
|
127
|
+
|
128
|
+
def hsl_map(degrees, options)
|
48
129
|
h, s, l = @color.hsl
|
49
130
|
|
50
|
-
degrees.map do |deg|
|
51
|
-
Color.new(ColorModes::Hsl.new((h + deg) % 360, s, l))
|
131
|
+
degrees.map! do |deg|
|
132
|
+
Color.new(ColorModes::Hsl.new((h + deg) % 360, s, l), @color.format)
|
52
133
|
end
|
134
|
+
|
135
|
+
with_reformat(degrees, options[:as])
|
53
136
|
end
|
54
137
|
end
|
55
138
|
end
|
@@ -1,26 +1,44 @@
|
|
1
1
|
module Chroma
|
2
2
|
module Helpers
|
3
3
|
module Bounders
|
4
|
+
# Bounds a value `n` that is from `0` to `max` to `0` to `1`.
|
5
|
+
#
|
6
|
+
# @param n [Numeric, String]
|
7
|
+
# @param max [Fixnum]
|
8
|
+
# @return [Float]
|
4
9
|
def bound01(n, max)
|
5
10
|
is_percent = n.to_s.include? '%'
|
6
11
|
n = [max, [0, n.to_f].max].min
|
7
|
-
n = (n * max).to_i / 100 if is_percent
|
12
|
+
n = (n * max).to_i / 100.0 if is_percent
|
8
13
|
|
9
14
|
return 1 if (n - max).abs < 0.000001
|
10
15
|
|
11
16
|
(n % max) / max.to_f
|
12
17
|
end
|
13
18
|
|
19
|
+
# Ensure alpha value `a` is between `0` and `1`.
|
20
|
+
#
|
21
|
+
# @param a [Numeric, String] alpha value
|
22
|
+
# @return [Numeric]
|
14
23
|
def bound_alpha(a)
|
15
24
|
a = a.to_f
|
16
25
|
a = 1 if a < 0 || a > 1
|
17
26
|
a
|
18
27
|
end
|
19
28
|
|
29
|
+
# Ensures a number between `0` and `1`. Returns `n` if it is between `0`
|
30
|
+
# and `1`.
|
31
|
+
#
|
32
|
+
# @param n [Numeric]
|
33
|
+
# @return [Numeric]
|
20
34
|
def clamp01(n)
|
21
35
|
[1, [0, n].max].min
|
22
36
|
end
|
23
37
|
|
38
|
+
# Converts `n` to a percentage type value.
|
39
|
+
#
|
40
|
+
# @param n [Numeric, String]
|
41
|
+
# @return [String, Float]
|
24
42
|
def to_percentage(n)
|
25
43
|
n = n.to_f
|
26
44
|
n = "#{n * 100}%" if n <= 1
|
@@ -1,14 +1,25 @@
|
|
1
1
|
module Chroma
|
2
|
+
# Class internally used to build custom palettes from {Chroma.define_palette}.
|
2
3
|
class PaletteBuilder
|
4
|
+
# Wrapper to instantiate a new instance of {PaletteBuilder} and call its
|
5
|
+
# {PaletteBuilder#build} method.
|
6
|
+
#
|
7
|
+
# @param name [Symbol, String] the name of the custom palette
|
8
|
+
# @param block [Proc] the palette definition block
|
9
|
+
# @return [Symbol, String] the name of the custom palette
|
3
10
|
def self.build(name, &block)
|
4
11
|
new(name, &block).build
|
5
12
|
end
|
6
13
|
|
14
|
+
# @param name [Symbol, String] the name of the custom palette
|
15
|
+
# @param block [Proc] the palette definition block
|
7
16
|
def initialize(name, &block)
|
8
17
|
@name = name
|
9
18
|
@block = block
|
10
19
|
end
|
11
20
|
|
21
|
+
# Build the custom palette by defining a new method on {Harmonies}.
|
22
|
+
# @return [Symbol, String] the name of the custom palette
|
12
23
|
def build
|
13
24
|
dsl = PaletteBuilderDsl.new
|
14
25
|
dsl.instance_eval(&@block)
|
@@ -23,6 +34,7 @@ module Chroma
|
|
23
34
|
|
24
35
|
private
|
25
36
|
|
37
|
+
# Internal class for palette building DSL syntax.
|
26
38
|
class PaletteBuilderDsl
|
27
39
|
attr_reader :conversions
|
28
40
|
|
@@ -36,6 +48,8 @@ module Chroma
|
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
51
|
+
# Internal class to represent color modification calls in the palette
|
52
|
+
# builder DSL definition syntax.
|
39
53
|
class ColorCalls
|
40
54
|
attr_reader :name, :args
|
41
55
|
|
data/lib/chroma/rgb_generator.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
module Chroma
|
2
|
+
# Main module to generate an instance of {ColorModes::Rgb} from several
|
3
|
+
# possible inputs.
|
2
4
|
module RgbGenerator
|
3
5
|
class << self
|
6
|
+
# Generates an instance of {ColorModes::Rgb} as well as color format
|
7
|
+
# symbol.
|
8
|
+
#
|
9
|
+
# @param input [String, ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
|
10
|
+
# @return [[ColorModes::Rgb, Symbol]]
|
4
11
|
def generate_rgb_and_format(input)
|
5
12
|
get_generator(input).generate.tap do |(rgb)|
|
6
13
|
rgb.r = round(rgb.r)
|
@@ -23,12 +30,7 @@ module Chroma
|
|
23
30
|
end
|
24
31
|
|
25
32
|
def round(n)
|
26
|
-
|
27
|
-
n.round
|
28
|
-
else
|
29
|
-
#(n * 100).round / 100
|
30
|
-
n
|
31
|
-
end
|
33
|
+
n < 1 ? n.round : n
|
32
34
|
end
|
33
35
|
end
|
34
36
|
end
|