kodachroma 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.
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ # Base rgb generator class.
5
+ # @abstract
6
+ class Base
7
+ include Helpers::Bounders
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromHexStringValues < Base
5
+ # @param format [Symbol] color format
6
+ # @param r [String] red value
7
+ # @param g [String] green value
8
+ # @param b [String] blue value
9
+ # @param a [String] alpha value
10
+ def initialize(format, r, g, b, a = 'ff')
11
+ @format = format || :hex
12
+ @r = r
13
+ @g = g
14
+ @b = b
15
+ @a = a
16
+ end
17
+
18
+ # Generates a {ColorModes::Rgb}.
19
+ # @return [ColorModes::Rgb]
20
+ def generate
21
+ r, g, b = [@r, @g, @b].map { |n| n.to_i(16) }
22
+ a = @a.to_i(16) / 255.0
23
+ [ColorModes::Rgb.new(r, g, b, a), @format]
24
+ end
25
+
26
+ class << self
27
+ # Generates a {ColorModes::Rgb} from 3-character hexadecimal.
28
+ # @return [ColorModes::Rgb]
29
+ #
30
+ # @param format [Symbol] color format
31
+ # @param r [String] red value
32
+ # @param g [String] green value
33
+ # @param b [String] blue value
34
+ def from_hex3(format, r, g, b)
35
+ new(format || :hex3, r * 2, g * 2, b * 2)
36
+ end
37
+
38
+ # Generates a {ColorModes::Rgb} from 6-character hexadecimal.
39
+ # @return [ColorModes::Rgb]
40
+ #
41
+ # @param format [Symbol] color format
42
+ # @param r [String] red value
43
+ # @param g [String] green value
44
+ # @param b [String] blue value
45
+ def from_hex6(format, r, g, b)
46
+ new(format, r, g, b)
47
+ end
48
+
49
+ # Generates a {ColorModes::Rgb} from 8-character hexadecimal.
50
+ # @return [ColorModes::Rgb]
51
+ #
52
+ # @param format [Symbol] color format
53
+ # @param r [String] red value
54
+ # @param g [String] green value
55
+ # @param b [String] blue value
56
+ # @param a [String] alpha value
57
+ def from_hex8(format, a, r, g, b)
58
+ new(format || :hex8, r, g, b, a)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromHsl < Base
5
+ # @param format [Symbol] color format
6
+ # @param hsl [ColorModes::Hsl]
7
+ def initialize(format, hsl)
8
+ @format = format
9
+ @hsl = hsl
10
+ end
11
+
12
+ # Generates a {ColorModes::Rgb}.
13
+ # @return [ColorModes::Rgb]
14
+ def generate
15
+ FromHslValues.new(@format, *@hsl.to_a).generate
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromHslValues < Base
5
+ # @param format [Symbol] color format
6
+ # @param h [String, Numeric] hue value
7
+ # @param s [String, Numeric] saturation value
8
+ # @param l [String, Numeric] lightness value
9
+ # @param a [String, Numeric] alpha value
10
+ def initialize(format, h, s, l, a = 1)
11
+ s = to_percentage(s)
12
+ l = to_percentage(l)
13
+
14
+ @format = format || :hsl
15
+ @hsl = ColorModes::Hsl.new(h, s, l, a)
16
+ end
17
+
18
+ # Generates a {ColorModes::Rgb}.
19
+ # @return [ColorModes::Rgb]
20
+ def generate
21
+ [Converters::RgbConverter.convert_hsl(@hsl), @format]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromHsv < Base
5
+ # @param format [Symbol] color format
6
+ # @param hsv [ColorModes::Hsv]
7
+ def initialize(format, hsv)
8
+ @format = format
9
+ @hsv = hsv
10
+ end
11
+
12
+ # Generates a {ColorModes::Rgb}.
13
+ # @return [ColorModes::Rgb]
14
+ def generate
15
+ FromHsvValues.new(@format, *@hsv.to_a).generate
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromHsvValues < Base
5
+ # @param format [Symbol] color format
6
+ # @param h [String, Numeric] hue value
7
+ # @param s [String, Numeric] saturation value
8
+ # @param v [String, Numeric] value value
9
+ # @param a [String, Numeric] alpha value
10
+ def initialize(format, h, s, v, a = 1)
11
+ s = to_percentage(s)
12
+ v = to_percentage(v)
13
+
14
+ @format = format || :hsv
15
+ @hsv = ColorModes::Hsv.new(h, s, v, a)
16
+ end
17
+
18
+ # Generates a {ColorModes::Rgb}.
19
+ # @return [ColorModes::Rgb]
20
+ def generate
21
+ [Converters::RgbConverter.convert_hsv(@hsv), @format]
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromRgb < Base
5
+ # @param format [Symbol] color format
6
+ # @param rgb [ColorModes::Rgb]
7
+ def initialize(format, rgb)
8
+ @format = format
9
+ @rgb = rgb
10
+ end
11
+
12
+ # Generates a {ColorModes::Rgb}.
13
+ # @return [ColorModes::Rgb]
14
+ def generate
15
+ FromRgbValues.new(@format, @rgb.r, @rgb.g, @rgb.b, @rgb.a).generate
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromRgbValues < Base
5
+ # @param format [Symbol] color format
6
+ # @param r [String, Numeric] red value
7
+ # @param g [String, Numeric] green value
8
+ # @param b [String, Numeric] blue value
9
+ # @param a [String, Numeric] alpha value
10
+ def initialize(format, r, g, b, a = 1)
11
+ @format = format || :rgb
12
+ @r = r
13
+ @g = g
14
+ @b = b
15
+ @a = a
16
+ end
17
+
18
+ # Generates a {ColorModes::Rgb}.
19
+ # @return [ColorModes::Rgb]
20
+ def generate
21
+ r, g, b = [@r, @g, @b].map { |n| bound01(n, 255) * 255 }
22
+ a = bound_alpha(@a)
23
+ [ColorModes::Rgb.new(r, g, b, a), @format]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module RgbGenerator
4
+ class FromString < Base
5
+ # Returns the regex matchers and rgb generation classes for various
6
+ # string color formats.
7
+ #
8
+ # @api private
9
+ # @return [Hash<Symbol, Hash>]
10
+ def self.matchers
11
+ @matchers ||= begin
12
+ # TinyColor.js matchers
13
+ css_int = '[-\\+]?\\d+%?'
14
+ css_num = '[-\\+]?\\d*\\.\\d+%?'
15
+ css_unit = "(?:#{ css_num })|(?:#{ css_int })"
16
+ permissive_prefix = '[\\s|\\(]+('
17
+ permissive_delim = ')[,|\\s]+('
18
+ permissive_suffix = ')\\s*\\)?'
19
+ permissive_match3 = "#{ permissive_prefix }#{ [css_unit] * 3 * permissive_delim }#{ permissive_suffix }"
20
+ permissive_match4 = "#{ permissive_prefix }#{ [css_unit] * 4 * permissive_delim }#{ permissive_suffix }"
21
+ hex_match = '[0-9a-fA-F]'
22
+
23
+ {
24
+ rgb: { regex: /rgb#{ permissive_match3 }/, class_name: :FromRgbValues },
25
+ rgba: { regex: /rgba#{ permissive_match4 }/, class_name: :FromRgbValues },
26
+ hsl: { regex: /hsl#{ permissive_match3 }/, class_name: :FromHslValues },
27
+ hsla: { regex: /hsla#{ permissive_match4 }/, class_name: :FromHslValues },
28
+ hsv: { regex: /hsv#{ permissive_match3 }/, class_name: :FromHsvValues },
29
+ hsva: { regex: /hsva#{ permissive_match4 }/, class_name: :FromHsvValues },
30
+ hex3: { regex: /^#?#{ "(#{ hex_match }{1})" * 3 }$/, class_name: :FromHexStringValues, builder: :from_hex3 },
31
+ hex6: { regex: /^#?#{ "(#{ hex_match }{2})" * 3 }$/, class_name: :FromHexStringValues, builder: :from_hex6 },
32
+ hex8: { regex: /^#?#{ "(#{ hex_match }{2})" * 4 }$/, class_name: :FromHexStringValues, builder: :from_hex8 }
33
+ }.freeze
34
+ end
35
+ end
36
+
37
+ # @param format [Symbol] unused
38
+ # @param input [String] input to parse
39
+ def initialize(_format, input)
40
+ @input = normalize_input(input)
41
+ end
42
+
43
+ # Generates a {ColorModes::Rgb}.
44
+ # @return [ColorModes::Rgb]
45
+ def generate
46
+ get_generator.generate
47
+ end
48
+
49
+ private
50
+
51
+ def get_generator
52
+ if color = Kodachroma.hex_from_name(@input)
53
+ format = :name
54
+ elsif @input == 'transparent'
55
+ return FromRgbValues.new(:name, 0, 0, 0, 0)
56
+ else
57
+ format = nil
58
+ color = @input
59
+ end
60
+
61
+ match = nil
62
+
63
+ _, hash = matchers.find do |_, h|
64
+ !(match = h[:regex].match(color)).nil?
65
+ end
66
+
67
+ if match.nil?
68
+ raise Errors::UnrecognizedColor, "Unrecognized color `#{ color }'"
69
+ end
70
+
71
+ build_generator(match[1..-1], hash[:class_name], hash[:builder], format)
72
+ end
73
+
74
+ def build_generator(args, class_name, builder, format)
75
+ builder ||= :new
76
+ klass = RgbGenerator.const_get(class_name)
77
+ klass.__send__(builder, *([format] + args))
78
+ end
79
+
80
+ def normalize_input(input)
81
+ input.strip.downcase
82
+ end
83
+
84
+ def matchers
85
+ self.class.matchers
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ # Main module to generate an instance of {ColorModes::Rgb} from several
4
+ # possible inputs.
5
+ module RgbGenerator
6
+ class << self
7
+ # Generates an instance of {ColorModes::Rgb} as well as color format
8
+ # symbol.
9
+ #
10
+ # @param input [String, ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
11
+ # @return [[ColorModes::Rgb, Symbol]]
12
+ def generate_rgb_and_format(input)
13
+ get_generator(input).generate.tap do |(rgb)|
14
+ rgb.r = round(rgb.r)
15
+ rgb.g = round(rgb.g)
16
+ rgb.b = round(rgb.b)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def get_generator(input)
23
+ klass = case input
24
+ when String then FromString
25
+ when ColorModes::Hsl then FromHsl
26
+ when ColorModes::Hsv then FromHsv
27
+ when ColorModes::Rgb then FromRgb
28
+ end
29
+
30
+ klass.new(nil, input)
31
+ end
32
+
33
+ def round(n)
34
+ n < 1 ? n.round : n
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ VERSION = '1.0.0'
4
+ end
data/lib/kodachroma.rb ADDED
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+ # General
3
+ require 'kodachroma/version'
4
+ require 'kodachroma/errors'
5
+ require 'yaml'
6
+
7
+ # Modules
8
+ require 'kodachroma/helpers/bounders'
9
+
10
+ # Color
11
+ require 'kodachroma/color/attributes'
12
+ require 'kodachroma/color/serializers'
13
+ require 'kodachroma/color/modifiers'
14
+ require 'kodachroma/color'
15
+ require 'kodachroma/color_modes'
16
+
17
+ # Palettes
18
+ require 'kodachroma/harmonies'
19
+ require 'kodachroma/palette_builder'
20
+
21
+ # RGB Generators
22
+ require 'kodachroma/rgb_generator'
23
+ require 'kodachroma/rgb_generator/base'
24
+ require 'kodachroma/rgb_generator/from_string'
25
+ require 'kodachroma/rgb_generator/from_rgb_values'
26
+ require 'kodachroma/rgb_generator/from_rgb'
27
+ require 'kodachroma/rgb_generator/from_hsl_values'
28
+ require 'kodachroma/rgb_generator/from_hsl'
29
+ require 'kodachroma/rgb_generator/from_hsv_values'
30
+ require 'kodachroma/rgb_generator/from_hsv'
31
+ require 'kodachroma/rgb_generator/from_hex_string_values'
32
+
33
+ # Converters
34
+ require 'kodachroma/converters/base'
35
+ require 'kodachroma/converters/rgb_converter'
36
+ require 'kodachroma/converters/hsl_converter'
37
+ require 'kodachroma/converters/hsv_converter'
38
+
39
+ # The main module.
40
+ module Kodachroma
41
+ class << self
42
+ # Returns a new instance of color. Supports hexadecimal, rgb, rgba, hsl,
43
+ # hsla, hsv, hsva, and named color formats.
44
+ #
45
+ # @api public
46
+ #
47
+ # @example
48
+ # Kodachroma.paint('red')
49
+ # Kodachroma.paint('#f00')
50
+ # Kodachroma.paint('#ff0000')
51
+ # Kodachroma.paint('rgb(255, 0, 0)')
52
+ # Kodachroma.paint('hsl(0, 100%, 50%)')
53
+ # Kodachroma.paint('hsv(0, 100%, 100%)')
54
+ #
55
+ # @param input [String] the color
56
+ # @return [Color] an instance of {Color}
57
+ def paint(input)
58
+ Color.new(input)
59
+ end
60
+
61
+ # Returns the hexadecimal string representation of a named color and nil
62
+ # if no match is found. Favors 3-character hexadecimal if possible.
63
+ #
64
+ # @example
65
+ # Kodachroma.hex_from_name('red') #=> 'f00'
66
+ # Kodachroma.hex_from_name('aliceblue') #=> 'f0f8ff'
67
+ # Kodachroma.hex_from_name('foo') #=> nil
68
+ #
69
+ # @param name [String] the color name
70
+ # @return [String, nil] the color as a string hexadecimal or nil
71
+ def hex_from_name(name)
72
+ named_colors_map[name]
73
+ end
74
+
75
+ # Returns the color name of a hexadecimal color if available and nil if no
76
+ # match is found. Requires 3-character hexadecimal input for applicable
77
+ # colors.
78
+ #
79
+ # @example
80
+ # Kodachroma.name_from_hex('f00') #=> 'red'
81
+ # Kodachroma.name_from_hex('f0f8ff') #=> 'aliceblue'
82
+ # Kodachroma.name_from_hex('123123') #=> nil
83
+ #
84
+ # @param hex [String] the hexadecimal color
85
+ # @return [String, nil] the color name or nil
86
+ def name_from_hex(hex)
87
+ hex_named_colors_map[hex]
88
+ end
89
+
90
+ # Defines a custom palette for use by {Color#palette}. Uses a DSL inside
91
+ # `block` that mirrors the methods in {Color::Modifiers}.
92
+ #
93
+ # @example
94
+ # 'red'.paint.palette.respond_to? :my_palette #=> false
95
+ #
96
+ # Kodachroma.define_palette :my_palette do
97
+ # spin 60
98
+ # spin 120
99
+ # spin 240
100
+ # end
101
+ #
102
+ # 'red'.paint.palette.respond_to? :my_palette #=> true
103
+ #
104
+ # @param name [Symbol, String] the name of the custom palette
105
+ # @param block [Proc] the palette definition block
106
+ # @raise [Errors::PaletteDefinedError] if the palette is already defined
107
+ # @return [Symbol, String] the name of the custom palette
108
+ def define_palette(name, &block)
109
+ if Harmonies.method_defined? name
110
+ raise Errors::PaletteDefinedError, "Palette `#{ name }' already exists"
111
+ end
112
+
113
+ palette_evaluator = PaletteBuilder.build(&block)
114
+
115
+ Harmonies.send(:define_method, name) do
116
+ palette_evaluator.evaluate(@color)
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def hex_named_colors_map
123
+ @hex_named_colors_map ||= named_colors_map.invert
124
+ end
125
+
126
+ def named_colors_map
127
+ @named_colors ||= YAML.load_file(File.expand_path('support/named_colors.yml', __dir__))
128
+ end
129
+ end
130
+ end