chroma 0.0.1.alpha.2 → 0.0.1.alpha.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,11 +3,36 @@ module Chroma
3
3
  class << self
4
4
  private
5
5
 
6
- def build(*attrs)
7
- Class.new do
8
- attr_accessor *(attrs + [:a])
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
- EOS
21
- end
45
+ end
46
+ EOS
22
47
  end
23
48
  end
24
49
 
25
- Rgb = build :r, :g, :b
26
- Hsl = build :h, :s, :l
27
- Hsv = build :h, :s, :v
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
 
@@ -0,0 +1,6 @@
1
+ module Chroma
2
+ module Errors
3
+ class PaletteDefinedError < StandardError; end
4
+ class UnrecognizedColor < StandardError; end
5
+ end
6
+ end
@@ -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
@@ -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
- def complement
8
- [@color, @color.complement]
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
- def triad
12
- hsl_map([0, 120, 240])
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
- def tetrad
16
- hsl_map([0, 90, 180, 270])
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
- def split_complement
20
- hsl_map([0, 72, 216])
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
- def analogous(results = 6, slices = 30)
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
- def monochromatic(results = 6)
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 hsl_map(degrees)
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
 
@@ -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
- if n < 1
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