pixelart 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -3
- data/Manifest.txt +5 -0
- data/README.md +290 -290
- data/Rakefile +32 -32
- data/lib/pixelart/base.rb +93 -84
- data/lib/pixelart/blur.rb +19 -19
- data/lib/pixelart/circle.rb +46 -0
- data/lib/pixelart/color.rb +131 -131
- data/lib/pixelart/composite.rb +154 -154
- data/lib/pixelart/generator.rb +199 -0
- data/lib/pixelart/gradient.rb +106 -106
- data/lib/pixelart/image.rb +283 -272
- data/lib/pixelart/led.rb +37 -37
- data/lib/pixelart/misc.rb +66 -66
- data/lib/pixelart/palette.rb +72 -72
- data/lib/pixelart/pixelator.rb +165 -165
- data/lib/pixelart/sample.rb +120 -0
- data/lib/pixelart/silhouette.rb +35 -35
- data/lib/pixelart/sketch.rb +69 -69
- data/lib/pixelart/spots.rb +146 -146
- data/lib/pixelart/stripes.rb +116 -0
- data/lib/pixelart/transparent.rb +60 -60
- data/lib/pixelart/ukraine.rb +20 -0
- data/lib/pixelart/vector.rb +163 -163
- data/lib/pixelart/version.rb +22 -22
- data/lib/pixelart.rb +12 -12
- metadata +11 -6
data/lib/pixelart/image.rb
CHANGED
@@ -1,272 +1,283 @@
|
|
1
|
-
module Pixelart
|
2
|
-
|
3
|
-
class Image
|
4
|
-
|
5
|
-
def self.read( path ) ## convenience helper
|
6
|
-
img_inner = ChunkyPNG::Image.from_file( path )
|
7
|
-
img = new( img_inner.width, img_inner.height, img_inner )
|
8
|
-
img
|
9
|
-
end
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
CHARS = '.@xo^~%*+=:' ## todo/check: rename to default chars or such? why? why not?
|
14
|
-
|
15
|
-
## todo/check: support default chars encoding auto-of-the-box always
|
16
|
-
## or require user-defined chars to be passed in - why? why not?
|
17
|
-
def self.parse( pixels, colors:, chars: CHARS )
|
18
|
-
has_keys = colors.is_a?(Hash) ## check if passed-in user-defined keys (via hash table)?
|
19
|
-
|
20
|
-
colors = parse_colors( colors )
|
21
|
-
pixels = parse_pixels( pixels )
|
22
|
-
|
23
|
-
width = pixels.reduce(1) {|width,row| row.size > width ? row.size : width }
|
24
|
-
height = pixels.size
|
25
|
-
|
26
|
-
img = new( width, height )
|
27
|
-
|
28
|
-
pixels.each_with_index do |row,y|
|
29
|
-
row.each_with_index do |color,x|
|
30
|
-
pixel = if has_keys ## if passed-in user-defined keys check only the user-defined keys
|
31
|
-
colors[color]
|
32
|
-
else
|
33
|
-
## try map ascii art char (.@xo etc.) to color index (0,1,2)
|
34
|
-
## if no match found - fallback on assuming draw by number (0 1 2 etc.) encoding
|
35
|
-
pos = chars.index( color )
|
36
|
-
if pos
|
37
|
-
colors[ pos.to_s ]
|
38
|
-
else ## assume nil (not found)
|
39
|
-
colors[ color ]
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
img[x,y] = pixel
|
44
|
-
end # each row
|
45
|
-
end # each data
|
46
|
-
|
47
|
-
img
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
def initialize( width, height, initial=Color::TRANSPARENT )
|
53
|
-
### todo/fix:
|
54
|
-
## change params to *args only - why? why not?
|
55
|
-
## make width/height optional if image passed in?
|
56
|
-
|
57
|
-
if initial.is_a?( ChunkyPNG::Image )
|
58
|
-
@img = initial
|
59
|
-
else
|
60
|
-
## todo/check - initial - use parse_color here too e.g. allow "#fff" too etc.
|
61
|
-
@img = ChunkyPNG::Image.new( width, height, initial )
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
def zoom( zoom=2, spacing: 0 )
|
68
|
-
## create a new zoom factor x image (2x, 3x, etc.)
|
69
|
-
|
70
|
-
width = @img.width*zoom+(@img.width-1)*spacing
|
71
|
-
height = @img.height*zoom+(@img.height-1)*spacing
|
72
|
-
|
73
|
-
img = Image.new( width, height )
|
74
|
-
|
75
|
-
@img.width.times do |x|
|
76
|
-
@img.height.times do |y|
|
77
|
-
pixel = @img[x,y]
|
78
|
-
zoom.times do |n|
|
79
|
-
zoom.times do |m|
|
80
|
-
img[n+zoom*x+spacing*x,
|
81
|
-
m+zoom*y+spacing*y] = pixel
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end # each x
|
85
|
-
end # each y
|
86
|
-
|
87
|
-
img
|
88
|
-
end
|
89
|
-
alias_method :scale, :zoom
|
90
|
-
|
91
|
-
|
92
|
-
def crop( x, y, crop_width, crop_height )
|
93
|
-
Image.new( nil, nil,
|
94
|
-
image.crop( x,y, crop_width, crop_height ) )
|
95
|
-
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
#######################
|
100
|
-
## filter / effects
|
101
|
-
|
102
|
-
def grayscale
|
103
|
-
img = @img.grayscale
|
104
|
-
Image.new( img.width, img.height, img )
|
105
|
-
end
|
106
|
-
alias_method :greyscale, :grayscale
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
def flip
|
111
|
-
img = @img.flip
|
112
|
-
Image.new( img.width, img.height, img )
|
113
|
-
end
|
114
|
-
alias_method :flip_horizontally, :flip
|
115
|
-
|
116
|
-
def mirror
|
117
|
-
img = @img.mirror
|
118
|
-
Image.new( img.width, img.height, img )
|
119
|
-
end
|
120
|
-
alias_method :flip_vertically, :mirror
|
121
|
-
alias_method :flop, :mirror
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
img = @img.
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
##
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
##
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
end
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
##
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
class Image
|
4
|
+
|
5
|
+
def self.read( path ) ## convenience helper
|
6
|
+
img_inner = ChunkyPNG::Image.from_file( path )
|
7
|
+
img = new( img_inner.width, img_inner.height, img_inner )
|
8
|
+
img
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
CHARS = '.@xo^~%*+=:' ## todo/check: rename to default chars or such? why? why not?
|
14
|
+
|
15
|
+
## todo/check: support default chars encoding auto-of-the-box always
|
16
|
+
## or require user-defined chars to be passed in - why? why not?
|
17
|
+
def self.parse( pixels, colors:, chars: CHARS )
|
18
|
+
has_keys = colors.is_a?(Hash) ## check if passed-in user-defined keys (via hash table)?
|
19
|
+
|
20
|
+
colors = parse_colors( colors )
|
21
|
+
pixels = parse_pixels( pixels )
|
22
|
+
|
23
|
+
width = pixels.reduce(1) {|width,row| row.size > width ? row.size : width }
|
24
|
+
height = pixels.size
|
25
|
+
|
26
|
+
img = new( width, height )
|
27
|
+
|
28
|
+
pixels.each_with_index do |row,y|
|
29
|
+
row.each_with_index do |color,x|
|
30
|
+
pixel = if has_keys ## if passed-in user-defined keys check only the user-defined keys
|
31
|
+
colors[color]
|
32
|
+
else
|
33
|
+
## try map ascii art char (.@xo etc.) to color index (0,1,2)
|
34
|
+
## if no match found - fallback on assuming draw by number (0 1 2 etc.) encoding
|
35
|
+
pos = chars.index( color )
|
36
|
+
if pos
|
37
|
+
colors[ pos.to_s ]
|
38
|
+
else ## assume nil (not found)
|
39
|
+
colors[ color ]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
img[x,y] = pixel
|
44
|
+
end # each row
|
45
|
+
end # each data
|
46
|
+
|
47
|
+
img
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
def initialize( width, height, initial=Color::TRANSPARENT )
|
53
|
+
### todo/fix:
|
54
|
+
## change params to *args only - why? why not?
|
55
|
+
## make width/height optional if image passed in?
|
56
|
+
|
57
|
+
if initial.is_a?( ChunkyPNG::Image )
|
58
|
+
@img = initial
|
59
|
+
else
|
60
|
+
## todo/check - initial - use parse_color here too e.g. allow "#fff" too etc.
|
61
|
+
@img = ChunkyPNG::Image.new( width, height, initial )
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
def zoom( zoom=2, spacing: 0 )
|
68
|
+
## create a new zoom factor x image (2x, 3x, etc.)
|
69
|
+
|
70
|
+
width = @img.width*zoom+(@img.width-1)*spacing
|
71
|
+
height = @img.height*zoom+(@img.height-1)*spacing
|
72
|
+
|
73
|
+
img = Image.new( width, height )
|
74
|
+
|
75
|
+
@img.width.times do |x|
|
76
|
+
@img.height.times do |y|
|
77
|
+
pixel = @img[x,y]
|
78
|
+
zoom.times do |n|
|
79
|
+
zoom.times do |m|
|
80
|
+
img[n+zoom*x+spacing*x,
|
81
|
+
m+zoom*y+spacing*y] = pixel
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end # each x
|
85
|
+
end # each y
|
86
|
+
|
87
|
+
img
|
88
|
+
end
|
89
|
+
alias_method :scale, :zoom
|
90
|
+
|
91
|
+
|
92
|
+
def crop( x, y, crop_width, crop_height )
|
93
|
+
Image.new( nil, nil,
|
94
|
+
image.crop( x,y, crop_width, crop_height ) )
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
#######################
|
100
|
+
## filter / effects
|
101
|
+
|
102
|
+
def grayscale
|
103
|
+
img = @img.grayscale
|
104
|
+
Image.new( img.width, img.height, img )
|
105
|
+
end
|
106
|
+
alias_method :greyscale, :grayscale
|
107
|
+
|
108
|
+
|
109
|
+
|
110
|
+
def flip
|
111
|
+
img = @img.flip
|
112
|
+
Image.new( img.width, img.height, img )
|
113
|
+
end
|
114
|
+
alias_method :flip_horizontally, :flip
|
115
|
+
|
116
|
+
def mirror
|
117
|
+
img = @img.mirror
|
118
|
+
Image.new( img.width, img.height, img )
|
119
|
+
end
|
120
|
+
alias_method :flip_vertically, :mirror
|
121
|
+
alias_method :flop, :mirror
|
122
|
+
|
123
|
+
|
124
|
+
def rotate_counter_clockwise # 90 degrees
|
125
|
+
img = @img.rotate_counter_clockwise
|
126
|
+
Image.new( img.width, img.height, img )
|
127
|
+
end
|
128
|
+
alias_method :rotate_left, :rotate_counter_clockwise
|
129
|
+
|
130
|
+
def rotate_clockwise # 90 degrees
|
131
|
+
img = @img.rotate_clockwise
|
132
|
+
Image.new( img.width, img.height, img )
|
133
|
+
end
|
134
|
+
alias_method :rotate_right, :rotate_clockwise
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
## add replace_colors alias too? - why? why not?
|
139
|
+
def change_colors( color_map )
|
140
|
+
color_map = _parse_color_map( color_map )
|
141
|
+
|
142
|
+
img = @img.dup ## note: make a deep copy!!!
|
143
|
+
_change_colors!( img, color_map )
|
144
|
+
|
145
|
+
## wrap into Pixelart::Image - lets you use zoom() and such
|
146
|
+
Image.new( img.width, img.height, img )
|
147
|
+
end
|
148
|
+
alias_method :recolor, :change_colors
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
## predefined palette8bit color maps
|
153
|
+
## (grayscale to sepia/blue/false/etc.)
|
154
|
+
## - todo/check - keep "shortcut" convenience predefined map - why? why not?
|
155
|
+
PALETTE8BIT = {
|
156
|
+
sepia: Palette8bit::GRAYSCALE.zip( Palette8bit::SEPIA ).to_h,
|
157
|
+
blue: Palette8bit::GRAYSCALE.zip( Palette8bit::BLUE ).to_h,
|
158
|
+
false: Palette8bit::GRAYSCALE.zip( Palette8bit::FALSE ).to_h,
|
159
|
+
}
|
160
|
+
|
161
|
+
def change_palette8bit( palette )
|
162
|
+
## step 0: mapping from grayscale to new 8bit palette (256 colors)
|
163
|
+
color_map = if palette.is_a?( String ) || palette.is_a?( Symbol )
|
164
|
+
PALETTE8BIT[ palette.to_sym ]
|
165
|
+
## todo/fix: check for missing/undefined palette not found - why? why not?
|
166
|
+
else
|
167
|
+
## make sure we have colors all in Integer not names, hex, etc.
|
168
|
+
palette = _parse_colors( palette )
|
169
|
+
Palette8bit::GRAYSCALE.zip( palette ).to_h
|
170
|
+
end
|
171
|
+
|
172
|
+
## step 1: convert to grayscale (256 colors)
|
173
|
+
img = @img.grayscale
|
174
|
+
_change_colors!( img, color_map )
|
175
|
+
|
176
|
+
## wrap into Pixelart::Image - lets you use zoom() and such
|
177
|
+
Image.new( img.width, img.height, img )
|
178
|
+
end
|
179
|
+
alias_method :change_palette256, :change_palette8bit
|
180
|
+
|
181
|
+
|
182
|
+
####
|
183
|
+
## private helpers
|
184
|
+
def _parse_colors( colors )
|
185
|
+
colors.map {|color| Color.parse( color ) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def _parse_color_map( color_map )
|
189
|
+
color_map.map do |k,v|
|
190
|
+
[Color.parse(k), Color.parse(v)]
|
191
|
+
end.to_h
|
192
|
+
end
|
193
|
+
|
194
|
+
def _change_colors!( img, color_map )
|
195
|
+
img.width.times do |x|
|
196
|
+
img.height.times do |y|
|
197
|
+
color = img[x,y]
|
198
|
+
new_color = color_map[color]
|
199
|
+
img[x,y] = new_color if new_color
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
#####
|
208
|
+
# (image) delegates
|
209
|
+
## todo/check: add some more??
|
210
|
+
def save( path, constraints = {} )
|
211
|
+
# step 1: make sure outdir exits
|
212
|
+
outdir = File.dirname( path )
|
213
|
+
FileUtils.mkdir_p( outdir ) unless Dir.exist?( outdir )
|
214
|
+
|
215
|
+
# step 2: save
|
216
|
+
@img.save( path, constraints )
|
217
|
+
end
|
218
|
+
alias_method :write, :save
|
219
|
+
|
220
|
+
|
221
|
+
def compose!( other, x=0, y=0 )
|
222
|
+
@img.compose!( other.image, x, y ) ## note: "unwrap" inner image ref
|
223
|
+
end
|
224
|
+
alias_method :paste!, :compose!
|
225
|
+
|
226
|
+
|
227
|
+
def width() @img.width; end
|
228
|
+
def height() @img.height; end
|
229
|
+
|
230
|
+
def []( x, y ) @img[x,y]; end
|
231
|
+
def []=( x, y, value ) @img[x,y]=value; end
|
232
|
+
|
233
|
+
def pixels() @img.pixels; end
|
234
|
+
|
235
|
+
### todo/check: add colors() e.g. @img.pixels.uniq - why? why not?
|
236
|
+
|
237
|
+
|
238
|
+
## return image ref - use a different name - why? why not?
|
239
|
+
## change to to_image - why? why not?
|
240
|
+
def image() @img; end
|
241
|
+
|
242
|
+
|
243
|
+
|
244
|
+
|
245
|
+
######
|
246
|
+
# helpers
|
247
|
+
def self.parse_pixels( pixels )
|
248
|
+
data = []
|
249
|
+
pixels.each_line do |line|
|
250
|
+
line = line.strip
|
251
|
+
next if line.start_with?( '#' ) || line.empty? ## note: allow comments and empty lines
|
252
|
+
|
253
|
+
## note: allow multiple spaces or tabs to separate pixel codes
|
254
|
+
## e.g. o o o o o o o o o o o o dg lg w w lg w lg lg dg dg w w lg dg o o o o o o o o o o o
|
255
|
+
## or
|
256
|
+
data << line.split( /[ \t]+/)
|
257
|
+
end
|
258
|
+
data
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
|
263
|
+
def self.parse_colors( colors )
|
264
|
+
if colors.is_a?( Array ) ## convenience shortcut
|
265
|
+
## note: always auto-add color 0 as pre-defined transparent - why? why not?
|
266
|
+
h = { '0' => Color::TRANSPARENT }
|
267
|
+
colors.each_with_index do |color, i|
|
268
|
+
h[ (i+1).to_s ] = Color.parse( color )
|
269
|
+
end
|
270
|
+
h
|
271
|
+
else ## assume hash table with color map
|
272
|
+
## convert into ChunkyPNG::Color
|
273
|
+
colors.map do |key,color|
|
274
|
+
## always convert key to string why? why not? use symbol?
|
275
|
+
[ key.to_s, Color.parse( color ) ]
|
276
|
+
end.to_h
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
|
281
|
+
end # class Image
|
282
|
+
end # module Pixelart
|
283
|
+
|
data/lib/pixelart/led.rb
CHANGED
@@ -1,37 +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
|
-
|
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
|
+
|