pixelart 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +2 -0
- data/lib/pixelart/base.rb +6 -0
- data/lib/pixelart/generator.rb +199 -0
- data/lib/pixelart/sample.rb +120 -0
- data/lib/pixelart/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9cb1460263f319b98c4d333f6caa42118eb94f583f9cc6cdaa5bfa6bc4316b9
|
4
|
+
data.tar.gz: 2deabe3c3bc35c62d249e0ff5178d23f986d1131c96daf53a8d36fde51cd6de8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57213049f46aac9a612adb3d9e236ce03aa336318705cabaed98f1f87557dc5dc08a73a7f50254852ab0c3c988ca0ed3d5b3ba6666925640ab837692c573be7c
|
7
|
+
data.tar.gz: 9ff7ae16a77eca4b2d4deb65b98747aa053ab97ab15b0f8a40d373f5ecb96dcd2c18d2fdfd18beca0c06a5e9330719dd2df1a3be1efac07c8f008dfa0b4db614
|
data/Manifest.txt
CHANGED
@@ -8,12 +8,14 @@ lib/pixelart/blur.rb
|
|
8
8
|
lib/pixelart/circle.rb
|
9
9
|
lib/pixelart/color.rb
|
10
10
|
lib/pixelart/composite.rb
|
11
|
+
lib/pixelart/generator.rb
|
11
12
|
lib/pixelart/gradient.rb
|
12
13
|
lib/pixelart/image.rb
|
13
14
|
lib/pixelart/led.rb
|
14
15
|
lib/pixelart/misc.rb
|
15
16
|
lib/pixelart/palette.rb
|
16
17
|
lib/pixelart/pixelator.rb
|
18
|
+
lib/pixelart/sample.rb
|
17
19
|
lib/pixelart/silhouette.rb
|
18
20
|
lib/pixelart/sketch.rb
|
19
21
|
lib/pixelart/spots.rb
|
data/lib/pixelart/base.rb
CHANGED
@@ -31,12 +31,18 @@ require 'pixelart/palette'
|
|
31
31
|
require 'pixelart/image'
|
32
32
|
require 'pixelart/composite'
|
33
33
|
|
34
|
+
require 'pixelart/sample' ## (down)sample / pixelate
|
35
|
+
|
34
36
|
|
35
37
|
require 'pixelart/pixelator'
|
36
38
|
|
37
39
|
require 'pixelart/misc' ## misc helpers
|
38
40
|
require 'pixelart/stripes'
|
39
41
|
|
42
|
+
|
43
|
+
require 'pixelart/generator' ## generate images from text via spritesheets
|
44
|
+
|
45
|
+
|
40
46
|
#########################
|
41
47
|
# (special) effects / filters / etc
|
42
48
|
require 'pixelart/circle'
|
@@ -0,0 +1,199 @@
|
|
1
|
+
####
|
2
|
+
# "simple" generator (no different sizes, genders, etc.)
|
3
|
+
# uses built-in spritesheet for (archetypes &) attributes
|
4
|
+
|
5
|
+
|
6
|
+
module Pixelart
|
7
|
+
|
8
|
+
class Metadata
|
9
|
+
class Sprite
|
10
|
+
attr_reader :id, :name, :type, :more_names
|
11
|
+
|
12
|
+
def initialize( id:,
|
13
|
+
name:,
|
14
|
+
type:,
|
15
|
+
more_names: [] )
|
16
|
+
@id = id # zero-based index eg. 0,1,2,3, etc.
|
17
|
+
@name = name
|
18
|
+
@type = type
|
19
|
+
@more_names = more_names
|
20
|
+
end
|
21
|
+
end # class Metadata::Sprite
|
22
|
+
end # class Metadata
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
class Generator
|
28
|
+
|
29
|
+
######
|
30
|
+
# static helpers - (turn into "true" static self.class methods - why? why not?)
|
31
|
+
#
|
32
|
+
def self.normalize_key( str )
|
33
|
+
## add & e.g. B&W
|
34
|
+
## add ' e.g. McDonald's Red
|
35
|
+
str.downcase.gsub(/[ ()&°'_-]/, '').strip
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.normalize_name( str )
|
39
|
+
## normalize spaces in more names
|
40
|
+
str.strip.gsub( /[ ]{2,}/, ' ' )
|
41
|
+
end
|
42
|
+
|
43
|
+
def normalize_key( str ) self.class.normalize_key( str ); end
|
44
|
+
def normalize_name( str ) self.class.normalize_name( str ); end
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
def build_attributes_by_name( recs )
|
49
|
+
h = {}
|
50
|
+
recs.each_with_index do |rec|
|
51
|
+
names = [rec.name] + rec.more_names
|
52
|
+
|
53
|
+
names.each do |name|
|
54
|
+
key = normalize_key( name )
|
55
|
+
|
56
|
+
if h[ key ]
|
57
|
+
puts "!!! ERROR - attribute name is not unique:"
|
58
|
+
pp rec
|
59
|
+
puts "duplicate:"
|
60
|
+
pp h[key]
|
61
|
+
exit 1
|
62
|
+
end
|
63
|
+
h[ key ] = rec
|
64
|
+
end
|
65
|
+
end
|
66
|
+
## pp h
|
67
|
+
h
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def build_recs( recs ) ## build and normalize (meta data) records
|
72
|
+
## sort by id
|
73
|
+
recs = recs.sort do |l,r|
|
74
|
+
l['id'].to_i( 10 ) <=> r['id'].to_i( 10 ) # use base10 (decimal)
|
75
|
+
end
|
76
|
+
|
77
|
+
## assert all recs are in order by id (0 to size)
|
78
|
+
recs.each_with_index do |rec, exp_id|
|
79
|
+
id = rec['id'].to_i(10)
|
80
|
+
if id != exp_id
|
81
|
+
puts "!! ERROR - meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
|
82
|
+
exit 1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
## convert to "wrapped / immutable" kind-of struct
|
87
|
+
recs = recs.map do |rec|
|
88
|
+
id = rec['id'].to_i( 10 )
|
89
|
+
name = normalize_name( rec['name'] )
|
90
|
+
type = rec['type']
|
91
|
+
|
92
|
+
more_names = (rec['more_names'] || '').split( '|' )
|
93
|
+
more_names = more_names.map {|str| normalize_name( str ) }
|
94
|
+
|
95
|
+
Metadata::Sprite.new(
|
96
|
+
id: id,
|
97
|
+
name: name,
|
98
|
+
type: type,
|
99
|
+
more_names: more_names )
|
100
|
+
end
|
101
|
+
recs
|
102
|
+
end # method build_recs
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
def initialize( image_path="./spritesheet.png",
|
107
|
+
meta_path="./spritesheet.csv",
|
108
|
+
width: 24,
|
109
|
+
height: 24 )
|
110
|
+
@width = width
|
111
|
+
@height = height
|
112
|
+
|
113
|
+
@sheet = ImageComposite.read( image_path, width: @width, height: @height )
|
114
|
+
recs = CsvHash.read( meta_path )
|
115
|
+
|
116
|
+
@recs = build_recs( recs )
|
117
|
+
|
118
|
+
## lookup by "case/space-insensitive" name / key
|
119
|
+
@attributes_by_name = build_attributes_by_name( @recs )
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def spritesheet() @sheet; end
|
124
|
+
alias_method :sheet, :spritesheet
|
125
|
+
|
126
|
+
|
127
|
+
def records() @recs; end
|
128
|
+
alias_method :meta, :records
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
def find_meta( q )
|
134
|
+
key = normalize_key( q ) ## normalize q(uery) string/symbol
|
135
|
+
|
136
|
+
rec = @attributes_by_name[ key ]
|
137
|
+
if rec
|
138
|
+
puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type}"
|
139
|
+
else
|
140
|
+
puts "!! WARN - no lookup found for key >#{key}<"
|
141
|
+
end
|
142
|
+
|
143
|
+
rec
|
144
|
+
end
|
145
|
+
|
146
|
+
def find( q )
|
147
|
+
rec = find_meta( q )
|
148
|
+
|
149
|
+
## return image if record found
|
150
|
+
rec ? @sheet[ rec.id ] : nil
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def to_recs( *values )
|
155
|
+
recs = []
|
156
|
+
|
157
|
+
attribute_names = values
|
158
|
+
|
159
|
+
attribute_names.each do |attribute_name|
|
160
|
+
attribute = find_meta( attribute_name )
|
161
|
+
if attribute.nil?
|
162
|
+
puts "!! ERROR - attribute >#{attribute_name}< not found; sorry"
|
163
|
+
exit 1
|
164
|
+
end
|
165
|
+
recs << attribute
|
166
|
+
end
|
167
|
+
|
168
|
+
recs
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
|
174
|
+
def generate_image( *values, background: nil )
|
175
|
+
|
176
|
+
ids = if values[0].is_a?( Integer ) ## assume integer number (indexes)
|
177
|
+
values
|
178
|
+
else ## assume strings (names)
|
179
|
+
to_recs( *values ).map { |rec| rec.id }
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
img = Image.new( @width, @height )
|
184
|
+
|
185
|
+
if background ## for now assume background is (simply) color
|
186
|
+
img.compose!( Image.new( @width, @height, background ) )
|
187
|
+
end
|
188
|
+
|
189
|
+
ids.each do |id|
|
190
|
+
img.compose!( @sheet[ id ] )
|
191
|
+
end
|
192
|
+
|
193
|
+
img
|
194
|
+
end
|
195
|
+
alias_method :generate, :generate_image
|
196
|
+
|
197
|
+
end # class Generator
|
198
|
+
|
199
|
+
end # module Pixelart
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module Pixelart
|
2
|
+
|
3
|
+
class Image
|
4
|
+
|
5
|
+
def self.calc_sample_steps( width, new_width,
|
6
|
+
center: true,
|
7
|
+
debug: false )
|
8
|
+
## todo/fix: assert new_width is smaller than width
|
9
|
+
if debug
|
10
|
+
puts
|
11
|
+
puts "==> from: #{width}px to: #{new_width}px"
|
12
|
+
end
|
13
|
+
|
14
|
+
indexes = []
|
15
|
+
|
16
|
+
base_step = width / new_width ## pixels per pixel
|
17
|
+
|
18
|
+
err_step = (width % new_width) * 2 ## multiply by 2
|
19
|
+
denominator = new_width * 2 # denominator (in de - nenner e.g. 1/nenner 4/nenner)
|
20
|
+
|
21
|
+
overflow = err_step*new_width/denominator ## todo/check - assert that div is always WITHOUT remainder!!!!!
|
22
|
+
|
23
|
+
if debug
|
24
|
+
puts
|
25
|
+
puts "base_step (pixels per pixel):"
|
26
|
+
puts " #{base_step} - #{base_step} * #{new_width}px = #{base_step*new_width}px"
|
27
|
+
puts "err_step (in 1/#{width}*2):"
|
28
|
+
puts " #{err_step} / #{denominator} - #{err_step*new_width} / #{denominator} = +#{err_step*new_width/denominator}px overflow"
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
|
32
|
+
# initial pixel offset
|
33
|
+
index = 0
|
34
|
+
err = err_step/2 ## note: start off with +err_step/2 to add overflow pixel in the "middle"
|
35
|
+
|
36
|
+
|
37
|
+
index += if center.is_a?( Integer )
|
38
|
+
center
|
39
|
+
elsif center
|
40
|
+
base_step/2
|
41
|
+
else
|
42
|
+
0 # use 0px offset
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
new_width.times do |i|
|
47
|
+
if err >= denominator ## overflow
|
48
|
+
puts " -- overflow #{err}/#{denominator} - add +1 pixel offset to #{i}" if debug
|
49
|
+
index += 1
|
50
|
+
err -= denominator
|
51
|
+
end
|
52
|
+
|
53
|
+
puts " #{i} => #{index} -- #{err} / #{denominator}" if debug
|
54
|
+
|
55
|
+
|
56
|
+
indexes[i] = index
|
57
|
+
|
58
|
+
index += base_step
|
59
|
+
err += err_step
|
60
|
+
end
|
61
|
+
|
62
|
+
indexes
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
## todo/check: rename to sample to resample or downsample - why? why not?
|
68
|
+
def sample( steps_x, steps_y=steps_x,
|
69
|
+
top_x: 0, top_y: 0 )
|
70
|
+
width = steps_x.size
|
71
|
+
height = steps_y.size
|
72
|
+
puts " downsampling from #{self.width}x#{self.height} to #{width}x#{height}..."
|
73
|
+
|
74
|
+
dest = Image.new( width, height )
|
75
|
+
|
76
|
+
steps_x.each_with_index do |step_x, x|
|
77
|
+
steps_y.each_with_index do |step_y, y|
|
78
|
+
pixel = self[top_x+step_x, top_y+step_y]
|
79
|
+
|
80
|
+
dest[x,y] = pixel
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
dest
|
85
|
+
end
|
86
|
+
alias_method :pixelate, :sample
|
87
|
+
|
88
|
+
|
89
|
+
def sample_debug( steps_x, steps_y=steps_x,
|
90
|
+
color: Color.parse( '#ffff00' ),
|
91
|
+
top_x: 0,
|
92
|
+
top_y: 0) ## add a yellow pixel
|
93
|
+
|
94
|
+
## todo/fix: get a clone of the image (DO NOT modify in place)
|
95
|
+
|
96
|
+
img = self
|
97
|
+
|
98
|
+
steps_x.each_with_index do |step_x, x|
|
99
|
+
steps_y.each_with_index do |step_y, y|
|
100
|
+
base_x = top_x+step_x
|
101
|
+
base_y = top_y+step_y
|
102
|
+
|
103
|
+
img[base_x,base_y] = color
|
104
|
+
|
105
|
+
## add more colors
|
106
|
+
img[base_x+1,base_y] = color
|
107
|
+
img[base_x+2,base_y] = color
|
108
|
+
|
109
|
+
img[base_x,base_y+1] = color
|
110
|
+
img[base_x,base_y+2] = color
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
self
|
115
|
+
end
|
116
|
+
alias_method :pixelate_debug, :sample_debug
|
117
|
+
|
118
|
+
end # class Image
|
119
|
+
end # module Pixelart
|
120
|
+
|
data/lib/pixelart/version.rb
CHANGED
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: 1.3.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-05-
|
11
|
+
date: 2022-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: chunky_png
|
@@ -106,12 +106,14 @@ files:
|
|
106
106
|
- lib/pixelart/circle.rb
|
107
107
|
- lib/pixelart/color.rb
|
108
108
|
- lib/pixelart/composite.rb
|
109
|
+
- lib/pixelart/generator.rb
|
109
110
|
- lib/pixelart/gradient.rb
|
110
111
|
- lib/pixelart/image.rb
|
111
112
|
- lib/pixelart/led.rb
|
112
113
|
- lib/pixelart/misc.rb
|
113
114
|
- lib/pixelart/palette.rb
|
114
115
|
- lib/pixelart/pixelator.rb
|
116
|
+
- lib/pixelart/sample.rb
|
115
117
|
- lib/pixelart/silhouette.rb
|
116
118
|
- lib/pixelart/sketch.rb
|
117
119
|
- lib/pixelart/spots.rb
|