pixelart 0.1.3 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cc4a5d1c756256a1162bdab5208ff01549b7e2543ccb301457845dd1f393b48
4
- data.tar.gz: 1f07a5a8203fc4b4fc93446bc52d6abf8ca1b94368c48952649c423ef0100b15
3
+ metadata.gz: 3c72ac16513ce785c2bfcd63fe1d230950d03d597e0def19d4a35034b743d513
4
+ data.tar.gz: dd3fcd6b2af8eaade1f9dc7bc7566af24ee3b5688d86363845cfc4794ee426d5
5
5
  SHA512:
6
- metadata.gz: 6031f337310c6935841ae3a9c2528f4aa6179c8a5b6b9a4b4aa21d0f98b9a9dd5a0578792cce660be1f15c11d95e35785efa80547e1950d28cf8e3ff21a05243
7
- data.tar.gz: 4615099b098378c2eee14cb854c2ee67237a278172b3c789be1de72a59c9ba6779e8dc2a4e9a4384c93b2771d5c8f31a8cf1f51cff308d6bf0e27468a50d0cb3
6
+ metadata.gz: 4f299efeddddc2fe143595fbee1fa5b68fedd39f643da7700e25702398f5d3d3a5adc141268fa127dc669db476a41d64971b563997653ee1b2e0af8d418163eb
7
+ data.tar.gz: e8cc7732cc24c04bcfb076f0ae48f088f2a48c4e83f23b897b194ae7def003effd8c08da8c0a7cde08fb55ea09ae2bf2ce0d9c017d38b759531e53881c5e7871
data/Manifest.txt CHANGED
@@ -3,7 +3,12 @@ Manifest.txt
3
3
  README.md
4
4
  Rakefile
5
5
  lib/pixelart.rb
6
+ lib/pixelart/base.rb
6
7
  lib/pixelart/color.rb
7
8
  lib/pixelart/gradient.rb
8
9
  lib/pixelart/image.rb
10
+ lib/pixelart/led.rb
11
+ lib/pixelart/misc.rb
12
+ lib/pixelart/palette.rb
13
+ lib/pixelart/pixelator.rb
9
14
  lib/pixelart/version.rb
data/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  pixelart - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes
4
4
 
5
5
 
6
- * home :: [github.com/cryptocopycats/mooncats](https://github.com/cryptocopycats/mooncats)
7
- * bugs :: [github.com/cryptocopycats/mooncats/issues](https://github.com/cryptocopycats/mooncats/issues)
6
+ * home :: [github.com/rubycoco/pixel](https://github.com/rubycoco/pixel)
7
+ * bugs :: [github.com/rubycoco/pixel/issues](https://github.com/rubycoco/pixel/issues)
8
8
  * gem :: [rubygems.org/gems/pixelart](https://rubygems.org/gems/pixelart)
9
9
  * rdoc :: [rubydoc.info/gems/pixelart](http://rubydoc.info/gems/pixelart)
10
10
 
@@ -58,7 +58,7 @@ Note: The color 0 (transparent) is auto-magically added / defined.
58
58
  And let's mint a mooncat image:
59
59
 
60
60
  ``` ruby
61
- img = Pixelart::Image.parse( pixels, colors: colors )
61
+ img = Image.parse( pixels, colors: colors )
62
62
  img.save( './i/mooncat_white.png' )
63
63
  ```
64
64
 
@@ -71,8 +71,8 @@ img3x.save( './i/mooncat_white-3x.png' )
71
71
 
72
72
  Voila!
73
73
 
74
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/mooncat_white.png)
75
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/mooncat_white-3x.png)
74
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/mooncat_white.png)
75
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/mooncat_white-3x.png)
76
76
 
77
77
 
78
78
 
@@ -91,7 +91,7 @@ colors = [
91
91
  And let's start minting:
92
92
 
93
93
  ``` ruby
94
- img = Pixelart::Image.parse( pixels, colors: colors )
94
+ img = Image.parse( pixels, colors: colors )
95
95
  img.save( './i/mooncat_black.png' )
96
96
 
97
97
  img3x = img.zoom( 3 )
@@ -100,8 +100,8 @@ img3x.save( './i/mooncat_black-3x.png' )
100
100
 
101
101
  Voila! Black is the new White!
102
102
 
103
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/mooncat_black.png)
104
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/mooncat_black-3x.png)
103
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/mooncat_black.png)
104
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/mooncat_black-3x.png)
105
105
 
106
106
 
107
107
 
@@ -214,7 +214,7 @@ colors = {
214
214
  And let's mint an imperial master image:
215
215
 
216
216
  ``` ruby
217
- img = Pixelart::Image.parse( pixels, colors: colors )
217
+ img = Image.parse( pixels, colors: colors )
218
218
  img.save( './i/vader.png' )
219
219
  ```
220
220
 
@@ -227,8 +227,52 @@ img5x.save( './i/vader5x.png' )
227
227
 
228
228
  Voila!
229
229
 
230
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/vader.png)
231
- ![](https://github.com/cryptocopycats/mooncats/raw/master/pixelart/i/vader5x.png)
230
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/vader.png)
231
+ ![](https://github.com/rubycoco/pixel/raw/master/pixelart/i/vader5x.png)
232
+
233
+
234
+
235
+
236
+ ## Modular "Base" Version
237
+
238
+
239
+ Note: By default if you require pixelart
240
+ all classes inside the `Pixelart` module such as `Image`, `Color`, `Gradient`, `Palette8bit`, etc. get "top-leveled", that is,
241
+ included in the top level e.g.:
242
+
243
+ ``` ruby
244
+ require 'pixelart/base'
245
+ include Pixelart
246
+ ```
247
+
248
+ And now you can use all classes without
249
+ the `Pixelart::` module scope e.g.:
250
+
251
+ ``` ruby
252
+ gradient = Gradient.new( '000000', 'ffffff' )
253
+
254
+ pp colors = gradient.colors( 256 )
255
+ puts '---'
256
+ pp colors.map { |color| Color.to_hex( color ) }
257
+ ```
258
+
259
+ vs
260
+
261
+ ``` ruby
262
+ gradient = Pixelart::Gradient.new( '000000', 'ffffff' )
263
+
264
+ pp colors = gradient.colors( 256 )
265
+ puts '---'
266
+ pp colors.map { |color| Pixelart::Color.to_hex( color ) }
267
+ ```
268
+
269
+
270
+ For a "stricter" modular version require the "base" version
271
+ that always requires the `Pixelart::` module scope e.g.:
272
+
273
+ ``` ruby
274
+ require 'pixelart/base'
275
+ ```
232
276
 
233
277
 
234
278
 
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ Hoe.spec 'pixelart' do
8
8
  self.summary = "pixelart - mint your own pixel art images off chain using any design (in ascii text) in any colors; incl. 2x/4x/8x zoom for bigger sizes"
9
9
  self.description = summary
10
10
 
11
- self.urls = { home: 'https://github.com/cryptocopycats/mooncats' }
11
+ self.urls = { home: 'https://github.com/rubycoco/pixel' }
12
12
 
13
13
  self.author = 'Gerald Bauer'
14
14
  self.email = 'wwwmake@googlegroups.com'
data/lib/pixelart.rb CHANGED
@@ -1,26 +1,10 @@
1
- ## 3rd party
2
- require 'chunky_png'
3
1
 
4
- ## stdlib
5
- require 'pp'
6
- require 'time'
7
- require 'date'
8
- require 'fileutils'
2
+ ## our own code (without "top-level" shortcuts e.g. "modular version")
3
+ require 'pixelart/base' # aka "strict(er)" version
9
4
 
10
5
 
11
- ## our own code
12
- require 'pixelart/version' # note: let version always go first
13
- require 'pixelart/color'
14
- require 'pixelart/gradient'
15
- require 'pixelart/image'
6
+ ###
7
+ # add convenience top-level shortcuts / aliases
8
+ # make Image, Color, Palette8bit, etc top-level
9
+ include Pixelart
16
10
 
17
-
18
-
19
-
20
-
21
- ### add some convenience shortcuts
22
- PixelArt = Pixelart
23
-
24
-
25
-
26
- puts Pixelart.banner # say hello
@@ -0,0 +1,40 @@
1
+ ## 3rd party
2
+ require 'chunky_png'
3
+
4
+ ## stdlib
5
+ require 'pp'
6
+ require 'time'
7
+ require 'date'
8
+ require 'fileutils'
9
+
10
+
11
+ ## our own code
12
+ require 'pixelart/version' # note: let version always go first
13
+ require 'pixelart/color'
14
+ require 'pixelart/gradient'
15
+ require 'pixelart/palette'
16
+ require 'pixelart/image'
17
+
18
+ require 'pixelart/pixelator'
19
+
20
+ require 'pixelart/misc' ## misc helpers
21
+
22
+ require 'pixelart/led' ## (special) effects / filters
23
+
24
+
25
+
26
+
27
+ ##########
28
+ # add some spelling convenience variants
29
+ PixelArt = Pixelart
30
+
31
+ module Pixelart
32
+ Palette256 = Palette8Bit = Palette8bit
33
+
34
+ Palette256Image = Palette8BitImage = Palette8bitImage =
35
+ ImagePalette256 = ImagePalette8Bit = ImagePalette8bit
36
+ end
37
+
38
+
39
+
40
+ puts Pixelart.banner # say hello
@@ -56,6 +56,75 @@ class Color
56
56
  def self.b( color ) ChunkyPNG::Color.b( color ); end
57
57
 
58
58
  def self.rgb( r, g, b ) ChunkyPNG::Color.rgb( r, g, b); end
59
+
60
+
61
+
62
+ ## known built-in color names
63
+ def self.build_names
64
+ names = {
65
+ '#00000000' => 'TRANSPARENT',
66
+ '#000000ff' => 'BLACK',
67
+ '#ffffffff' => 'WHITE',
68
+ }
69
+
70
+ ## auto-add grayscale 1 to 254
71
+ (1..254).each do |n|
72
+ hex = "#" + ('%02x' % n)*3
73
+ hex << "ff" ## add alpha channel (255)
74
+ names[ hex ] = "8-BIT GRAYSCALE ##{n}"
75
+ end
76
+
77
+ names
78
+ end
79
+
80
+ NAMES = build_names
81
+
82
+
83
+
84
+ def self.format( color )
85
+ rgb = [r(color),
86
+ g(color),
87
+ b(color)]
88
+
89
+ # rgb in hex (string format)
90
+ # note: do NOT include alpha channel for now - why? why not?
91
+ hex = "#" + rgb.map{|num| '%02x' % num }.join
92
+
93
+ hsl = to_hsl( color )
94
+ ## get alpha channel (transparency) for hsla
95
+ alpha = hsl[3]
96
+
97
+
98
+ buf = ''
99
+ buf << hex
100
+ buf << " / "
101
+ buf << "rgb("
102
+ buf << "%3d " % rgb[0]
103
+ buf << "%3d " % rgb[1]
104
+ buf << "%3d)" % rgb[2]
105
+ buf << " - "
106
+ buf << "hsl("
107
+ buf << "%3d° " % (hsl[0] % 360)
108
+ buf << "%3d%% " % (hsl[1]*100+0.5).to_i
109
+ buf << "%3d%%)" % (hsl[2]*100+0.5).to_i
110
+
111
+ if alpha != 255
112
+ buf << " - α(%3d%%)" % (alpha*100/255+0.5).to_i
113
+ else
114
+ buf << " " ## add empty for 255 (full opacity)
115
+ end
116
+
117
+ ## note: add alpha channel to hex
118
+ alpha_hex = '%02x' % alpha
119
+ name = NAMES[ hex+alpha_hex ]
120
+ buf << " - #{name}" if name
121
+
122
+ buf
123
+ end
124
+ class << self
125
+ alias_method :fmt, :format
126
+ end
127
+
59
128
  end # class Color
60
129
  end # module Pixelart
61
130
 
@@ -1,8 +1,9 @@
1
1
 
2
- ## inspired by
2
+ ## inspired / helped by
3
3
  ## https://en.wikipedia.org/wiki/List_of_software_palettes#Color_gradient_palettes
4
4
  ## https://github.com/mistic100/tinygradient
5
5
  ## https://mistic100.github.io/tinygradient/
6
+ ## https://bsouthga.dev/posts/color-gradients-with-python
6
7
 
7
8
 
8
9
  module Pixelart
@@ -21,23 +22,36 @@ class Gradient
21
22
 
22
23
 
23
24
  def colors( steps )
24
- ## note: for now only support two colors
25
- ## note: gradient will include start (first)
26
- ## AND stop color (last) - stop color is NOT excluded for now!!
27
- start = @stops[0]
28
- stop = @stops[1]
29
-
30
- step = stepize( start, stop, steps-1 )
31
- ## pp step
32
-
33
- gradient = [start]
34
-
35
- 1.upto(steps-2).each do |i|
36
- color = interpolate( step, start, i )
37
- gradient << color
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
38
54
  end
39
- ## note: use original passed in stop color (should match calculated)
40
- gradient << stop
41
55
 
42
56
  ## convert to color (Integer)
43
57
  gradient.map do |color|
@@ -45,52 +59,48 @@ class Gradient
45
59
  end
46
60
  end
47
61
 
48
- ##
49
- # Linearly compute the step size between start and end
50
- ## (not normalized)
51
- # @param {StepValue} start
52
- # @param {StepValue} end
53
- # @param {number} steps - number of desired steps
54
- #@return {StepValue}
55
62
 
56
- def stepize(start, stop, steps)
57
- step = []
58
63
 
59
- [0,1,2].each do |k|
60
- step << Float(stop[k] - start[k]) / Float(steps)
61
- end
62
-
63
- step
64
- end
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
65
74
 
66
- ##
67
- # Compute the final step color
68
- # @param {StepValue} step - from `stepize`
69
- # @param {StepValue} start
70
- # @param {number} i - color index
71
- # @return {StepValue}
75
+ color << value
76
+ end
77
+ color
78
+ end
72
79
 
73
- RGB_MAX = [256, 256, 256]
74
- def interpolate( step, start, i )
75
- color = []
76
80
 
77
- [0,1,2].each do |k|
78
- color[k] = step[k]*i + start[k]
81
+ def linear_gradient( start, stop, steps,
82
+ include_stop: true )
79
83
 
80
- color[k] = if color[k] < 0.0
81
- color[k] + RGB_MAX[k]
82
- else
83
- color[k] % RGB_MAX[k]
84
- end
84
+ gradient = [start] ## auto-add start color (first color in gradient)
85
85
 
86
- ## convert back to Integer from Float
87
- ## add 0.5 for rounding up (starting with 0.5) - why? why not?
88
- color[k] = (color[k]+0.5).to_i
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
89
97
  end
90
- color
98
+
99
+ gradient
91
100
  end
92
101
 
93
102
 
103
+
94
104
  end # class Gradient
95
105
  end # module Pixelart
96
106
 
@@ -65,19 +65,71 @@ alias_method :scale, :zoom
65
65
 
66
66
 
67
67
 
68
+ #######################
69
+ ## filter / effects
68
70
 
69
- def parse_color_map( color_map )
70
- color_map.map do |k,v|
71
- [Color.parse(k), Color.parse(v)]
72
- end.to_h
71
+ def grayscale
72
+ img = @img.grayscale
73
+ Image.new( img.width, img.height, img )
73
74
  end
74
75
 
75
76
  ## add replace_colors alias too? - why? why not?
76
77
  def change_colors( color_map )
78
+ color_map = _parse_color_map( color_map )
79
+
77
80
  img = @img.dup ## note: make a deep copy!!!
78
- color_map = parse_color_map( color_map )
79
- ## pp color_map
81
+ _change_colors!( img, color_map )
82
+
83
+ ## wrap into Pixelart::Image - lets you use zoom() and such
84
+ Image.new( img.width, img.height, img )
85
+ end
86
+ alias_method :recolor, :change_colors
87
+
88
+
89
+
90
+ ## predefined palette8bit color maps
91
+ ## (grayscale to sepia/blue/false/etc.)
92
+ ## - todo/check - keep "shortcut" convenience predefined map - why? why not?
93
+ PALETTE8BIT = {
94
+ sepia: Palette8bit::GRAYSCALE.zip( Palette8bit::SEPIA ).to_h,
95
+ blue: Palette8bit::GRAYSCALE.zip( Palette8bit::BLUE ).to_h,
96
+ false: Palette8bit::GRAYSCALE.zip( Palette8bit::FALSE ).to_h,
97
+ }
98
+
99
+ def change_palette8bit( palette )
100
+ ## step 0: mapping from grayscale to new 8bit palette (256 colors)
101
+ color_map = if palette.is_a?( String ) || palette.is_a?( Symbol )
102
+ PALETTE8BIT[ palette.to_sym ]
103
+ ## todo/fix: check for missing/undefined palette not found - why? why not?
104
+ else
105
+ ## make sure we have colors all in Integer not names, hex, etc.
106
+ palette = _parse_colors( palette )
107
+ Palette8bit::GRAYSCALE.zip( palette ).to_h
108
+ end
109
+
110
+ ## step 1: convert to grayscale (256 colors)
111
+ img = @img.grayscale
112
+ _change_colors!( img, color_map )
113
+
114
+ ## wrap into Pixelart::Image - lets you use zoom() and such
115
+ Image.new( img.width, img.height, img )
116
+ end
117
+ alias_method :change_palette256, :change_palette8bit
118
+
119
+
120
+ ####
121
+ ## private helpers
122
+ def _parse_colors( colors )
123
+ colors.map {|color| Color.parse( color ) }
124
+ end
125
+
126
+ def _parse_color_map( color_map )
127
+ color_map.map do |k,v|
128
+ [Color.parse(k), Color.parse(v)]
129
+ end.to_h
130
+ end
80
131
 
132
+ def _change_colors!( img, color_map )
81
133
  img.width.times do |x|
82
134
  img.height.times do |y|
83
135
  color = img[x,y]
@@ -85,12 +137,7 @@ def change_colors( color_map )
85
137
  img[x,y] = new_color if new_color
86
138
  end
87
139
  end
88
-
89
- ## wrap into Pixelart::Image - lets you use zoom() and such
90
- Image.new( img.width, img.height, img )
91
140
  end
92
- alias_method :recolor, :change_colors
93
-
94
141
 
95
142
 
96
143
 
@@ -124,6 +171,7 @@ def []=( x, y, value ) @img[x,y]=value; end
124
171
  def pixels() @img.pixels; end
125
172
 
126
173
  ## return image ref - use a different name - why? why not?
174
+ ## change to to_image - why? why not?
127
175
  def image() @img; end
128
176
 
129
177
 
@@ -0,0 +1,37 @@
1
+ module Pixelart
2
+
3
+
4
+ class Image
5
+ def led( led=8, spacing: 2, round_corner: false )
6
+
7
+ width = @img.width*led + (@img.width-1)*spacing
8
+ height = @img.height*led + (@img.height-1)*spacing
9
+
10
+ puts " #{width}x#{height}"
11
+
12
+ img = Image.new( width, height, Color::BLACK )
13
+
14
+ @img.width.times do |x|
15
+ @img.height.times do |y|
16
+ pixel = @img[x,y]
17
+ pixel = Color::BLACK if pixel == Color::TRANSPARENT
18
+ led.times do |n|
19
+ led.times do |m|
20
+ ## round a little - drop all four corners for now
21
+ next if round_corner &&
22
+ [[0,0],[0,1],[1,0],[1,1],[0,2],[2,0],
23
+ [0,led-1],[0,led-2],[1,led-1],[1,led-2],[0,led-3],[2,led-1],
24
+ [led-1,0],[led-1,1],[led-2,0],[led-2,1],[led-1,2],[led-3,0],
25
+ [led-1,led-1],[led-1,led-2],[led-2,led-1],[led-2,led-2],[led-1,led-3],[led-3,led-1],
26
+ ].include?( [n,m] )
27
+ img[x*led+n + spacing*x,
28
+ y*led+m + spacing*y] = pixel
29
+ end
30
+ end
31
+ end
32
+ end
33
+ img
34
+ end
35
+ end # class Image
36
+ end # module Pixelart
37
+
@@ -0,0 +1,37 @@
1
+ module Pixelart
2
+
3
+
4
+ class ImagePalette8bit < Image # or use Palette256 alias?
5
+ def initialize( colors, size: 1, spacing: nil )
6
+ ## todo/check: change size arg to pixel or such? better name/less confusing - why? why not?
7
+
8
+ ## todo/check: assert colors MUST have 256 colors!!!!
9
+
10
+ ## use a "smart" default if no spacing set
11
+ ## 0 for for (pixel) size == 1
12
+ ## 1 for the rest
13
+ spacing = size == 1 ? 0 : 1 if spacing.nil?
14
+
15
+ img = ChunkyPNG::Image.new( 32*size+(32-1)*spacing,
16
+ 8*size+(8-1)*spacing )
17
+
18
+ colors.each_with_index do |color,i|
19
+ y,x = i.divmod( 32 )
20
+ if size > 1
21
+ size.times do |n|
22
+ size.times do |m|
23
+ img[ x*size+n+spacing*x,
24
+ y*size+m+spacing*y] = color
25
+ end
26
+ end
27
+ else
28
+ img[x,y] = color
29
+ end
30
+ end
31
+
32
+ super( img.width, img.height, img )
33
+ end
34
+ end # class ImagePalette8bit
35
+ end # module Pixelart
36
+
37
+
@@ -0,0 +1,72 @@
1
+ module Pixelart
2
+
3
+
4
+ class Palette8bit # or use Palette256 alias?
5
+
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,117 @@
1
+ module Pixelart
2
+
3
+
4
+ class Pixelator # or use Minifier or such - rename - why? why not?
5
+
6
+ def initialize( img, width=24, height=24 )
7
+ @img = img.is_a?( Image ) ? img.image : img ## "unwrap" if Pixelart::Image
8
+ @width = width
9
+ @height = height
10
+
11
+ ## calculate pixel size / density / resolution
12
+ ## how many pixels per pixel?
13
+ @xsize, @xoverflow = img.width.divmod( width )
14
+ @ysize, @yoverflow = img.height.divmod( height )
15
+
16
+ puts "minify image size from (#{@img.width}x#{@img.height}) to (#{width}x#{height})"
17
+ puts " pixel size (#{@xsize}x#{@ysize}) - #{@xsize*@ysize} pixel(s) per pixel"
18
+ puts " overflow x: #{@xoverflow}, y: #{@yoverflow} pixel(s)" if @xoverflow > 0 || @yoverflow > 0
19
+ end
20
+
21
+
22
+ def grid( spacing: 10 )
23
+ width = @img.width + (@width-1)*spacing
24
+ height = @img.height + (@height-1)*spacing
25
+
26
+ img = ChunkyPNG::Image.new( width, height, ChunkyPNG::Color::WHITE )
27
+
28
+ @img.width.times do |x|
29
+ xpixel = x/@xsize
30
+ @img.height.times do |y|
31
+ ypixel = y/@ysize
32
+
33
+ ## clip overflow pixels
34
+ xpixel = @width-1 if xpixel >= @width
35
+ ypixel = @height-1 if ypixel >= @height
36
+
37
+ color = @img[x,y]
38
+ img[x + spacing*xpixel,
39
+ y + spacing*ypixel] = color
40
+ end
41
+ end
42
+
43
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
44
+ end
45
+
46
+
47
+ # pixels by coordinates (x/y) with color statistics / usage
48
+ def pixels
49
+ @pixels ||= begin
50
+ pixels = []
51
+ @img.width.times do |x|
52
+ xpixel = x/@xsize
53
+ @img.height.times do |y|
54
+ ypixel = y/@ysize
55
+
56
+ ## skip/cut off overflow pixels
57
+ next if xpixel >= @width || ypixel >= @height
58
+
59
+ color = @img[x,y]
60
+ colors = pixels[xpixel+ypixel*@width] ||= Hash.new(0)
61
+ colors[ color ] += 1
62
+ end
63
+ end
64
+
65
+ ## sort pixel colors by usage / count (highest first)
66
+ pixels = pixels.map do |pixel|
67
+ pixel.sort do |l,r|
68
+ r[1] <=> l[1]
69
+ end.to_h
70
+ end
71
+ pixels
72
+ end
73
+ end
74
+
75
+ def pixel(x,y) pixels[x+y*@width]; end
76
+ alias_method :[], :pixel
77
+
78
+
79
+ def can_pixelate?
80
+ # check if any pixel has NOT a color with a 50% majority?
81
+ count = 0
82
+ @width.times do |x|
83
+ @height.times do |y|
84
+ pixel = pixel( x, y )
85
+ sum = pixel.values.sum
86
+ color_count = pixel.values[0]
87
+ if color_count < (sum/2)
88
+ count += 1
89
+ ## todo/check: stor warn in a public errors or warns array - why? why not?
90
+ puts "!! WARN #{count} - pixel (#{x}/#{y}) - no majority (50%) color:"
91
+ pp pixel
92
+ end
93
+ end
94
+ end
95
+
96
+ count == 0 ## return true if not warnings found
97
+ end
98
+ alias_method :pixelate?, :can_pixelate?
99
+
100
+
101
+ def pixelate
102
+ img = ChunkyPNG::Image.new( @width, @height )
103
+
104
+ @width.times do |x|
105
+ @height.times do |y|
106
+ pixel = pixel( x, y )
107
+ color = pixel.keys[0]
108
+ img[x,y] = color
109
+ end
110
+ end
111
+
112
+ Image.new( img.width, img.height, img ) ## wrap in Pixelart::Image - why? why not?
113
+ end
114
+ end # class Pixelator
115
+ end # module Pixelart
116
+
117
+
@@ -3,7 +3,7 @@ module Pixelart
3
3
 
4
4
  MAJOR = 0
5
5
  MINOR = 1
6
- PATCH = 3
6
+ PATCH = 8
7
7
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
8
 
9
9
  def self.version
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pixelart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-16 00:00:00.000000000 Z
11
+ date: 2021-04-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: chunky_png
@@ -73,11 +73,16 @@ files:
73
73
  - README.md
74
74
  - Rakefile
75
75
  - lib/pixelart.rb
76
+ - lib/pixelart/base.rb
76
77
  - lib/pixelart/color.rb
77
78
  - lib/pixelart/gradient.rb
78
79
  - lib/pixelart/image.rb
80
+ - lib/pixelart/led.rb
81
+ - lib/pixelart/misc.rb
82
+ - lib/pixelart/palette.rb
83
+ - lib/pixelart/pixelator.rb
79
84
  - lib/pixelart/version.rb
80
- homepage: https://github.com/cryptocopycats/mooncats
85
+ homepage: https://github.com/rubycoco/pixel
81
86
  licenses:
82
87
  - Public Domain
83
88
  metadata: {}