pigment 0.2.1 → 0.3.1
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/CHANGELOG.md +18 -0
- data/LICENSE.md +22 -0
- data/README.md +182 -1
- data/lib/pigment.rb +3 -307
- data/lib/pigment/color.rb +195 -0
- data/lib/pigment/color/hsl.rb +158 -0
- data/lib/pigment/color/invalid_color_format_error.rb +10 -0
- data/lib/pigment/color/rgb.rb +283 -0
- data/lib/pigment/default_rgb_palette.rb +891 -0
- data/lib/pigment/float_snap.rb +14 -0
- data/lib/pigment/palette.rb +100 -0
- data/lib/pigment/version.rb +3 -0
- metadata +103 -22
- data/lib/colors.rb +0 -755
@@ -0,0 +1,195 @@
|
|
1
|
+
require_relative '../pigment'
|
2
|
+
|
3
|
+
module Pigment
|
4
|
+
# Represent an abstract color in any format
|
5
|
+
module Color
|
6
|
+
class << self
|
7
|
+
# @param [#to_f] red
|
8
|
+
# @param [#to_f] blue
|
9
|
+
# @param [#to_f] green
|
10
|
+
# @param [#to_f] alpha
|
11
|
+
# @return [Pigment::Color::RGB]
|
12
|
+
def rgba(red, blue, green, alpha = 1.0)
|
13
|
+
Pigment::Color::RGB.new(red, blue, green, alpha)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Integer] hue between 0 and 360 degrees
|
17
|
+
# @param [#to_f] saturation between 0.0 and 1.0
|
18
|
+
# @param [#to_f] lightness between 0.0 and 1.0
|
19
|
+
# @param [#to_f] alpha between 0.0 and 1.0
|
20
|
+
# @return [Pigment::Color::HSL]
|
21
|
+
def hsla(hue, saturation, lightness, alpha = 1.0)
|
22
|
+
Pigment::Color::HSL.from_hue_angle(hue, saturation, lightness, alpha)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :rgb, :rgba
|
26
|
+
alias_method :hsl, :hsla
|
27
|
+
|
28
|
+
# sets aliases for any color format
|
29
|
+
# @param [Class] klass
|
30
|
+
def included(klass)
|
31
|
+
klass.alias_method :gray?, :grayscale?
|
32
|
+
klass.alias_method :-@, :inverse
|
33
|
+
klass.alias_method :inv, :inverse
|
34
|
+
klass.alias_method :invert, :inverse
|
35
|
+
klass.alias_method :complementary, :inverse
|
36
|
+
klass.alias_method :complement, :inverse
|
37
|
+
klass.alias_method :to_floats, :to_a
|
38
|
+
klass.alias_method :to_f, :to_a
|
39
|
+
klass.alias_method :inspect, :to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [Class] color_type
|
44
|
+
# @return [Pigment::Color]
|
45
|
+
def into(color_type)
|
46
|
+
color_type.convert(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
# @return [Pigment::Color]
|
50
|
+
def dup
|
51
|
+
self.class.new(*to_a)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Pigment::Color]
|
55
|
+
def inverse
|
56
|
+
into(Pigment::Color::RGB).inverse.into(self.class)
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param [Object] other
|
60
|
+
# @return [Boolean]
|
61
|
+
def inverse?(other)
|
62
|
+
other.is_a?(Pigment::Color) && self.inverse == other
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Array<Pigment::Color>]
|
66
|
+
def triadic
|
67
|
+
into(Pigment::Color::RGB).triadic.map { |color| color.into(self.class) }
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param [Object] other
|
71
|
+
# @return [Boolean]
|
72
|
+
def triadic_of?(other)
|
73
|
+
other.into(self.class)
|
74
|
+
other.triadic.include?(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param [Array<Object>] others
|
78
|
+
# @return [Boolean]
|
79
|
+
def triadic_include?(*others)
|
80
|
+
colors = triadic
|
81
|
+
others.all? { |color| colors.include?(color) }
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Array<Pigment::Color>]
|
85
|
+
def split
|
86
|
+
into(Pigment::Color::HSL).split.map { |color| color.into(self.class) }
|
87
|
+
end
|
88
|
+
|
89
|
+
# @param [Object] other
|
90
|
+
# @return [Boolean]
|
91
|
+
def split_of?(other)
|
92
|
+
other.split.include?(self)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param [Array<Object>] others
|
96
|
+
# @return [Boolean]
|
97
|
+
def split_include?(*others)
|
98
|
+
colors = split
|
99
|
+
others.all? { |color| colors.include?(color) }
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Array<Pigment::Color>]
|
103
|
+
def analogous
|
104
|
+
into(Pigment::Color::HSL).analogous.map { |color| color.into(self.class) }
|
105
|
+
end
|
106
|
+
|
107
|
+
# @param [Object] other
|
108
|
+
# @return [Boolean]
|
109
|
+
def analogous_of?(other)
|
110
|
+
other.analogous.include?(self)
|
111
|
+
end
|
112
|
+
|
113
|
+
# @param [Array<Object>] others
|
114
|
+
# @return [Boolean]
|
115
|
+
def analogous_include?(*others)
|
116
|
+
colors = analogous
|
117
|
+
others.all? { |color| colors.include?(color) }
|
118
|
+
end
|
119
|
+
|
120
|
+
# @return [Array<Pigment::Color>]
|
121
|
+
def tetradic
|
122
|
+
into(Pigment::Color::HSL).tetradic.map { |color| color.into(self.class) }
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [Object] other
|
126
|
+
# @return [Boolean]
|
127
|
+
def tetradic_of?(other)
|
128
|
+
other.tetradic.include?(self)
|
129
|
+
end
|
130
|
+
|
131
|
+
# @param [Array<Object>] others
|
132
|
+
# @return [Boolean]
|
133
|
+
def tetradic_include?(*others)
|
134
|
+
others.all? { |color| tetradic.include?(color) }
|
135
|
+
end
|
136
|
+
|
137
|
+
# @return [Array<Pigment::Color>]
|
138
|
+
def rectangular
|
139
|
+
into(Pigment::Color::HSL).rectangular.map { |color| color.into(self.class) }
|
140
|
+
end
|
141
|
+
|
142
|
+
# @param [Object] other
|
143
|
+
# @return [Boolean]
|
144
|
+
def rectangular_of?(other)
|
145
|
+
other.rectangular.include?(self)
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [Array<Object>] others
|
149
|
+
# @return [Boolean]
|
150
|
+
def rectangular_include?(*others)
|
151
|
+
colors = rectangular
|
152
|
+
others.all? { |color| colors.include?(color) }
|
153
|
+
end
|
154
|
+
|
155
|
+
# @return [Array<Pigment::Color>]
|
156
|
+
def tertiary
|
157
|
+
into(Pigment::Color::HSL).tertiary.map { |color| color.into(self.class) }
|
158
|
+
end
|
159
|
+
|
160
|
+
# @param [Object] other
|
161
|
+
# @return [Boolean]
|
162
|
+
def tertiary_of?(other)
|
163
|
+
other.tertiary.include?(self)
|
164
|
+
end
|
165
|
+
|
166
|
+
# @param [Array<Object>] others
|
167
|
+
# @return [Boolean]
|
168
|
+
def tertiary_include?(*others)
|
169
|
+
colors = tertiary
|
170
|
+
others.all? { |color| colors.include?(color) }
|
171
|
+
end
|
172
|
+
|
173
|
+
# @param [Boolean] with_alpha
|
174
|
+
# @return [String]
|
175
|
+
def to_html(with_alpha: false)
|
176
|
+
"##{to_hex(with_alpha: with_alpha)}"
|
177
|
+
end
|
178
|
+
|
179
|
+
# @param [Boolean] with_alpha
|
180
|
+
# @return [Array<Integer>]
|
181
|
+
def to_ints(with_alpha: true)
|
182
|
+
into(Pigment::Color::RGB).to_ints(with_alpha: with_alpha)
|
183
|
+
end
|
184
|
+
|
185
|
+
# @param [Boolean] with_alpha
|
186
|
+
# @return [String]
|
187
|
+
def to_hex(with_alpha: true)
|
188
|
+
into(Pigment::Color::RGB).to_hex(with_alpha: with_alpha)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
require_relative 'color/rgb'
|
194
|
+
require_relative 'color/hsl'
|
195
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require_relative '../color'
|
2
|
+
|
3
|
+
module Pigment
|
4
|
+
module Color
|
5
|
+
# Represent a color in the HSL Format
|
6
|
+
class HSL
|
7
|
+
using FloatSnap
|
8
|
+
|
9
|
+
# @return [Float]
|
10
|
+
attr_reader :hue, :saturation, :lightness, :alpha
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# Converts a color into HSL format from any possible format, given they know how to convert to RGB
|
15
|
+
# @param [Pigment::Color] color
|
16
|
+
# @return [Pigment::Color::HSL]
|
17
|
+
def convert(color)
|
18
|
+
return color if color.is_a?(self)
|
19
|
+
color = color.into(Pigment::Color::RGB)
|
20
|
+
rgb = color.rgb
|
21
|
+
r, g, b = rgb
|
22
|
+
|
23
|
+
min = rgb.min
|
24
|
+
max = rgb.max
|
25
|
+
chroma = max - min
|
26
|
+
sum = max + min
|
27
|
+
l = sum / 2.0
|
28
|
+
|
29
|
+
return new(0, 0, l, color.alpha) if chroma == 0.0
|
30
|
+
|
31
|
+
s = l > 0.5 ? chroma / (2.0 - sum) : chroma / sum
|
32
|
+
|
33
|
+
h = case max
|
34
|
+
when r then ((g - b) / chroma) / 6 + (g < b && 1 || 0)
|
35
|
+
when g then ((b - r) / chroma) / 6.0 + (1.0 / 3.0)
|
36
|
+
when b then ((r - g) / chroma) / 6.0 + (2.0 / 3.0)
|
37
|
+
end
|
38
|
+
|
39
|
+
new(h, s, l, color.alpha)
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param [Integer] hue between 0 and 360 degrees
|
43
|
+
# @param [#to_f] saturation between 0.0 and 1.0
|
44
|
+
# @param [#to_f] lightness between 0.0 and 1.0
|
45
|
+
# @param [#to_f] alpha between 0.0 and 1.0
|
46
|
+
# @return [Pigment::Color::HSL]
|
47
|
+
def from_hue_angle(hue, saturation, lightness, alpha = 1.0)
|
48
|
+
new(hue / 360.0, saturation, lightness, alpha)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [#to_f] hue between 0.0 and 1.0 mapping to 0 to 360 degrees
|
53
|
+
# @param [#to_f] saturation between 0.0 and 1.0
|
54
|
+
# @param [#to_f] lightness between 0.0 and 1.0
|
55
|
+
# @param [#to_f] alpha between 0.0 and 1.0
|
56
|
+
# @return [Pigment::Color::HSL]
|
57
|
+
# @raise [InvalidColorFormatError]
|
58
|
+
def initialize(hue, saturation, lightness, alpha = 1.0)
|
59
|
+
@hue = hue % 1.0
|
60
|
+
@saturation = saturation.to_f.snap
|
61
|
+
@lightness = lightness.to_f.snap
|
62
|
+
@alpha = alpha.to_f.snap
|
63
|
+
|
64
|
+
color = to_f(with_alpha: true)
|
65
|
+
raise InvalidColorFormatError, color unless color.all? { |c| c.between?(0.0, 1.0) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Pigment::Color::HSL] the grayscale correspondent of the color
|
69
|
+
def grayscale
|
70
|
+
self.class.new(@hue, 0, @lightness, @alpha)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [Boolean] true if saturation is 0, false otherwise
|
74
|
+
def grayscale?
|
75
|
+
@saturation == 0
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [Array<Pigment::Color>] An array with the triadic colors of the color
|
79
|
+
def triadic
|
80
|
+
[self.class.new(@hue + 1 / 3.0, s, l), self.class.new(@hue + 2 / 3.0, s, l)]
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Array<Pigment::Color>] An array with the split colors of the color
|
84
|
+
def split
|
85
|
+
[self.class.new(@hue - 5 / 12.0, s, l), self.class.new(@hue + 5 / 12.0, s, l)]
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [Array<Pigment::Color>] An array with the analogous colors of the color
|
89
|
+
def analogous
|
90
|
+
[self.class.new(@hue - 1 / 12.0, s, l), self.class.new(@hue + 1 / 12.0, s, l)]
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Array<Pigment::Color>] An array with the tetradic colors of the color
|
94
|
+
def tetradic
|
95
|
+
[
|
96
|
+
self.class.new(@hue + 1 / 4.0, @saturation, @lightness),
|
97
|
+
self.class.new(@hue + 1 / 2.0, @saturation, @lightness),
|
98
|
+
self.class.new(@hue + 3 / 4.0, @saturation, @lightness)
|
99
|
+
]
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [Array<Pigment::Color>] An array with the rectangular colors of the color
|
103
|
+
def rectangular
|
104
|
+
[
|
105
|
+
self.class.new(@hue + 1 / 6.0, @saturation, @lightness),
|
106
|
+
self.class.new(@hue + 1 / 2.0, @saturation, @lightness),
|
107
|
+
self.class.new(@hue + 2 / 3.0, @saturation, @lightness)
|
108
|
+
]
|
109
|
+
end
|
110
|
+
|
111
|
+
# @return [Array<Pigment::Color>] An array with the tertiary colors of the color
|
112
|
+
def tertiary
|
113
|
+
[
|
114
|
+
self.class.new(@hue + 1 / 6.0, @saturation, @lightness),
|
115
|
+
self.class.new(@hue + 1 / 3.0, @saturation, @lightness),
|
116
|
+
self.class.new(@hue + 1 / 2.0, @saturation, @lightness),
|
117
|
+
self.class.new(@hue + 2 / 3.0, @saturation, @lightness),
|
118
|
+
self.class.new(@hue + 5 / 6.0, @saturation, @lightness),
|
119
|
+
]
|
120
|
+
end
|
121
|
+
|
122
|
+
# @param [Boolean] with_alpha
|
123
|
+
# @return [Array<Float>] an array with the color components
|
124
|
+
def to_a(with_alpha: true)
|
125
|
+
with_alpha ? [@hue, @saturation, @lightness, @alpha] : [@hue, @saturation, @lightness]
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [String] the string representation of a hsl color
|
129
|
+
def to_s
|
130
|
+
"HSL Color(hue: #{hue}, saturation: #{saturation}, lightness: #{lightness}, alpha: #{alpha})"
|
131
|
+
end
|
132
|
+
|
133
|
+
# @param [Object] other
|
134
|
+
# @return [Boolean] wether the color is equal to other object
|
135
|
+
def ==(other)
|
136
|
+
other = Pigment::Color::HSL.convert(other)
|
137
|
+
other.hue.snap == @hue.snap &&
|
138
|
+
other.saturation.snap == @saturation.snap &&
|
139
|
+
other.lightness.snap == @lightness.snap &&
|
140
|
+
other.alpha.snap == @alpha.snap
|
141
|
+
end
|
142
|
+
|
143
|
+
alias_method :a, :alpha
|
144
|
+
alias_method :h, :hue
|
145
|
+
alias_method :s, :saturation
|
146
|
+
alias_method :l, :lightness
|
147
|
+
|
148
|
+
include Pigment::Color
|
149
|
+
|
150
|
+
private
|
151
|
+
# @return [Array] an array with the respective hsla components
|
152
|
+
def method_missing(method, *args)
|
153
|
+
super unless method =~ /^[ahsl]+$/ && args.empty?
|
154
|
+
method.to_s.each_char.map { |component| send(component) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
require_relative '../color'
|
2
|
+
|
3
|
+
module Pigment
|
4
|
+
module Color
|
5
|
+
# Represent a color in the RGB Format
|
6
|
+
class RGB
|
7
|
+
using FloatSnap
|
8
|
+
|
9
|
+
# @return [Float]
|
10
|
+
attr_reader :red, :green, :blue, :alpha
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Converts a color into RGB format from any possible format
|
14
|
+
# @param [Pigment::Color] color
|
15
|
+
# @return [Pigment::Color]
|
16
|
+
# @raise [InvalidColorFormatError]
|
17
|
+
def convert(color)
|
18
|
+
case color
|
19
|
+
when RGB then color
|
20
|
+
when HSL then from_hsl(*color)
|
21
|
+
else raise InvalidColorFormatError, color
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a Pigment::Color::RGB from an HTML Hex code String
|
26
|
+
# @param [String, Integer, Float] hex
|
27
|
+
# @return [Pigment::Color::RGB]
|
28
|
+
# @raise [InvalidColorFormatError]
|
29
|
+
def from_hex(hex)
|
30
|
+
case hex
|
31
|
+
when String then from_hex_string(hex)
|
32
|
+
when Integer then from_hex_integer(hex)
|
33
|
+
when Float then from_hex_integer(hex.round)
|
34
|
+
else raise InvalidColorFormatError, hex
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Creates a Pigment::Color from a group of rgba Integers
|
39
|
+
# @param [Integer] red
|
40
|
+
# @param [Integer] green
|
41
|
+
# @param [Integer] blue
|
42
|
+
# @param [Integer] alpha
|
43
|
+
# @return [Pigment::Color::RGB]
|
44
|
+
# @raise [InvalidColorFormatError]
|
45
|
+
def from_rgba_integers(red, green, blue, alpha = 255)
|
46
|
+
color = [red, green, blue, alpha]
|
47
|
+
raise InvalidColorFormatError, color unless color.all? do |c|
|
48
|
+
(0..255).include? c
|
49
|
+
end
|
50
|
+
new(*color.map { |c| c / 255.0 })
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [Integer] red
|
54
|
+
# @param [Integer] green
|
55
|
+
# @param [Integer] blue
|
56
|
+
# @return [Pigment::Color::RGB]
|
57
|
+
def from_rgb_integers(red, green, blue)
|
58
|
+
from_rgba_integers(red, green, blue)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Pigment::Color::RGB] a random generated color
|
62
|
+
def random
|
63
|
+
new(rand(0.0..1.0), rand(0.0..1.0), rand(0.0..1.0))
|
64
|
+
end
|
65
|
+
|
66
|
+
# Suppress an array of floats by dividing by the greatest color component.
|
67
|
+
# @param [Array] color
|
68
|
+
# @return [Array]
|
69
|
+
def suppress(color)
|
70
|
+
return color.map { |c| c / color.max } unless color.max.between?(0.0, 1.0)
|
71
|
+
color
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
# @param [Integer] hex
|
76
|
+
# @return [Pigment::Color::RGB]
|
77
|
+
def from_hex_integer(hex)
|
78
|
+
raise InvalidColorFormatError, hex unless hex.is_a?(Numeric) && hex.between?(0, 0xFFFFFFFF)
|
79
|
+
red = (hex >> 24 & 0xFF)
|
80
|
+
green = (hex >> 16 & 0xFF)
|
81
|
+
blue = (hex >> 8 & 0xFF)
|
82
|
+
alpha = (hex & 0xFF)
|
83
|
+
from_rgba_integers(red, green, blue, alpha)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [String] hex
|
87
|
+
# @raise [InvalidColorFormatError]
|
88
|
+
# @return [Pigment::Color::RGB]
|
89
|
+
def from_hex_string(hex)
|
90
|
+
matches = hex.match(/^#?(?<r>\h{2})(?<g>\h{2})(?<b>\h{2})(?<a>\h{2})?$/)&.named_captures
|
91
|
+
raise InvalidColorFormatError, hex unless matches
|
92
|
+
matches["a"] ||= 'FF'
|
93
|
+
new(*matches.values.map { |value| value.to_i(16) / 255.0 })
|
94
|
+
end
|
95
|
+
|
96
|
+
# Creates a Pigment::Color::RGB form the HSL color System. It's mostly used to calculate harmonic colors.
|
97
|
+
# @param [#to_f] hue between 0.0 and 1.0
|
98
|
+
# @param [#to_f] saturation between 0.0 and 1.0
|
99
|
+
# @param [#to_f] lightness between 0.0 and 1.0
|
100
|
+
# @param [#to_f] alpha between 0.0 and 1.0
|
101
|
+
# @return [Pigment::Color::RGB]
|
102
|
+
def from_hsl(hue, saturation, lightness, alpha = 1.0)
|
103
|
+
return new(lightness, lightness, lightness, alpha) if saturation == 0
|
104
|
+
v2 = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - saturation * lightness
|
105
|
+
v1 = 2 * lightness - v2
|
106
|
+
color = [hue + (1 / 3.0), hue, hue - (1 / 3.0)].map do |hv|
|
107
|
+
hv = hv < 0 ? hv + 1
|
108
|
+
: hv > 1 ? hv - 1
|
109
|
+
: hv
|
110
|
+
|
111
|
+
case
|
112
|
+
when 6 * hv < 1 then v1 + (v2 - v1) * 6 * hv
|
113
|
+
when 2 * hv < 1 then v2
|
114
|
+
when 3 * hv < 2 then v1 + (v2 - v1) * ((2 / 3.0) - hv) * 6
|
115
|
+
else v1
|
116
|
+
end
|
117
|
+
end
|
118
|
+
color << alpha
|
119
|
+
new(*color.map { |c| c.round(2) })
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Pigment uses sRGB or sRGBA as the default color system
|
124
|
+
# Pigment::Color::RGB is represented as an array of floats, which are ranged from 0.0 to 1.0
|
125
|
+
# @param [Float, Integer] red between 0.0 and 1.0
|
126
|
+
# @param [Float, Integer] green between 0.0 and 1.0
|
127
|
+
# @param [Float, Integer] blue between 0.0 and 1.0
|
128
|
+
# @param [Float, Integer] alpha between 0.0 and 1.0
|
129
|
+
# @return [Pigment::Color::RGB]
|
130
|
+
# @raise [InvalidColorFormatError]
|
131
|
+
def initialize(red, green, blue, alpha = 1.0)
|
132
|
+
@red = red.to_f.snap
|
133
|
+
@green = green.to_f.snap
|
134
|
+
@blue = blue.to_f.snap
|
135
|
+
@alpha = alpha.to_f
|
136
|
+
|
137
|
+
color = to_floats(with_alpha: true)
|
138
|
+
raise InvalidColorFormatError, color unless color.all? { |c| c.between?(0.0, 1.0) }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Sums all the two colors components. If any component gets out of the 0 to 1.0 range it gets suppressed.
|
142
|
+
# @param [Pigment::Color] color
|
143
|
+
# @return [Pigment::Color::RGB]
|
144
|
+
# @raise [InvalidColorFormatError]
|
145
|
+
def +(color)
|
146
|
+
raise InvalidColorFormatError, color unless color.is_a?(Pigment::Color)
|
147
|
+
color = color.into(self.class)
|
148
|
+
color = [
|
149
|
+
@red + color.red,
|
150
|
+
@green + color.green,
|
151
|
+
@blue + color.blue
|
152
|
+
]
|
153
|
+
|
154
|
+
self.class.new(*self.class.suppress(color), @alpha)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Subtracts all the two color components. If any component gets out of the 0 to 1.0 range it gets suppressed.
|
158
|
+
# Tone component will be 0 if it gets lower than 0
|
159
|
+
# @param [Pigment::Color] color
|
160
|
+
# @return [Pigment::Color::RGB]
|
161
|
+
# @raise [InvalidColorFormatError]
|
162
|
+
def -(color)
|
163
|
+
raise InvalidColorFormatError, color unless color.is_a?(Pigment::Color)
|
164
|
+
color = color.into(self.class)
|
165
|
+
self.class.new(*self.class.suppress([
|
166
|
+
@red - color.red,
|
167
|
+
@green - color.green,
|
168
|
+
@blue - color.blue
|
169
|
+
].map { |c| c >= 0 ? c : 0 }), @alpha)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Multiplies all the color components by n. If any component gets out of the 0 to 1.0 range it gets suppressed.
|
173
|
+
# @param [Numeric] n
|
174
|
+
# @return [Pigment::Color::RGB]
|
175
|
+
def *(n)
|
176
|
+
raise ArgumentError, "Expecting Numeric. Given #{n.class}" unless n.is_a? Numeric
|
177
|
+
n = rgb.map { |c| c * n.to_f }
|
178
|
+
self.class.new(*self.class.suppress(n), @alpha)
|
179
|
+
end
|
180
|
+
|
181
|
+
# Divides all the color components by n. If any component gets out of the 0 to 1.0 range it gets suppressed.
|
182
|
+
# @param [Numeric] n
|
183
|
+
# @return [Pigment::Color::RGB]
|
184
|
+
def /(n)
|
185
|
+
raise ArgumentError, "Expecting Numeric. Given #{n.class}" unless n.is_a? Numeric
|
186
|
+
n = rgb.map { |c| c / n.to_f }
|
187
|
+
self.class.new(*self.class.suppress(n), @alpha)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Test if two colors are equal
|
191
|
+
# @param [Pigment::Color] other
|
192
|
+
# @return [Boolean]
|
193
|
+
def ==(other)
|
194
|
+
return false unless other.is_a?(Pigment::Color)
|
195
|
+
other = Pigment::Color::RGB.convert(other)
|
196
|
+
other.red.snap == @red.snap &&
|
197
|
+
other.blue.snap == @blue.snap &&
|
198
|
+
other.green.snap == @green.snap &&
|
199
|
+
other.alpha.snap == @alpha.snap
|
200
|
+
end
|
201
|
+
|
202
|
+
# @return [Pigment::Color::RGB] the grayscale correspondent of the color
|
203
|
+
def grayscale
|
204
|
+
gray = (@red + @green + @blue) / 3.0
|
205
|
+
self.class.new(gray, gray, gray, @alpha)
|
206
|
+
end
|
207
|
+
|
208
|
+
# @return [Boolean] true if all components are the same, false otherwise
|
209
|
+
def grayscale?
|
210
|
+
@red == @green && @green == @blue
|
211
|
+
end
|
212
|
+
|
213
|
+
# Interpolates two colors. Amount must be an float between -1.0 and 1.0
|
214
|
+
# @param [Pigment::Color] color
|
215
|
+
# @param [Float] amount
|
216
|
+
# @return [Pigment::Color::RGB]
|
217
|
+
# @raise [InvalidColorFormatError]
|
218
|
+
def interpolate(color, amount = 0.5)
|
219
|
+
raise InvalidColorFormatError, color unless color.is_a?(Pigment::Color)
|
220
|
+
raise ArgumentError, "Amount must be an float between -1.0 and 1.0, got #{amount}" unless (-1.0..1.0).include?(amount)
|
221
|
+
color = color.into(Pigment::Color::RGB)
|
222
|
+
n = [rgb, color.rgb].transpose.map! { |c, d| c + amount * (d - c) }
|
223
|
+
self.class.new(*self.class.suppress(n))
|
224
|
+
end
|
225
|
+
|
226
|
+
# @return [RGB] the Invert color
|
227
|
+
def inverse
|
228
|
+
self.class.new(*rgb.map { |c| 1.0 - c }, @alpha)
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
# @return [Array<Pigment::Color>] An array of the triadic colors from self
|
233
|
+
def triadic
|
234
|
+
[self.class.new(@blue, @red, @green, @alpha), self.class.new(@green, @blue, @red, @alpha)]
|
235
|
+
end
|
236
|
+
|
237
|
+
# @param [Boolean] with_alpha
|
238
|
+
# @return [Array<Float>] an array of the color components. Alpha value is passed as well if with_alpha is set to true.
|
239
|
+
def to_a(with_alpha: true)
|
240
|
+
with_alpha ? [@red, @green, @blue, @alpha] : [@red, @green, @blue]
|
241
|
+
end
|
242
|
+
|
243
|
+
# @param [Boolean] with_alpha
|
244
|
+
# @return [Array<Integer>] an array of the color components. Alpha value is passed as well if with_alpha is set to true.
|
245
|
+
def to_ints(with_alpha: true)
|
246
|
+
to_a(with_alpha: with_alpha).map { |v| (v * 255).to_i }
|
247
|
+
end
|
248
|
+
|
249
|
+
# @param [Boolean] with_alpha
|
250
|
+
# @return [String] an hexadecimal representation of the color components. Alpha value is passed as well if
|
251
|
+
# with_alpha is set to true.
|
252
|
+
def to_hex(with_alpha: true)
|
253
|
+
to_ints(with_alpha: with_alpha).map { |v| '%02x' % v }.join
|
254
|
+
end
|
255
|
+
|
256
|
+
# @return [Array<Float>] an array with the red, green and blue color components
|
257
|
+
def rgb
|
258
|
+
to_a(with_alpha: false)
|
259
|
+
end
|
260
|
+
|
261
|
+
# @return [String] the string representation of a hsl color
|
262
|
+
def to_s
|
263
|
+
"RGB Color(red: #{red}, green: #{green}, blue: #{blue}, alpha: #{alpha})"
|
264
|
+
end
|
265
|
+
|
266
|
+
alias_method :mix, :interpolate
|
267
|
+
alias_method :r, :red
|
268
|
+
alias_method :g, :green
|
269
|
+
alias_method :b, :blue
|
270
|
+
alias_method :a, :alpha
|
271
|
+
alias_method :rgba, :to_a
|
272
|
+
|
273
|
+
include Pigment::Color
|
274
|
+
|
275
|
+
private
|
276
|
+
# @return [Array<Float>] an array with the respective rgba components
|
277
|
+
def method_missing(method, *args)
|
278
|
+
return super unless method =~ /^[abgr]+$/ && args.empty?
|
279
|
+
method.size.times.map{ |i| send(method[i]) }
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|