kodachroma 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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