pixelart-colors 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +13 -0
- data/README.md +31 -0
- data/Rakefile +28 -0
- data/lib/pixelart/colors/base.rb +28 -0
- data/lib/pixelart/colors/color.rb +321 -0
- data/lib/pixelart/colors/format.rb +77 -0
- data/lib/pixelart/colors/gradient.rb +106 -0
- data/lib/pixelart/colors/palette.rb +72 -0
- data/lib/pixelart/colors/version.rb +25 -0
- data/lib/pixelart/colors.rb +22 -0
- data/sandbox/test_colors.rb +28 -0
- data/sandbox/test_gradient.rb +62 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 20d3ab72b943a3e9b6cbe0a7f67cf376f6f7a077de3c0456d93a8b2a3e386e0d
|
4
|
+
data.tar.gz: e4bdb8430d7d20337d3e01bd7daa885b01ead91f692af8d6af0a4b53b65c9f8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7d8e890fd954aa76e116a0c9cf8ae27450b6115e0e41091dbcc580dd147cf609f21546acc8aa151b1df249f871e28aacc5173bbbee864e370a6a0e90d5aa8883
|
7
|
+
data.tar.gz: 5f83c8148e7ffb34f7b049481943ee671e3a6bd63bfbd38a43a2e82c6e23fff14e5d29b4823620b22da93474de12b0b8987fe82e38a3273b9b4b05e5a56f2c19
|
data/CHANGELOG.md
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
CHANGELOG.md
|
2
|
+
Manifest.txt
|
3
|
+
README.md
|
4
|
+
Rakefile
|
5
|
+
lib/pixelart/colors.rb
|
6
|
+
lib/pixelart/colors/base.rb
|
7
|
+
lib/pixelart/colors/color.rb
|
8
|
+
lib/pixelart/colors/format.rb
|
9
|
+
lib/pixelart/colors/gradient.rb
|
10
|
+
lib/pixelart/colors/palette.rb
|
11
|
+
lib/pixelart/colors/version.rb
|
12
|
+
sandbox/test_colors.rb
|
13
|
+
sandbox/test_gradient.rb
|
data/README.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Pixel Art (True) Colors
|
2
|
+
|
3
|
+
pixelart-colors - helpers to work with true colors as integers; 256 shades for r/g/b (red, green and blue) totaling 16 million colors; incl. hsl, hsv color space converters and more
|
4
|
+
|
5
|
+
|
6
|
+
* home :: [github.com/pixelartexchange/pixelart](https://github.com/pixelartexchange/pixelart)
|
7
|
+
* bugs :: [github.com/pixelartexchange/pixelart/issues](https://github.com/pixelartexchange/pixelart/issues)
|
8
|
+
* gem :: [rubygems.org/gems/pixelart-colors](https://rubygems.org/gems/pixelart-colors)
|
9
|
+
* rdoc :: [rubydoc.info/gems/pixelart](http://rubydoc.info/gems/pixelart-colors)
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
To be done
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
## License
|
23
|
+
|
24
|
+
The scripts are dedicated to the public domain.
|
25
|
+
Use it as you please with no restrictions whatsoever.
|
26
|
+
|
27
|
+
|
28
|
+
## Questions? Comments?
|
29
|
+
|
30
|
+
Post them on the [D.I.Y. Punk (Pixel) Art reddit](https://old.reddit.com/r/DIYPunkArt). Thanks.
|
31
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'hoe'
|
2
|
+
require './lib/pixelart/colors/version.rb'
|
3
|
+
|
4
|
+
Hoe.spec 'pixelart-colors' do
|
5
|
+
|
6
|
+
self.version = Pixelart::Module::Colors::VERSION
|
7
|
+
|
8
|
+
|
9
|
+
self.summary = "pixelart-colors - helpers to work with true colors as integers; 256 shades for r/g/b (red, green and blue) totaling 16 million colors; incl. hsl, hsv color space converters and more"
|
10
|
+
self.description = summary
|
11
|
+
|
12
|
+
self.urls = { home: 'https://github.com/pixelartexchange/pixelart' }
|
13
|
+
|
14
|
+
self.author = 'Gerald Bauer'
|
15
|
+
self.email = 'wwwmake@googlegroups.com'
|
16
|
+
|
17
|
+
# switch extension to .markdown for gihub formatting
|
18
|
+
self.readme_file = 'README.md'
|
19
|
+
self.history_file = 'CHANGELOG.md'
|
20
|
+
|
21
|
+
self.extra_deps = []
|
22
|
+
|
23
|
+
self.licenses = ['Public Domain']
|
24
|
+
|
25
|
+
self.spec_extras = {
|
26
|
+
required_ruby_version: '>= 2.3'
|
27
|
+
}
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
# bonus / prologue / convenience 3rd party
|
3
|
+
# require 'csvreader' -- add later - why? why not?
|
4
|
+
|
5
|
+
|
6
|
+
## stdlib
|
7
|
+
require 'pp'
|
8
|
+
require 'time'
|
9
|
+
require 'date'
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
require 'json'
|
13
|
+
require 'yaml'
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
## our own code
|
19
|
+
require 'pixelart/colors/version' # note: let version always go first
|
20
|
+
require 'pixelart/colors/color'
|
21
|
+
require 'pixelart/colors/format'
|
22
|
+
|
23
|
+
require 'pixelart/colors/gradient'
|
24
|
+
require 'pixelart/colors/palette'
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
puts Pixelart::Module::Colors.banner # say hello
|
@@ -0,0 +1,321 @@
|
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
|
4
|
+
class Color ## todo/check - change class to module Color - why? why not?
|
5
|
+
|
6
|
+
TRANSPARENT = 0 # rgba( 0, 0, 0, 0)
|
7
|
+
BLACK = 0xff # rgba( 0, 0, 0, 255)
|
8
|
+
WHITE = 0xffffffff # rgba(255, 255, 255, 255)
|
9
|
+
|
10
|
+
|
11
|
+
def self.parse( color )
|
12
|
+
if color.is_a?( Integer ) ## e.g. assumes rgba or such
|
13
|
+
color ## pass through as is 1:1
|
14
|
+
elsif color.is_a?( Array ) ## assume array of hsl(a) e. g. [180, 0.86, 0.88]
|
15
|
+
from_hsl( *color )
|
16
|
+
elsif color.is_a?( String )
|
17
|
+
if color.downcase == 'transparent' ## special case for builtin colors
|
18
|
+
TRANSPARENT
|
19
|
+
else
|
20
|
+
## note: return an Integer !!! (not a Color class or such!!! )
|
21
|
+
from_hex( color )
|
22
|
+
end
|
23
|
+
else
|
24
|
+
raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# Creates a new color using an r, g, b triple and an alpha value.
|
30
|
+
# @param [Integer] r The r-component (0-255)
|
31
|
+
# @param [Integer] g The g-component (0-255)
|
32
|
+
# @param [Integer] b The b-component (0-255)
|
33
|
+
# @param [Integer] a The opacity (0-255)
|
34
|
+
# @return [Integer] The newly constructed color value.
|
35
|
+
def self.rgba(r, g, b, a)
|
36
|
+
r << 24 | g << 16 | b << 8 | a
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a new color using an r, g, b triple.
|
40
|
+
# @param [Integer] r The r-component (0-255)
|
41
|
+
# @param [Integer] g The g-component (0-255)
|
42
|
+
# @param [Integer] b The b-component (0-255)
|
43
|
+
# @return [Integer] The newly constructed color value.
|
44
|
+
def self.rgb(r, g, b)
|
45
|
+
r << 24 | g << 16 | b << 8 | 0xff
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# Returns the red-component from the color value.
|
50
|
+
#
|
51
|
+
# @param [Integer] value The color value.
|
52
|
+
# @return [Integer] A value between 0 and MAX.
|
53
|
+
def self.r(value)
|
54
|
+
(value & 0xff000000) >> 24
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the green-component from the color value.
|
58
|
+
#
|
59
|
+
# @param [Integer] value The color value.
|
60
|
+
# @return [Integer] A value between 0 and MAX.
|
61
|
+
def self.g(value)
|
62
|
+
(value & 0x00ff0000) >> 16
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the blue-component from the color value.
|
66
|
+
#
|
67
|
+
# @param [Integer] value The color value.
|
68
|
+
# @return [Integer] A value between 0 and MAX.
|
69
|
+
def self.b(value)
|
70
|
+
(value & 0x0000ff00) >> 8
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the alpha channel value for the color value.
|
74
|
+
#
|
75
|
+
# @param [Integer] value The color value.
|
76
|
+
# @return [Integer] A value between 0 and MAX.
|
77
|
+
def self.a(value)
|
78
|
+
value & 0x000000ff
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
# Returns true if this color is fully opaque.
|
85
|
+
#
|
86
|
+
# @param [Integer] value The color to test.
|
87
|
+
# @return [true, false] True if the alpha channel equals MAX.
|
88
|
+
def self.opaque?(value)
|
89
|
+
a(value) == 0x000000ff
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns true if this color is fully transparent.
|
93
|
+
#
|
94
|
+
# @param [Integer] value The color to test.
|
95
|
+
# @return [true, false] True if the r, g and b component are equal.
|
96
|
+
def self.grayscale?(value)
|
97
|
+
r(value) == b(value) && b(value) == g(value)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns true if this color is fully transparent.
|
101
|
+
#
|
102
|
+
# @param [Integer] value The color to test.
|
103
|
+
# @return [true, false] True if the alpha channel equals 0.
|
104
|
+
def self.transparent?(value)
|
105
|
+
a(value) == 0x00000000
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
# The regexp to parse 3-digit hex color values.
|
111
|
+
HEX3_COLOR_REGEXP = /\A(?:#|0x)?([0-9a-f]{3})\z/i
|
112
|
+
|
113
|
+
# The regexp to parse 6- and 8-digit hex color values.
|
114
|
+
HEX6_COLOR_REGEXP = /\A(?:#|0x)?([0-9a-f]{6})([0-9a-f]{2})?\z/i
|
115
|
+
|
116
|
+
|
117
|
+
def self.from_hex( hex_str )
|
118
|
+
## Creates a color by converting it from a string in hex notation.
|
119
|
+
##
|
120
|
+
## It supports colors with (#rrggbbaa) or without (#rrggbb)
|
121
|
+
## alpha channel as well as the 3-digit short format (#rgb)
|
122
|
+
## for those without. Color strings may include
|
123
|
+
## the prefix "0x" or "#"".
|
124
|
+
base_color = case hex_str
|
125
|
+
when HEX3_COLOR_REGEXP
|
126
|
+
$1.gsub( /([0-9a-f])/i, '\1\1' ).hex << 8
|
127
|
+
when HEX6_COLOR_REGEXP
|
128
|
+
$1.hex << 8
|
129
|
+
else
|
130
|
+
raise ArgumentError, "Not a valid hex color notation: #{hex_str.inspect}!"
|
131
|
+
end
|
132
|
+
opacity = $2 ? $2.hex : 0xff
|
133
|
+
base_color | opacity
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns a string representing this color using hex notation (i.e.
|
137
|
+
# #rrggbbaa).
|
138
|
+
#
|
139
|
+
# @param [Integer] color The color to convert.
|
140
|
+
# @param [Boolean] include_alpha
|
141
|
+
# @return [String] The color in hex notation, starting with a pound sign.
|
142
|
+
def self.to_hex( color, include_alpha: true )
|
143
|
+
include_alpha ? ("#%08x" % color) : ("#%06x" % [color >> 8])
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
# Creates a new color from an HSL triple.
|
149
|
+
#
|
150
|
+
# This implementation follows the modern convention of 0 degrees hue
|
151
|
+
# indicating red.
|
152
|
+
#
|
153
|
+
# @param [Fixnum] hue The hue component (0-360)
|
154
|
+
# @param [Fixnum] saturation The saturation component (0-1)
|
155
|
+
# @param [Fixnum] lightness The lightness component (0-1)
|
156
|
+
# @param [Fixnum] alpha Defaults to opaque (255).
|
157
|
+
# @return [Integer] The newly constructed color value.
|
158
|
+
# @raise [ArgumentError] if the hsl triple is invalid.
|
159
|
+
# @see https://en.wikipedia.org/wiki/HSL_and_HSV
|
160
|
+
|
161
|
+
def self.from_hsl( hue, saturation, lightness, alpha=255)
|
162
|
+
raise ArgumentError, "Hue #{hue} was not between 0 and 360" unless (0..360).cover?(hue)
|
163
|
+
raise ArgumentError, "Saturation #{saturation} was not between 0 and 1" unless (0..1).cover?(saturation)
|
164
|
+
raise ArgumentError, "Lightness #{lightness} was not between 0 and 1" unless (0..1).cover?(lightness)
|
165
|
+
|
166
|
+
chroma = (1 - (2 * lightness - 1).abs) * saturation
|
167
|
+
rgb = cylindrical_to_cubic(hue, saturation, lightness, chroma)
|
168
|
+
rgb.map! { |component| ((component + lightness - 0.5 * chroma) * 255).to_i }
|
169
|
+
rgb << alpha
|
170
|
+
rgba(*rgb)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Creates a new color from an HSV triple.
|
174
|
+
#
|
175
|
+
# Create a new color using an HSV (sometimes also called HSB) triple. The
|
176
|
+
# words `value` and `brightness` are used interchangeably and synonymously
|
177
|
+
# in descriptions of this colorspace. This implementation follows the modern
|
178
|
+
# convention of 0 degrees hue indicating red.
|
179
|
+
#
|
180
|
+
# @param [Fixnum] hue The hue component (0-360)
|
181
|
+
# @param [Fixnum] saturation The saturation component (0-1)
|
182
|
+
# @param [Fixnum] value The value (brightness) component (0-1)
|
183
|
+
# @param [Fixnum] alpha Defaults to opaque (255).
|
184
|
+
# @return [Integer] The newly constructed color value.
|
185
|
+
# @raise [ArgumentError] if the hsv triple is invalid.
|
186
|
+
# @see https://en.wikipedia.org/wiki/HSL_and_HSV
|
187
|
+
def self.from_hsv(hue, saturation, value, alpha=255)
|
188
|
+
raise ArgumentError, "Hue must be between 0 and 360" unless (0..360).cover?(hue)
|
189
|
+
raise ArgumentError, "Saturation must be between 0 and 1" unless (0..1).cover?(saturation)
|
190
|
+
raise ArgumentError, "Value/brightness must be between 0 and 1" unless (0..1).cover?(value)
|
191
|
+
|
192
|
+
chroma = value * saturation
|
193
|
+
rgb = cylindrical_to_cubic(hue, saturation, value, chroma)
|
194
|
+
rgb.map! { |component| ((component + value - chroma) * 255).to_i }
|
195
|
+
rgb << alpha
|
196
|
+
rgba(*rgb)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Convert one HSL or HSV triple and associated chroma to a scaled rgb triple
|
200
|
+
#
|
201
|
+
# This method encapsulates the shared mathematical operations needed to
|
202
|
+
# convert coordinates from a cylindrical colorspace such as HSL or HSV into
|
203
|
+
# coordinates of the RGB colorspace.
|
204
|
+
#
|
205
|
+
# Even though chroma values are derived from the other three coordinates,
|
206
|
+
# the formula for calculating chroma differs for each colorspace. Since it
|
207
|
+
# is calculated differently for each colorspace, it must be passed in as
|
208
|
+
# a parameter.
|
209
|
+
#
|
210
|
+
# @param [Fixnum] hue The hue-component (0-360)
|
211
|
+
# @param [Fixnum] saturation The saturation-component (0-1)
|
212
|
+
# @param [Fixnum] y_component The y_component can represent either lightness
|
213
|
+
# or brightness/value (0-1) depending on which scheme (HSV/HSL) is being used.
|
214
|
+
# @param [Fixnum] chroma The associated chroma value.
|
215
|
+
# @return [Array<Fixnum>] A scaled r,g,b triple. Scheme-dependent
|
216
|
+
# adjustments are still needed to reach the true r,g,b values.
|
217
|
+
# @see https://en.wikipedia.org/wiki/HSL_and_HSV
|
218
|
+
def self.cylindrical_to_cubic(hue, saturation, y_component, chroma)
|
219
|
+
hue_prime = hue.fdiv(60)
|
220
|
+
x = chroma * (1 - (hue_prime % 2 - 1).abs)
|
221
|
+
|
222
|
+
case hue_prime
|
223
|
+
when (0...1) then [chroma, x, 0]
|
224
|
+
when (1...2) then [x, chroma, 0]
|
225
|
+
when (2...3) then [0, chroma, x]
|
226
|
+
when (3...4) then [0, x, chroma]
|
227
|
+
when (4...5) then [x, 0, chroma]
|
228
|
+
when (5..6) then [chroma, 0, x]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
|
234
|
+
# Returns an array with the separate HSL components of a color.
|
235
|
+
#
|
236
|
+
# Note: Because this code internally handles colors as Integers for performance
|
237
|
+
# reasons, some rounding occurs when importing or exporting HSL colors
|
238
|
+
# whose coordinates are float-based. Because of this rounding, #to_hsl and
|
239
|
+
# #from_hsl may not be perfect inverses.
|
240
|
+
#
|
241
|
+
# This implementation follows the modern convention of 0 degrees hue indicating red.
|
242
|
+
#
|
243
|
+
# @param [Integer] color The color to convert.
|
244
|
+
# @param [Boolean] include_alpha Flag indicates whether a fourth element
|
245
|
+
# representing alpha channel should be included in the returned array.
|
246
|
+
# @return [Array<Fixnum>[0]] The hue of the color (0-360)
|
247
|
+
# @return [Array<Fixnum>[1]] The saturation of the color (0-1)
|
248
|
+
# @return [Array<Fixnum>[2]] The lightness of the color (0-1)
|
249
|
+
# @return [Array<Fixnum>[3]] Optional fourth element for alpha, included if
|
250
|
+
# include_alpha=true (0-255)
|
251
|
+
# @see https://en.wikipedia.org/wiki/HSL_and_HSV
|
252
|
+
def self.to_hsl(color, include_alpha: true )
|
253
|
+
hue, chroma, max, min = hue_and_chroma(color)
|
254
|
+
lightness = 0.5 * (max + min)
|
255
|
+
saturation = chroma.zero? ? 0.0 : chroma.fdiv(1 - (2 * lightness - 1).abs)
|
256
|
+
|
257
|
+
include_alpha ? [hue, saturation, lightness, a(color)] :
|
258
|
+
[hue, saturation, lightness]
|
259
|
+
end
|
260
|
+
|
261
|
+
# Returns an array with the separate HSV components of a color.
|
262
|
+
#
|
263
|
+
# Note: Because this code internally handles colors as Integers for performance
|
264
|
+
# reasons, some rounding occurs when importing or exporting HSV colors
|
265
|
+
# whose coordinates are float-based. Because of this rounding, #to_hsv and
|
266
|
+
# #from_hsv may not be perfect inverses.
|
267
|
+
#
|
268
|
+
# This implementation follows the modern convention of 0 degrees hue
|
269
|
+
# indicating red.
|
270
|
+
#
|
271
|
+
# @param [Integer] color
|
272
|
+
# @param [Boolean] include_alpha Flag indicates whether a fourth element
|
273
|
+
# representing alpha channel should be included in the returned array.
|
274
|
+
# @return [Array[0]] The hue of the color (0-360)
|
275
|
+
# @return [Array[1]] The saturation of the color (0-1)
|
276
|
+
# @return [Array[2]] The value of the color (0-1)
|
277
|
+
# @return [Array[3]] Optional fourth element for alpha, included if
|
278
|
+
# include_alpha=true (0-255)
|
279
|
+
# @see https://en.wikipedia.org/wiki/HSL_and_HSV
|
280
|
+
def self.to_hsv(color, include_alpha: true )
|
281
|
+
hue, chroma, max, _ = hue_and_chroma(color)
|
282
|
+
value = max
|
283
|
+
saturation = chroma.zero? ? 0.0 : chroma.fdiv(value)
|
284
|
+
|
285
|
+
include_alpha ? [hue, saturation, value, a(color)] :
|
286
|
+
[hue, saturation, value]
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
|
291
|
+
# This method encapsulates the logic needed to extract hue and chroma from
|
292
|
+
# a color. This logic is shared by the cylindrical HSV/HSB and HSL
|
293
|
+
# color space models.
|
294
|
+
#
|
295
|
+
# @param [Integer] color.
|
296
|
+
# @return [Fixnum] hue The hue of the color (0-360)
|
297
|
+
# @return [Fixnum] chroma The chroma of the color (0-1)
|
298
|
+
# @return [Fixnum] max The magnitude of the largest scaled rgb component (0-1)
|
299
|
+
# @return [Fixnum] min The magnitude of the smallest scaled rgb component (0-1)
|
300
|
+
# @private
|
301
|
+
def self.hue_and_chroma(color)
|
302
|
+
scaled_rgb = [r(color), g(color), b(color)]
|
303
|
+
scaled_rgb.map! { |component| component.fdiv(255) }
|
304
|
+
min, max = scaled_rgb.minmax
|
305
|
+
chroma = max - min
|
306
|
+
|
307
|
+
r, g, b = scaled_rgb
|
308
|
+
hue_prime = chroma.zero? ? 0 : case max
|
309
|
+
when r then (g - b).fdiv(chroma)
|
310
|
+
when g then (b - r).fdiv(chroma) + 2
|
311
|
+
when b then (r - g).fdiv(chroma) + 4
|
312
|
+
else 0
|
313
|
+
end
|
314
|
+
hue = 60 * hue_prime
|
315
|
+
|
316
|
+
[hue.round, chroma, max, min]
|
317
|
+
end
|
318
|
+
end # class Color
|
319
|
+
end # module Pixelart
|
320
|
+
|
321
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
module Pixelart
|
3
|
+
class Color ## todo/check - change class to module Color - why? why not?
|
4
|
+
|
5
|
+
|
6
|
+
## known built-in color names
|
7
|
+
def self.build_names
|
8
|
+
names = {
|
9
|
+
'#00000000' => 'TRANSPARENT',
|
10
|
+
'#000000ff' => 'BLACK',
|
11
|
+
'#ffffffff' => 'WHITE',
|
12
|
+
}
|
13
|
+
|
14
|
+
## auto-add grayscale 1 to 254
|
15
|
+
(1..254).each do |n|
|
16
|
+
hex = "#" + ('%02x' % n)*3
|
17
|
+
hex << "ff" ## add alpha channel (255)
|
18
|
+
names[ hex ] = "8-BIT GRAYSCALE ##{n}"
|
19
|
+
end
|
20
|
+
|
21
|
+
names
|
22
|
+
end
|
23
|
+
|
24
|
+
NAMES = build_names
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
def self.format( color )
|
29
|
+
rgb = [r(color),
|
30
|
+
g(color),
|
31
|
+
b(color)]
|
32
|
+
|
33
|
+
# rgb in hex (string format)
|
34
|
+
# note: do NOT include alpha channel for now - why? why not?
|
35
|
+
hex = "#" + rgb.map{|num| '%02x' % num }.join
|
36
|
+
|
37
|
+
hsl = to_hsl( color )
|
38
|
+
hsv = to_hsv( color )
|
39
|
+
|
40
|
+
buf = ''
|
41
|
+
buf << hex
|
42
|
+
buf << " / "
|
43
|
+
buf << "rgb("
|
44
|
+
buf << "%3d " % rgb[0]
|
45
|
+
buf << "%3d " % rgb[1]
|
46
|
+
buf << "%3d)" % rgb[2]
|
47
|
+
buf << " - "
|
48
|
+
buf << "hsl("
|
49
|
+
buf << "%3d° " % (hsl[0] % 360)
|
50
|
+
buf << "%3d%% " % (hsl[1]*100+0.5).to_i
|
51
|
+
buf << "%3d%%)" % (hsl[2]*100+0.5).to_i
|
52
|
+
buf << " - "
|
53
|
+
buf << "hsv("
|
54
|
+
buf << "%3d° " % (hsv[0] % 360)
|
55
|
+
buf << "%3d%% " % (hsv[1]*100+0.5).to_i
|
56
|
+
buf << "%3d%%)" % (hsv[2]*100+0.5).to_i
|
57
|
+
|
58
|
+
alpha = a(color)
|
59
|
+
if alpha != 255
|
60
|
+
buf << " - α(%3d%%)" % (alpha*100/255+0.5).to_i
|
61
|
+
else
|
62
|
+
buf << " " ## add empty for 255 (full opacity)
|
63
|
+
end
|
64
|
+
|
65
|
+
## note: add alpha channel to hex
|
66
|
+
alpha_hex = '%02x' % alpha
|
67
|
+
name = NAMES[ hex+alpha_hex ]
|
68
|
+
buf << " - #{name}" if name
|
69
|
+
|
70
|
+
buf
|
71
|
+
end
|
72
|
+
class << self
|
73
|
+
alias_method :fmt, :format
|
74
|
+
end
|
75
|
+
|
76
|
+
end # class Color
|
77
|
+
end # module Pixelart
|
@@ -0,0 +1,106 @@
|
|
1
|
+
|
2
|
+
## inspired / helped by
|
3
|
+
## https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
|
4
|
+
## https://github.com/mistic100/tinygradient
|
5
|
+
## https://mistic100.github.io/tinygradient/
|
6
|
+
## https://bsouthga.dev/posts/color-gradients-with-python
|
7
|
+
|
8
|
+
|
9
|
+
module Pixelart
|
10
|
+
|
11
|
+
class Gradient
|
12
|
+
|
13
|
+
def initialize( *stops )
|
14
|
+
## note: convert stop colors to rgb triplets e.g.
|
15
|
+
## from #ffffff to [255,255,255]
|
16
|
+
## #000000 to [0,0,0] etc.
|
17
|
+
@stops = stops.map do |stop|
|
18
|
+
stop = Color.parse( stop )
|
19
|
+
[Color.r(stop), Color.g(stop), Color.b(stop)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def colors( steps )
|
25
|
+
segments = @stops.size - 1
|
26
|
+
|
27
|
+
## note: gradient will include start (first)
|
28
|
+
## AND stop color (last) - stop color is NOT excluded for now!!
|
29
|
+
if segments == 1
|
30
|
+
start = @stops[0]
|
31
|
+
stop = @stops[1]
|
32
|
+
|
33
|
+
gradient = linear_gradient( start, stop, steps,
|
34
|
+
include_stop: true )
|
35
|
+
else
|
36
|
+
steps_per_segment, mod = steps.divmod( segments )
|
37
|
+
raise ArgumentError, "steps (#{steps}) must be divisible by # of segments (#{segments}); expected mod of 0 but got #{mod}" if mod != 0
|
38
|
+
|
39
|
+
gradient = []
|
40
|
+
segments.times do |segment|
|
41
|
+
start = @stops[segment]
|
42
|
+
stop = @stops[segment+1]
|
43
|
+
include_stop = (segment == segments-1) ## note: only include stop if last segment!!
|
44
|
+
|
45
|
+
# print " segment #{segment+1}/#{segments} #{steps_per_segment} color(s) - "
|
46
|
+
# print " start: #{start.inspect} "
|
47
|
+
# print include_stop ? 'include' : 'exclude'
|
48
|
+
# print " stop: #{stop.inspect}"
|
49
|
+
# print "\n"
|
50
|
+
|
51
|
+
gradient += linear_gradient( start, stop, steps_per_segment,
|
52
|
+
include_stop: include_stop )
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
## convert to color (Integer)
|
57
|
+
gradient.map do |color|
|
58
|
+
Color.rgb( *color )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
def interpolate( start, stop, steps, n )
|
65
|
+
## note: n - expected to start with 1,2,3,etc.
|
66
|
+
color = []
|
67
|
+
3.times do |i|
|
68
|
+
stepize = Float(stop[i] - start[i]) / Float(steps-1)
|
69
|
+
value = stepize * n
|
70
|
+
## convert back to Integer from Float
|
71
|
+
## add 0.5 for rounding up (starting with 0.5) - why? why not?
|
72
|
+
value = (value+0.5).to_i
|
73
|
+
value = start[i] + value
|
74
|
+
|
75
|
+
color << value
|
76
|
+
end
|
77
|
+
color
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def linear_gradient( start, stop, steps,
|
82
|
+
include_stop: true )
|
83
|
+
|
84
|
+
gradient = [start] ## auto-add start color (first color in gradient)
|
85
|
+
|
86
|
+
if include_stop
|
87
|
+
1.upto( steps-2 ).each do |n| ## sub two (-2), that is, start and stop color
|
88
|
+
gradient << interpolate( start, stop, steps, n )
|
89
|
+
end
|
90
|
+
# note: use original passed in stop color (should match calculated)
|
91
|
+
gradient << stop
|
92
|
+
else
|
93
|
+
1.upto( steps-1 ).each do |n| ## sub one (-1), that is, start color only
|
94
|
+
## note: add one (+1) to steps because stop color gets excluded (not included)!!
|
95
|
+
gradient << interpolate( start, stop, steps+1, n )
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
gradient
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
end # class Gradient
|
105
|
+
end # module Pixelart
|
106
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
|
4
|
+
class Palette8bit # or use Palette256 alias?
|
5
|
+
## todo/check: change class Palette8Bit to Module (like Class) - why? why not?
|
6
|
+
|
7
|
+
## auto-add grayscale 0 to 255
|
8
|
+
## e.g. rgb(0,0,0)
|
9
|
+
## rgb(1,1,1)
|
10
|
+
## rgb(2,2,2)
|
11
|
+
## ...
|
12
|
+
## rgb(255,255,255)
|
13
|
+
GRAYSCALE = (0..255).map { |n| Color.rgb( n, n, n ) }
|
14
|
+
|
15
|
+
|
16
|
+
## 8x32 gradient color stops
|
17
|
+
## see https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
|
18
|
+
|
19
|
+
SEPIA_STOPS = [
|
20
|
+
['080400', '262117'],
|
21
|
+
['272218', '453E2F'],
|
22
|
+
['463F30', '645C48'],
|
23
|
+
['655D48', '837A60'],
|
24
|
+
|
25
|
+
['847A60', 'A29778'],
|
26
|
+
['A39878', 'C1B590'],
|
27
|
+
['C2B691', 'E0D2A8'],
|
28
|
+
['E1D3A9', 'FEEFBF'],
|
29
|
+
]
|
30
|
+
|
31
|
+
BLUE_STOPS = [
|
32
|
+
['000000', '001F3E'],
|
33
|
+
['002040', '003F7E'],
|
34
|
+
['004080', '005FBD'],
|
35
|
+
['0060BF', '007FFD'],
|
36
|
+
|
37
|
+
['0080FF', '009FFF'],
|
38
|
+
['00A0FF', '00BFFF'],
|
39
|
+
['00C0FF', '00DFFF'],
|
40
|
+
['00E0FF', '00FEFF'],
|
41
|
+
]
|
42
|
+
|
43
|
+
FALSE_STOPS = [
|
44
|
+
['FF00FF', '6400FF'],
|
45
|
+
['5F00FF', '003CFF'],
|
46
|
+
['0041FF', '00DCFF'],
|
47
|
+
['00E1FF', '00FF82'],
|
48
|
+
|
49
|
+
['00FF7D', '1EFF00'],
|
50
|
+
['23FF00', 'BEFF00'],
|
51
|
+
['C3FF00', 'FFA000'],
|
52
|
+
['FF9B00', 'FF0000'],
|
53
|
+
]
|
54
|
+
|
55
|
+
|
56
|
+
def self.build_palette( gradients )
|
57
|
+
colors_per_gradient, mod = 256.divmod( gradients.size )
|
58
|
+
raise ArgumentError, "8bit palette - 256 must be divisible by # of gradients (#{gradients.size}; expected mod of 0 but got #{mod}" if mod != 0
|
59
|
+
|
60
|
+
colors = []
|
61
|
+
gradients.each do |stops|
|
62
|
+
colors += Gradient.new( *stops ).colors( colors_per_gradient )
|
63
|
+
end
|
64
|
+
colors
|
65
|
+
end
|
66
|
+
|
67
|
+
SEPIA = build_palette( SEPIA_STOPS )
|
68
|
+
BLUE = build_palette( BLUE_STOPS )
|
69
|
+
FALSE = build_palette( FALSE_STOPS )
|
70
|
+
end # class Palette8bit
|
71
|
+
end # module Pixelart
|
72
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
module Pixelart
|
3
|
+
module Module
|
4
|
+
module Colors
|
5
|
+
MAJOR = 0
|
6
|
+
MINOR = 1
|
7
|
+
PATCH = 0
|
8
|
+
VERSION = [MAJOR,MINOR,PATCH].join('.')
|
9
|
+
|
10
|
+
def self.version
|
11
|
+
VERSION
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.banner
|
15
|
+
"pixelart-colors/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.root
|
19
|
+
File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )
|
20
|
+
end
|
21
|
+
|
22
|
+
end # module Colors
|
23
|
+
end # module Module
|
24
|
+
end # module Pixelart
|
25
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
## our own code (without "top-level" shortcuts e.g. "modular version")
|
3
|
+
require 'pixelart/colors/base' # aka "strict(er)" version
|
4
|
+
|
5
|
+
|
6
|
+
|
7
|
+
##########
|
8
|
+
# add some spelling convenience variants
|
9
|
+
PixelArt = Pixelart
|
10
|
+
|
11
|
+
module Pixelart
|
12
|
+
Palette256 = Palette8Bit = Palette8bit
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
###
|
18
|
+
# add convenience top-level shortcuts / aliases
|
19
|
+
# make Image, Color, Palette8bit, etc top-level
|
20
|
+
include Pixelart
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib sandbox/test_colors.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'pixelart/colors/base'
|
7
|
+
|
8
|
+
colors = {'transparent' => Pixelart::Color::TRANSPARENT,
|
9
|
+
'black' => Pixelart::Color::BLACK,
|
10
|
+
'white' => Pixelart::Color::WHITE,
|
11
|
+
'red' => 0xff0000ff,
|
12
|
+
'yellow' => 0xffff00ff, # r(ed) + g(reen) = yellow
|
13
|
+
}
|
14
|
+
|
15
|
+
|
16
|
+
colors.each do |name,color|
|
17
|
+
puts "==> #{name} - #{color}"
|
18
|
+
print " transparent? "; pp Pixelart::Color.transparent?( color )
|
19
|
+
print " opaque? "; pp Pixelart::Color.opaque?( color )
|
20
|
+
|
21
|
+
print " to_hex: "; pp Pixelart::Color.to_hex( color )
|
22
|
+
print " to_hsl: "; pp Pixelart::Color.to_hsl( color )
|
23
|
+
print " to_hsv: "; pp Pixelart::Color.to_hsv( color )
|
24
|
+
print " "; puts Pixelart::Color.format( color )
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
puts "bye"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
###
|
2
|
+
# to run use
|
3
|
+
# ruby -I ./lib sandbox/test_gradient.rb
|
4
|
+
|
5
|
+
|
6
|
+
require 'pixelart/colors/base'
|
7
|
+
|
8
|
+
|
9
|
+
gradient = Pixelart::Gradient.new( '000000', 'ffffff' )
|
10
|
+
# gradient = Pixelart::Gradient.new( 'ffffff', '000000' )
|
11
|
+
|
12
|
+
pp colors = gradient.colors( 256 ) ## 256 steps
|
13
|
+
puts "---"
|
14
|
+
pp colors.map { |color| Pixelart::Color.format( color ) }
|
15
|
+
|
16
|
+
puts
|
17
|
+
puts "---"
|
18
|
+
pp colors = gradient.colors( 10 ) ## 10 steps
|
19
|
+
puts "---"
|
20
|
+
pp colors.map { |color| Pixelart::Color.format( color ) }
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
gradient = Pixelart::Gradient.new( '000000',
|
25
|
+
'050505',
|
26
|
+
'0a0a0a', '0e0e0e'
|
27
|
+
)
|
28
|
+
puts
|
29
|
+
puts "---"
|
30
|
+
pp colors = gradient.colors( 15 ) ## 15 steps (3x5)
|
31
|
+
puts "---"
|
32
|
+
pp colors.map { |color| Pixelart::Color.format( color ) }
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
####################
|
37
|
+
# https://uigradients.com/#Instagram
|
38
|
+
|
39
|
+
uigradients = {
|
40
|
+
'instagram' => ['833ab4',
|
41
|
+
'fd1d1d', 'fcb045'],
|
42
|
+
'flare' => ['f12711', 'f5af19'],
|
43
|
+
'terminal' => ['000000', '0f9b0f'],
|
44
|
+
'the_blue_lagoon' => ['43c6ac', '191654'],
|
45
|
+
'ibiza_sunset' => ['ee0979', 'ff6a00'],
|
46
|
+
'superman' => ['0099f7', 'f11712'],
|
47
|
+
'christmas' => ['2f7336', 'aa3a38'],
|
48
|
+
'dark_knight' => ['ba8b02', '181818'],
|
49
|
+
'ukraine' => ['004ff9', 'fff94c'],
|
50
|
+
}
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
uigradients.each do |name, stops|
|
55
|
+
puts
|
56
|
+
puts "==> #{name}:"
|
57
|
+
gradient = Pixelart::Gradient.new( *stops )
|
58
|
+
colors = gradient.colors( 256 )
|
59
|
+
pp colors.map { |color| Pixelart::Color.format( color ) }
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "bye"
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pixelart-colors
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gerald Bauer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rdoc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '7'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '7'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hoe
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.23'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.23'
|
47
|
+
description: pixelart-colors - helpers to work with true colors as integers; 256 shades
|
48
|
+
for r/g/b (red, green and blue) totaling 16 million colors; incl. hsl, hsv color
|
49
|
+
space converters and more
|
50
|
+
email: wwwmake@googlegroups.com
|
51
|
+
executables: []
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files:
|
54
|
+
- CHANGELOG.md
|
55
|
+
- Manifest.txt
|
56
|
+
- README.md
|
57
|
+
files:
|
58
|
+
- CHANGELOG.md
|
59
|
+
- Manifest.txt
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- lib/pixelart/colors.rb
|
63
|
+
- lib/pixelart/colors/base.rb
|
64
|
+
- lib/pixelart/colors/color.rb
|
65
|
+
- lib/pixelart/colors/format.rb
|
66
|
+
- lib/pixelart/colors/gradient.rb
|
67
|
+
- lib/pixelart/colors/palette.rb
|
68
|
+
- lib/pixelart/colors/version.rb
|
69
|
+
- sandbox/test_colors.rb
|
70
|
+
- sandbox/test_gradient.rb
|
71
|
+
homepage: https://github.com/pixelartexchange/pixelart
|
72
|
+
licenses:
|
73
|
+
- Public Domain
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options:
|
77
|
+
- "--main"
|
78
|
+
- README.md
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '2.3'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.3.7
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: pixelart-colors - helpers to work with true colors as integers; 256 shades
|
96
|
+
for r/g/b (red, green and blue) totaling 16 million colors; incl. hsl, hsv color
|
97
|
+
space converters and more
|
98
|
+
test_files: []
|