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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.org +401 -0
- data/lib/kodachroma/color/attributes.rb +59 -0
- data/lib/kodachroma/color/modifiers.rb +124 -0
- data/lib/kodachroma/color/serializers.rb +184 -0
- data/lib/kodachroma/color.rb +112 -0
- data/lib/kodachroma/color_modes.rb +55 -0
- data/lib/kodachroma/converters/base.rb +34 -0
- data/lib/kodachroma/converters/hsl_converter.rb +55 -0
- data/lib/kodachroma/converters/hsv_converter.rb +49 -0
- data/lib/kodachroma/converters/rgb_converter.rb +72 -0
- data/lib/kodachroma/errors.rb +8 -0
- data/lib/kodachroma/harmonies.rb +139 -0
- data/lib/kodachroma/helpers/bounders.rb +50 -0
- data/lib/kodachroma/palette_builder.rb +80 -0
- data/lib/kodachroma/rgb_generator/base.rb +10 -0
- data/lib/kodachroma/rgb_generator/from_hex_string_values.rb +63 -0
- data/lib/kodachroma/rgb_generator/from_hsl.rb +19 -0
- data/lib/kodachroma/rgb_generator/from_hsl_values.rb +25 -0
- data/lib/kodachroma/rgb_generator/from_hsv.rb +19 -0
- data/lib/kodachroma/rgb_generator/from_hsv_values.rb +25 -0
- data/lib/kodachroma/rgb_generator/from_rgb.rb +19 -0
- data/lib/kodachroma/rgb_generator/from_rgb_values.rb +27 -0
- data/lib/kodachroma/rgb_generator/from_string.rb +89 -0
- data/lib/kodachroma/rgb_generator.rb +38 -0
- data/lib/kodachroma/version.rb +4 -0
- data/lib/kodachroma.rb +130 -0
- data/lib/support/named_colors.yml +149 -0
- metadata +186 -0
@@ -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
|