pixelart-colors 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []