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,124 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ class Color
4
+ # Methods that return a new modified {Color}.
5
+ module Modifiers
6
+ # Lightens the color by the given `amount`.
7
+ #
8
+ # @example
9
+ # 'red'.paint.lighten #=> #ff3333
10
+ # 'red'.paint.lighten(20) #=> #ff6666
11
+ #
12
+ # @param amount [Fixnum]
13
+ # @return [Color]
14
+ def lighten(amount = 10)
15
+ hsl = self.hsl
16
+ hsl.l = clamp01(hsl.l + (amount / 100.0))
17
+ self.class.new(hsl, @format)
18
+ end
19
+
20
+ # Brightens the color by the given `amount`.
21
+ #
22
+ # @example
23
+ # 'red'.paint.brighten #=> #ff1a1a
24
+ # 'red'.paint.brighten(20) #=> #ff3333
25
+ #
26
+ # @param amount [Fixnum]
27
+ # @return [Color]
28
+ def brighten(amount = 10)
29
+ # Don't include alpha
30
+ rgb = @rgb.to_a[0..2]
31
+ amount = (255 * (-amount / 100.0)).round
32
+
33
+ rgb.map! do |n|
34
+ [0, [255, n - amount].min].max
35
+ end
36
+
37
+ self.class.new(ColorModes::Rgb.new(*rgb), @format)
38
+ end
39
+
40
+ # Darkens the color by the given `amount`.
41
+ #
42
+ # @example
43
+ # 'red'.paint.darken #=> #cc0000
44
+ # 'red'.paint.darken(20) #=> #990000
45
+ #
46
+ # @param amount [Fixnum]
47
+ # @return [Color]
48
+ def darken(amount = 10)
49
+ hsl = self.hsl
50
+ hsl.l = clamp01(hsl.l - (amount / 100.0))
51
+ self.class.new(hsl, @format)
52
+ end
53
+
54
+ # Desaturates the color by the given `amount`.
55
+ #
56
+ # @example
57
+ # 'red'.paint.desaturate #=> #f20d0d
58
+ # 'red'.paint.desaturate(20) #=> #e61919
59
+ #
60
+ # @param amount [Fixnum]
61
+ # @return [Color]
62
+ def desaturate(amount = 10)
63
+ hsl = self.hsl
64
+ hsl.s = clamp01(hsl.s - (amount / 100.0))
65
+ self.class.new(hsl, @format)
66
+ end
67
+
68
+ # Saturates the color by the given `amount`.
69
+ #
70
+ # @example
71
+ # '#123'.paint.saturate #=> #0e2236
72
+ # '#123'.paint.saturate(20) #=> #0a223a
73
+ #
74
+ # @param amount [Fixnum]
75
+ # @return [Color]
76
+ def saturate(amount = 10)
77
+ hsl = self.hsl
78
+ hsl.s = clamp01(hsl.s + (amount / 100.0))
79
+ self.class.new(hsl, @format)
80
+ end
81
+
82
+ # Converts the color to grayscale.
83
+ #
84
+ # @example
85
+ # 'green'.paint.grayscale #=> #404040
86
+ #
87
+ # @return [Color]
88
+ def grayscale
89
+ desaturate(100)
90
+ end
91
+
92
+ alias greyscale grayscale
93
+
94
+ # Sets color opacity to the given 'amount'.
95
+ #
96
+ # @example
97
+ # 'red'.paint.opacity(0.5) #=> #ff0000
98
+ # 'red'.paint.opacity(0.5).to_rgb #=> 'rgba(255, 0, 0, 0.5)'
99
+ #
100
+ # @param amount [Fixnum]
101
+ # @return [Color]
102
+ def opacity(amount)
103
+ rgb = @rgb.to_a[0..2] + [amount]
104
+ self.class.new(ColorModes::Rgb.new(*rgb), @format)
105
+ end
106
+
107
+ # Spins around the hue color wheel by `amount` in degrees.
108
+ #
109
+ # @example
110
+ # 'red'.paint.spin(30) #=> #ff8000
111
+ # 'red'.paint.spin(60) #=> yellow
112
+ # 'red'.paint.spin(90) #=> #80ff00
113
+ #
114
+ # @param amount [Fixnum]
115
+ # @return [Color]
116
+ def spin(amount)
117
+ hsl = self.hsl
118
+ hue = (hsl.h.round + amount) % 360
119
+ hsl.h = hue.negative? ? 360 + hue : hue
120
+ self.class.new(hsl, @format)
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ class Color
4
+ # Methods for serializing {Color} to different color mode string formats.
5
+ module Serializers
6
+ # Convert to hsv string.
7
+ #
8
+ # @example
9
+ # 'red'.paint.to_hsv #=> 'hsv(0, 100%, 100%)'
10
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_hsv #=> 'hsva(0, 100%, 100%, 0.5)'
11
+ #
12
+ # @return [String]
13
+ def to_hsv
14
+ to_hs(:v)
15
+ end
16
+
17
+ # Convert to hsl string.
18
+ #
19
+ # @example
20
+ # 'red'.paint.to_hsl #=> 'hsl(0, 100%, 50%)'
21
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_hsl #=> 'hsla(0, 100%, 50%, 0.5)'
22
+ #
23
+ # @return [String]
24
+ def to_hsl
25
+ to_hs(:l)
26
+ end
27
+
28
+ # Convert to hexadecimal string.
29
+ #
30
+ # @example
31
+ # 'red'.paint.to_hex #=> '#ff0000'
32
+ # 'red'.paint.to_hex(true) #=> '#f00'
33
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_hex #=> '#ff0000'
34
+ #
35
+ # @param allow_3 [true, false] output 3-character hexadecimal
36
+ # if possible
37
+ # @return [String]
38
+ def to_hex(allow_3 = false)
39
+ "##{ to_basic_hex(allow_3) }"
40
+ end
41
+
42
+ # Convert to 8-character hexadecimal string. The highest order byte
43
+ # (left most hexadecimal pair represents the alpha value).
44
+ #
45
+ # @example
46
+ # 'red'.paint.to_hex #=> '#ffff0000'
47
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_hex #=> '#80ff0000'
48
+ #
49
+ # @return [String]
50
+ def to_hex8
51
+ "##{ to_basic_hex8 }"
52
+ end
53
+
54
+ # Convert to rgb string.
55
+ #
56
+ # @example
57
+ # 'red'.paint.to_rgb #=> 'rgb(255, 0, 0)'
58
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_rgb #=> 'rgb(255, 0, 0, 0.5)'
59
+ #
60
+ # @return [String]
61
+ def to_rgb
62
+ middle = @rgb.to_a[0..2].map(&:round).join(', ')
63
+
64
+ with_alpha(:rgb, middle)
65
+ end
66
+
67
+ # Convert to named color if possible. If a color name can't be found, it
68
+ # returns `'<unknown>'` or the hexadecimal string based on the value of
69
+ # `hex_for_unknown`.
70
+ #
71
+ # @example
72
+ # 'red'.paint.to_name #=> 'red'
73
+ # 'rgba(255, 0, 0, 0.5)'.paint.to_name #=> '<unknown>'
74
+ # '#00f'.paint.to_name #=> 'blue'
75
+ # '#123'.paint.to_name(true) #=> '#112233'
76
+ #
77
+ # @param hex_for_unknown [true, false] determine how unknown color names
78
+ # should be returned
79
+ # @return [String]
80
+ def to_name(hex_for_unknown = false)
81
+ return 'transparent' if alpha.zero?
82
+
83
+ if alpha < 1 || (name = Kodachroma.name_from_hex(to_basic_hex(true))).nil?
84
+ if hex_for_unknown
85
+ to_hex
86
+ else
87
+ '<unknown>'
88
+ end
89
+ else
90
+ name
91
+ end
92
+ end
93
+
94
+ # Convert to a string based on the color format.
95
+ #
96
+ # @example
97
+ # 'red'.paint.to_s #=> 'red'
98
+ # 'rgb(255, 0, 0)'.paint.to_s #=> 'rgb(255, 0, 0)'
99
+ # '#f00'.paint.to_s #=> '#f00'
100
+ # '#80ff0000'.paint.to_s(:rgb) #=> 'rgba(255, 0, 0, 0.5)'
101
+ #
102
+ # @param format [Symbol] the color format
103
+ # @return [String]
104
+ def to_s(format = @format)
105
+ use_alpha = alpha < 1 && alpha >= 0 && /^hex(3|6)?$/ =~ format
106
+
107
+ return to_rgb if use_alpha
108
+
109
+ case format.to_s
110
+ when 'rgb' then to_rgb
111
+ when 'hex', 'hex6' then to_hex
112
+ when 'hex3' then to_hex(true)
113
+ when 'hex8' then to_hex8
114
+ when 'hsl' then to_hsl
115
+ when 'hsv' then to_hsv
116
+ when 'name' then to_name(true)
117
+ else to_hex
118
+ end
119
+ end
120
+
121
+ alias inspect to_s
122
+
123
+ # Converts to an instance of {ColorModes::Hsv}
124
+ # @return [ColorModes::Hsv]
125
+ def hsv
126
+ Converters::HsvConverter.convert_rgb(@rgb)
127
+ end
128
+
129
+ # Converts to an instance of {ColorModes::Hsl}
130
+ # @return [ColorModes::Hsl]
131
+ def hsl
132
+ Converters::HslConverter.convert_rgb(@rgb)
133
+ end
134
+
135
+ # Converts to an instance of {ColorModes::Rgb}
136
+ # @return [ColorModes::Rgb]
137
+ attr_reader :rgb
138
+
139
+ private
140
+
141
+ def to_basic_hex(allow_3 = false)
142
+ r, g, b = [@rgb.r, @rgb.g, @rgb.b].map do |n|
143
+ to_2char_hex(n)
144
+ end
145
+
146
+ if allow_3 && r[0] == r[1] && g[0] == g[1] && b[0] == b[1]
147
+ return "#{ r[0] }#{ g[0] }#{ b[0] }"
148
+ end
149
+
150
+ ([r, g, b].flatten * '').to_s
151
+ end
152
+
153
+ def to_basic_hex8
154
+ [
155
+ to_2char_hex(alpha * 255),
156
+ to_2char_hex(@rgb.r),
157
+ to_2char_hex(@rgb.g),
158
+ to_2char_hex(@rgb.b)
159
+ ].join
160
+ end
161
+
162
+ def to_hs(third)
163
+ name = "hs#{ third }"
164
+ color = send(name)
165
+
166
+ h = color.h.round
167
+ s = (color.s * 100).round
168
+ lv = (color.send(third) * 100).round
169
+
170
+ middle = "#{ h }, #{ s }%, #{ lv }%"
171
+
172
+ with_alpha(name, middle)
173
+ end
174
+
175
+ def with_alpha(mode, middle)
176
+ if alpha < 1
177
+ "#{ mode }a(#{ middle }, #{ rounded_alpha })"
178
+ else
179
+ "#{ mode }(#{ middle })"
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ # The main class to represent colors.
4
+ class Color
5
+ include Attributes
6
+ include Serializers
7
+ include Modifiers
8
+ include Helpers::Bounders
9
+
10
+ # @param input [String, ColorModes::Rgb, ColorModes::Hsl, ColorModes::Hsv]
11
+ # @param format [Symbol] the color mode format
12
+ def initialize(input, format = nil)
13
+ @input = input
14
+ @rgb, gen_format = generate_rgb_and_format(input)
15
+ @format = format || gen_format
16
+ end
17
+
18
+ # Returns self. Useful for ducktyping situations with {String#paint}.
19
+ #
20
+ # @example
21
+ # red = 'red'.paint
22
+ #
23
+ # red.paint #=> red
24
+ # red.paint.equal? red #=> true
25
+ #
26
+ # @return [self]
27
+ def paint
28
+ self
29
+ end
30
+
31
+ # Returns true if `self` is equal to `other` and they're both instances of
32
+ # {Color}.
33
+ #
34
+ # @example
35
+ # red = 'red'.paint
36
+ # blue = 'blue'.paint
37
+ #
38
+ # red.eql? red #=> true
39
+ # red.eql? blue #=> false
40
+ # red.eql? '#f00'.paint #=> true
41
+ #
42
+ # @param other [Color]
43
+ # @return [true, false]
44
+ def eql?(other)
45
+ self.class == other.class && self == other
46
+ end
47
+
48
+ # Returns true if both are equal in value.
49
+ #
50
+ # @example
51
+ # red = 'red'.paint
52
+ # blue = 'blue'.paint
53
+ #
54
+ # red == red #=> true
55
+ # red == blue #=> false
56
+ # red == '#f00'.paint #=> true
57
+ #
58
+ # @param other [Color]
59
+ # @return [true, false]
60
+ def ==(other)
61
+ to_hex == other.to_hex
62
+ end
63
+
64
+ # Returns the complementary color.
65
+ #
66
+ # @example
67
+ # 'red'.paint.complement #=> cyan
68
+ #
69
+ # @return [Color] the complementary color
70
+ def complement
71
+ hsl = self.hsl
72
+ hsl.h = (hsl.h + 180) % 360
73
+ self.class.new(hsl, @format)
74
+ end
75
+
76
+ # Returns an instance of {Harmonies} from which to call a palette method.
77
+ #
78
+ # @example
79
+ # 'red'.paint.palette #=> #<Kodachroma::Harmonies:0x007faf6b9f9148 @color=red>
80
+ #
81
+ # @return [Harmonies]
82
+ def palette
83
+ Harmonies.new(self)
84
+ end
85
+
86
+ # Defines a custom palette and immediately returns it. Uses a DSL inside
87
+ # `block` that mirrors the methods in {Color::Modifiers}.
88
+ #
89
+ # @example
90
+ # 'red'.paint.custom_palette do
91
+ # spin 60
92
+ # spin 180
93
+ # end
94
+ # #=> [red, yellow, cyan]
95
+ #
96
+ # @param block [Proc] the palette definition block
97
+ # @return [Array<Color>] palette array of colors
98
+ def custom_palette(&block)
99
+ PaletteBuilder.build(&block).evaluate(self)
100
+ end
101
+
102
+ private
103
+
104
+ def to_2char_hex(n)
105
+ n.round.to_s(16).rjust(2, '0')
106
+ end
107
+
108
+ def generate_rgb_and_format(input)
109
+ RgbGenerator.generate_rgb_and_format(input)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+ module Kodachroma
3
+ module ColorModes
4
+ class << self
5
+ private
6
+
7
+ # Builds a new color mode class.
8
+ #
9
+ # @param name [String] the class name
10
+ # @param attrs [Array<Symbol>] the instance attribute names
11
+ # @!macro [attach] build
12
+ # @!parse class $1
13
+ # attr_accessor :$2, :$3, :$4, :a
14
+ #
15
+ # # @param $2 [Numeric]
16
+ # # @param $3 [Numeric]
17
+ # # @param $4 [Numeric]
18
+ # # @param a [Numeric]
19
+ # def initialize(${2-4}, a = 1)
20
+ # @$2, @$3, @$4, @a = $2, $3, $4, a
21
+ # end
22
+ #
23
+ # # Returns the values `$2`, `$3`, `$4`, and `a` as an array.
24
+ # #
25
+ # # @return [Array<Numeric>]
26
+ # def to_a
27
+ # [@$2, @$3, @$4, @a]
28
+ # end
29
+ #
30
+ # alias_method :to_ary, :to_a
31
+ # end
32
+ def build(name, *attrs)
33
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
34
+ class #{ name }
35
+ attr_accessor #{ (attrs + [:a]).map { |attr| ":#{ attr }" } * ', ' }
36
+
37
+ def initialize(#{ attrs * ', ' }, a = 1)
38
+ #{ attrs.map { |attr| "@#{ attr }" } * ', ' }, @a = #{ attrs * ', ' }, a
39
+ end
40
+
41
+ def to_a
42
+ [#{ attrs.map { |attr| "@#{ attr }" } * ', ' }, @a]
43
+ end
44
+
45
+ alias_method :to_ary, :to_a
46
+ end
47
+ EOS
48
+ end
49
+ end
50
+
51
+ build 'Rgb', :r, :g, :b
52
+ build 'Hsl', :h, :s, :l
53
+ build 'Hsv', :h, :s, :v
54
+ end
55
+ end