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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 79ec5aef1ab5b45b400c11d9b56cd5dda30e191962bff97a91bfa9ba461d28ed
4
+ data.tar.gz: f684c0abf44be4e7d466f9acdbb4317510bd3b334004b8168c440dda089c50db
5
+ SHA512:
6
+ metadata.gz: aab005b7c4e84a23a9f1c515d27314f2f994a57c2f7bad76be1dbecc89285b3fe6793244fc414bf8786745212d6292e807a27062ace6be41c637dcc7a161a0be
7
+ data.tar.gz: 868457b116af47ae1c7601c5c541d43926cc5002e109823654ea67c7fb167660132c0fbad6f34ac55e1b0db0f52798f3eef727d27322df5117c3e60df36e658b
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # 0.3.1
2
+ - Fixes creating colors with approximation errors:
3
+ ```ruby
4
+ # before 0.3.1
5
+ BrilliantLavender.analogous
6
+ # Invalid Format [0.8063725490196079, 1.0000000000000004, 0.8666666666666667, 1.0]
7
+
8
+ # after 0.3.1
9
+ BrilliantLavender.analogous
10
+ # RGB Color(red: 0.8063725490196079, green: 1.0, blue: 0.8666666666666667, alpha: 1.0)
11
+ ```
12
+
13
+ # 0.3.0
14
+ - New classes per Color Format
15
+ - Pigment::Color::RGB
16
+ - Pigment::Color::HSL
17
+ - Base module for Color Formats Pigment::Color
18
+ - Palette class to handle a collection of colors
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ (The MIT License)
2
+
3
+ Copyright © 2021 Pedro Miranda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ 'Software'), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -6,4 +6,185 @@ To install:
6
6
  gem install pigment
7
7
  ```
8
8
 
9
- <a href='http://www.pledgie.com/campaigns/18945'><img alt='Click here to lend your support to: pigment (ruby gem) and make a donation at www.pledgie.com !' src='http://www.pledgie.com/campaigns/18945.png?skin_name=chrome' border='0' /></a>
9
+ ## Usage
10
+ ### RGB Color Format
11
+ The RGB color format is represented with four components between 0.0 and 1.0, representing 0 to 255
12
+ - `@red`
13
+ - `@green`
14
+ - `@blue`
15
+ - `@alpha`
16
+
17
+ ```ruby
18
+ require 'pigment/color/rgb'
19
+
20
+ red = Pigment::Color::RGB.new(1.0, 0.0, 0.0)
21
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.0)
22
+
23
+ red.to_a
24
+ # [1.0, 0.0, 0.0, 1.0]
25
+
26
+ semitransparent_red = Pigment::Color::RGB.new(1.0, 0.0, 0.0, 0.5)
27
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
28
+
29
+ red + semitransparent_red
30
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
31
+
32
+ semitransparent_red + red
33
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 0.5)
34
+
35
+ red - semitransparent_red
36
+ # RGB Color(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
37
+
38
+ half_red = red / 2
39
+ # RGB Color(red: 0.5, green: 0.0, blue: 0.0, alpha: 1.0)
40
+
41
+ half_red * 2 == red
42
+ # true
43
+
44
+ gray50 = Pigment::Color::RGB.new(1.0, 0.5, 0.0).grayscale
45
+ # RGB Color(red: 0.5, green: 0.5, blue: 0.5, alpha: 1.0)
46
+
47
+ gray50.grayscale?
48
+ # true
49
+
50
+ cyan = red.inverse
51
+ # RGB Color(red: 0.0, green: 1.0, blue: 1.0, alpha: 1.0)
52
+
53
+ green, blue = red.triadic
54
+ # [RGB Color(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), RGB Color(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)]
55
+
56
+ red.to_hex
57
+ # '0xff0000ff'
58
+
59
+ red.to_hex(with_alpha: false)
60
+ # '0xff0000'
61
+
62
+ Pigment::Color::RGB.convert(Pigment::Color::HSL.new(0.0, 1, 0.5))
63
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
64
+ ```
65
+
66
+ ### HSL Color Format
67
+
68
+ The HSL color format is represented with
69
+
70
+ - `@hue` - a value from 0.0 to 1.0, representing 0 to 360 degrees
71
+ - `@saturation` - a value from 0.0 to 1.0
72
+ - `@lightness` - a value from 0.0 to 1.0
73
+ - `@alpha` - a value from 0.0 to 1.0
74
+
75
+ This format is useful to calculate related colors:
76
+
77
+ - triadic
78
+ - split
79
+ - analogous
80
+ - tetradic
81
+ - rectangular
82
+ - tertiary
83
+
84
+ ```ruby
85
+ require 'pigment/color/hsl'
86
+
87
+ hsl_red = Pigment::Color::HSL.convert(red)
88
+ # HSL Color(hue: 0.0, saturation: 1.0, lightness: 0.5, alpha: 1.0)
89
+
90
+ hsl_red == Pigment::Color::HSL(0.0, 1, 0.5)
91
+ # HSL Color(hue: 0.0, saturation: 1.0, lightness: 0.5, alpha: 1.0)
92
+
93
+ hsl_red.triadic
94
+ hsl_red.split
95
+ hsl_red.analogous
96
+ hsl_red.tetradic
97
+ hsl_red.rectangular
98
+ hsl_red.tertiary
99
+
100
+ ```
101
+
102
+ ### Color Module
103
+
104
+ The color module works as the base for any possible color format, implementing common methods
105
+
106
+ ```ruby
107
+ red.to_html
108
+ # '#ff0000'
109
+
110
+ red.into(Pigment::HSL)
111
+ # HSL Color(hue: 0.0, saturation: 1.0, lightness: 0.5, alpha: 1.0)
112
+
113
+ red.inverse?(cyan)
114
+ # true
115
+
116
+ red.triadic_of?(blue)
117
+ # true
118
+
119
+ red.triadic_include?(green, blue)
120
+ # true
121
+
122
+ red.split_of?
123
+ red.split_include?
124
+ red.analogous_of?
125
+ red.analogous_include?
126
+ red.tetradic_of?
127
+ red.tetradic_include?
128
+ red.rectangular_of?
129
+ red.rectangular_include?
130
+ red.tertiary_of?
131
+ red.tertiary_include?
132
+ # of -> checks if self is included in argument result
133
+ # include -> checks if arguments are included in self result
134
+ ```
135
+
136
+ ### Color Palette
137
+
138
+ ```ruby
139
+ require 'pigment/palette'
140
+
141
+ pallete = Pigment::Palette.new(
142
+ red: red,
143
+ blue: blue,
144
+ )
145
+
146
+ pallete[:red]
147
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
148
+
149
+ pallete[:green] = green
150
+ # RGB Color(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
151
+
152
+ # Pigment::Palette is enumerable
153
+
154
+ palette.map.to_a
155
+ # [RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), RGB Color(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0), RGB Color(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)]
156
+ ```
157
+
158
+ ### Default Color Palette
159
+
160
+ ```ruby
161
+ require 'pigment/default_rgb_palette'
162
+
163
+ Pigment::Palette::RGB::DEFAULT[:Red]
164
+ # RGB Color(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0)
165
+
166
+
167
+ Pigment::Palette::RGB::Green
168
+ # RGB Color(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0)
169
+
170
+ Pigment::Palette::RGB.Blue
171
+ # RGB Color(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0)
172
+ ```
173
+
174
+ ## Changes
175
+ See [Changelog](CHANGELOG.md)
176
+
177
+
178
+ ## Future Work
179
+ - [ ] new color formats:
180
+ - [ ] Y'CbCr
181
+ - [ ] RYB
182
+ - [ ] CMYK
183
+ - [ ] redesign of Base Color module to support Additive color formats
184
+ - [ ] Palette Loader to load palette files
185
+
186
+ ## License
187
+ See [License](LICENSE.md)
188
+
189
+ ## Related interests
190
+ - [color-names](https://github.com/meodai/color-names): javascript collection of 25586 color names
data/lib/pigment.rb CHANGED
@@ -1,307 +1,3 @@
1
- module Pigment
2
- VERSION = '0.2.1'
3
-
4
- class Color
5
-
6
- @named_colors = {}
7
- attr_reader :color, :hsl
8
-
9
- # Pigment uses sRGB or sRGBA as the default color system
10
- # color can be a hexadecimal code preceded by a '#' like '#FF4CB2' or a array of floats (1.0, 0.3, 0.7)
11
- # or an array of integers between 0 and 255 (153, 255, 31)
12
- # @param [String, Array] color
13
- def initialize(*color)
14
- @color = case
15
- when color[0] =~ /^#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})?$/
16
- [$1, $2, $3, $4 || 'ff'].map { |v| v.to_i(16) / 255.0 }
17
- when color.is_a?(Array) && (color.all? { |c| (0.0..1.0).include? c }) && color.length == 3
18
- [color, 1.0].flatten!
19
- when color.is_a?(Array) && (color.all? { |c| (0..255).include? c && c.is_a?(Integer) } ) && color.length == 3
20
- [color.map { |c| c / 255.0 }, 1.0].flatten!
21
- when color.is_a?(Array) && (color.all? { |c| (0.0..1.0).include? c }) && color.length == 4
22
- color
23
- when color.is_a?(Array) && (color.all? { |c| (0..255).include? c && c.is_a?(Integer) } ) && color.length == 4
24
- color.map { |c| c / 255.0 }
25
- else
26
- raise ArgumentError, "Expected String or Array with length 3 or 4. Given #{color.class} with length = #{color.length}"
27
- end
28
- end
29
-
30
- # Selectors and Setters.
31
- %w'r g b a'.each_with_index do |m, i|
32
- define_method("#{m}", ->() { @color[i] })
33
- define_method("#{m}=", ->(value) { @color[i] = value if value.is_a?(Float) && (0.0..1.0).include?(value) })
34
- end
35
-
36
- %w'h s l'.each_with_index do |m, i|
37
- define_method("#{m}", ->() { hsl[i] })
38
- end
39
-
40
-
41
- # Returns an array with the rgb components
42
- # @return [Array]
43
- def rgb
44
- @color[0, 3]
45
- end
46
-
47
- # Return specified color by its name from the named_colors hash.
48
- # @param [Symbol] name
49
- # @return [Color]
50
- def self.[](name)
51
- @named_colors[name]
52
- end
53
-
54
- # Add name to a color , add it to the named_colors hash and defines a constant.
55
- # @param [Array of Strings] names
56
- # @param [Color] color
57
- def self.[]=(*names, color)
58
- color = new(color) unless color.is_a? Color
59
- names.each do |name|
60
- @named_colors[name.downcase] = color
61
- const_set("#{name}".to_sym, color)
62
- end
63
- end
64
-
65
- # Return all the named_colors as an array.
66
- def self.named_colors
67
- @named_colors.keys
68
- end
69
-
70
- # Return all the named_colors as a sorted array.
71
- def self.named_colors_sorted
72
- named_colors.sort
73
- end
74
-
75
-
76
- # Suppress an array of floats by dividing by the greatest color component.
77
- # @param [Array] color
78
- # @return [Array]
79
- def self.suppress(color)
80
- color.map! { |c| c / color.max } unless (0.0..1.0).include?(color.max)
81
- color
82
- end
83
-
84
- # Creates a Color form the HSL color System. It's mostly used to calculate harmonic colors.
85
- # @param [Float] h
86
- # @param [Float] s
87
- # @param [Float] l
88
- # @return [Color]
89
- def self.from_hsl(h, s, l)
90
- return new(l, l, l) if s == 0
91
- v2 = l < 0.5 ? l * (1 + s) : (l + s) - (s * l)
92
- v1 = 2 * l - v2
93
- color = [h + (1 / 3.0), h, h - (1 / 3.0)].map do |hv|
94
- case
95
- when hv < 0 then hv += 1
96
- when hv > 1 then hv -= 1
97
- when 6 * hv < 1 then v1 +(v2 - v1) * 6 * hv
98
- when 2 * hv < 1 then v2
99
- when 3 * hv < 2 then v1 + (v2 - v1) * ((2 / 3.0)- hv) * 6
100
- else v1
101
- end
102
- end
103
- new(*color)
104
- end
105
-
106
- # Same as inverse.
107
- def -@
108
- inv
109
- end
110
-
111
- # Sums all the two colors components. If any component gets out of the 0 to 1.0 range its suppressed.
112
- # @param [Numeric] color
113
- # @return [Color]
114
- def +(color)
115
- case color
116
- when Color
117
- self.class.new(*self.class.suppress([color.rgb, rgb].transpose.map! { |c, d| c + d }))
118
- else
119
- raise ArgumentError, "Expecting Color. Given #{color.class}"
120
- end
121
- end
122
-
123
- # Subtracts all the two color components. If any component gets out of the 0 to 1.0 range its suppressed.
124
- # If tone component gets lower than 0 it acts like its dealing with the inverse component -> 1 - component
125
- # @param [Numeric] color
126
- # @return [Color]
127
- def -(color)
128
- case color
129
- when Color
130
- self.class.new(*self.class.suppress([rgb, color.rgb].transpose.map! do |c, d|
131
- e = c - d
132
- e >= 0 ? e : e = 1 + e
133
- e
134
- end))
135
- else
136
- raise ArgumentError, "Expecting color. Given #{color.class}"
137
- end
138
- end
139
-
140
- # Multiplies all the color components by n. If any component gets out of the 0 to 1.0 range its suppressed.
141
- # @param [Numeric] n
142
- # @return [Color]
143
- def *(n)
144
- case n
145
- when Numeric
146
- n = rgb.map { |c| c * n.to_f }
147
- self.class.new(*self.class.suppress(n))
148
- else
149
- raise ArgumentError, "Expecting Numeric. Given #{n.class}"
150
- end
151
- end
152
-
153
- # Divides all the color components by n. If any component gets out of the 0 to 1.0 range its suppressed.
154
- # @param [Numeric] n
155
- # @return [Color]
156
- def /(n)
157
- case n
158
- when Numeric
159
- n = rgb.map { |c| c * n.to_f }
160
- self.class.new(*self.class.suppress(n))
161
- else
162
- raise ArgumentError, "Expecting Numeric. Given #{n.class}"
163
- end
164
- end
165
-
166
- # Test if two colors are equal
167
- # @param [Color] color
168
- # @return [Boolean]
169
- def ==(color)
170
- color.is_a?(Color) && color.rgb == rgb
171
- end
172
-
173
- # @return [Color]
174
- def dup
175
- self.class.new(*@color)
176
- end
177
-
178
- # Converts a color to its grayscale correspondent
179
- # @return [Color]
180
- def grayscale
181
- r = g = b = (self.r + self.g + self.b)/3
182
- self.class.new(r, g, b)
183
- end
184
-
185
- # Calculates the harmonic colors. Type can be :triadic, :split, :analogous, :complement or :complementary
186
- # @param [Symbol] type
187
- # @return [Color, Array of Colors]
188
- def harmonize(type = :triadic)
189
- color.to_hsl unless @hsl
190
- case type
191
- when :triadic
192
- [self.class.from_hsl(h - 5 / 12.0, s, l), self.class.from_hsl(h + 1 / 3.0, s, l)]
193
- when :split
194
- [self.class.from_hsl(h - 5 / 12.0, s, l), self.class.from_hsl(h + 5 / 12.0, s, l)]
195
- when :analogous
196
- [self.class.from_hsl(h - 1 / 12.0, s, l), self.class.from_hsl(h + 1 / 12.0, s, l)]
197
- when :complement, :complementary
198
- inv
199
- else
200
- raise ArgumentError, "Expected :triadic, :split, :analogous, :complement or :complementary. Given #{type}"
201
- end
202
- end
203
-
204
- # Interpolates two colors. Amount must be an float between -1.0 and 1.0
205
- # @param [Color] color
206
- # @param [Float] amount
207
- def interpolate(color, amount = 0.5)
208
- if color.is_a?(Color) && (-1.0..1.0).include?(amount)
209
- n = [rgb, color.rgb].transpose.map! { |c, d| c + amount * (d - c) }
210
- self.class.new(*self.class.suppress(n))
211
- else
212
- raise ArgumentError
213
- end
214
- end
215
-
216
- # Returns the Invert color
217
- # @return [Color]
218
- def inverse
219
- self.class.new(*rgb.map { |c| 1.0 - c })
220
- end
221
-
222
- # Returns a new Color without the given channels
223
- # @param [Array of Symbols] channels
224
- # @return [Color]
225
- def remove_channels(*channels)
226
- color = self.class.new(r, g, b, a)
227
- %w'r g b a'.each do |attr|
228
- color.send("#{attr}=", 0) if channels.include? attr.to_sym
229
- end
230
- color
231
- end
232
-
233
- # Creates an instance variable to keep the HSL values of a RGB color.
234
- # @return [Array]
235
- def to_hsl
236
- min = rgb.min
237
- max = rgb.max
238
- delta = (max - min)
239
- l = (max + min) / 2.0
240
-
241
- s = if delta == 0.0 # close to 0.0, so it's a grey
242
- h = 0
243
- 0
244
- elsif l < 0.5
245
- delta / (max + min)
246
- else
247
- delta / (2.0 - max - min)
248
- end
249
-
250
- h = if r == max
251
- h = ((g - b) / delta) / 6.0
252
- h += 1.0 if g < b
253
- h
254
- elsif g == max
255
- ((b - r) / delta) / 6.0 + (1.0 / 3.0)
256
- elsif b == max
257
- ((r - g) / delta) / 6.0 + (2.0 / 3.0)
258
- end
259
-
260
- h += 1 if h < 0
261
- h -= 1 if h > 1
262
- @hsl = [h, s, l]
263
- self
264
- end
265
-
266
- # Returns an array of the color components. Alpha value is passed as well if with_alpha is set to true.
267
- # @param [Boolean] with_alpha
268
- # @return [Array]
269
- def to_floats(with_alpha = true)
270
- with_alpha ? @color.dup : rgb
271
- end
272
-
273
- # Returns an array of the color components. Alpha value is passed as well if with_alpha is set to true.
274
- # @param [Boolean] with_alpha
275
- # @return [Array]
276
- def to_ints(with_alpha = true)
277
- to_floats(with_alpha).map { |v| Integer(v * 255) }
278
- end
279
-
280
- # Returns an array of the color components. Alpha value is passed as well if with_alpha is set to true.
281
- # @param [Boolean] with_alpha
282
- # @return [Array]
283
- def to_hex(with_alpha = true)
284
- to_ints(with_alpha).map { |v| '%02x' % v }.join
285
- end
286
-
287
- def to_s
288
- "Color(r=#{r}, g=#{g}, b=#{b}, a=#{a}#{", [h=#{h}, s=#{s}, l=#{l}]" if @hsl})"
289
- end
290
-
291
- alias_method :alpha=, :a=
292
- alias_method :inv, :inverse
293
- alias_method :invert, :inverse
294
- alias_method :complementary, :inverse
295
- alias_method :mix, :interpolate
296
- alias_method :red, :r
297
- alias_method :green, :g
298
- alias_method :blue, :b
299
- alias_method :alpha, :a
300
- alias_method :to_a, :to_floats
301
- alias_method :to_ary, :to_floats
302
- alias_method :to_f, :to_floats
303
- alias_method :to_hsl, :hsl
304
- end
305
- end
306
-
307
- require_relative 'colors.rb'
1
+ require_relative 'pigment/version'
2
+ require_relative 'pigment/float_snap'
3
+ require_relative 'pigment/color/invalid_color_format_error'