pixelart-colors 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ ### 0.0.1 / 2022-07-09
2
+
3
+ * Everything is new. First release
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: []