artbase-cocos 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,347 @@
1
+
2
+ class TokenCollection < Artbase::Base
3
+
4
+
5
+ def self.read( path )
6
+ puts "==> reading collection config >#{path}<..."
7
+ config = read_yaml( path )
8
+
9
+ ## note:
10
+ ## allow sources to be empty - use [] quick hack for now - why? why not?
11
+ ## allow count to be empty - use 999 quick hack for now - why? why not?
12
+ ##
13
+
14
+ new(
15
+ config['slug'],
16
+ config['count'] || 999,
17
+ token_base: config['token_base'],
18
+ image_base: config['image_base'],
19
+ format: config['format'],
20
+ source: config['source'] || [],
21
+ offset: config['offset'] || 0
22
+ )
23
+ end # method self.read
24
+
25
+
26
+
27
+
28
+ attr_reader :slug, :count
29
+
30
+ def initialize( slug, count,
31
+ token_base:,
32
+ image_base: nil,
33
+ image_base_id_format: nil,
34
+ format:,
35
+ source:,
36
+ top_x: 0,
37
+ top_y: 0,
38
+ center_x: true,
39
+ center_y: true,
40
+ excludes: [],
41
+ offset: 0 ) # check: rename count to items or such - why? why not?
42
+ @slug = slug
43
+ @count = count
44
+ @offset = offset ## starting by default at 0 (NOT 1 or such)
45
+
46
+ @token_base = token_base
47
+ @image_base = image_base
48
+ @image_base_id_format = image_base_id_format
49
+
50
+ @width, @height = _parse_dimension( format )
51
+
52
+
53
+ ## note: allow multiple source formats / dimensions
54
+ ### e.g. convert 512x512 into [ [512,512] ]
55
+ ##
56
+ source = [source] unless source.is_a?( Array )
57
+ @sources = source.map { |dimension| _parse_dimension( dimension ) }
58
+
59
+ @top_x = top_x ## more (down)sampling / pixelate options
60
+ @top_y = top_y
61
+ @center_x = center_x
62
+ @center_y = center_y
63
+
64
+ @excludes = excludes
65
+ end
66
+
67
+
68
+ ## e.g. convert dimension (width x height) "24x24" or "24 x 24" to [24,24]
69
+ def _parse_dimension( str )
70
+ str.split( /x/i ).map { |str| str.strip.to_i }
71
+ end
72
+
73
+
74
+ def _range( offset: 0 ) ## return "default" range - make "private" helper public - why? why not?
75
+ ## note: range uses three dots (...) exclusive (NOT inclusive) range
76
+ ## e.g. 0...100 => [0,..,99]
77
+ ## 1...101 => [1,..,100]
78
+ ##
79
+ ## note: allow offset argument
80
+ ## (to start with different offset - note: in addition to builtin 0/1 offset)
81
+
82
+ (0+@offset+offset...@count+@offset)
83
+ end
84
+
85
+
86
+
87
+ def each_image( range=_range,
88
+ exclude: true, &blk )
89
+ range.each do |id|
90
+ ####
91
+ # filter out/skip
92
+ # if exclude && @excludes.include?( id )
93
+ # puts " skipping / exclude #{id}..."
94
+ # next
95
+ # end
96
+
97
+ puts "==> #{id}"
98
+ img = Image.read( "./#{@slug}/#{@width}x#{@height}/#{id}.png" )
99
+ blk.call( img, id )
100
+ end
101
+ end
102
+
103
+
104
+ def each_meta( range=_range,
105
+ exclude: true, &blk )
106
+ range.each do |id| ## check: change/rename id to index - why? why not?
107
+ meta = Meta.read( "./#{@slug}/token/#{id}.json" )
108
+
109
+ ####
110
+ # filter out/skip
111
+ # if exclude && @excludes.include?( meta.name )
112
+ # puts " skipping / exclude #{id} >#{meta.name}<..."
113
+ # next
114
+ # end
115
+
116
+ blk.call( meta, id )
117
+ end
118
+ end
119
+
120
+
121
+
122
+
123
+
124
+ def pixelate( range=_range, exclude: true,
125
+ force: false,
126
+ debug: false,
127
+ zoom: nil,
128
+ faster: false )
129
+
130
+ range.each do |id|
131
+
132
+ if exclude && @excludes.include?( id )
133
+ puts " skipping #{id}; listed in excludes #{@excludes.inspect}"
134
+ next
135
+ end
136
+
137
+ outpath = "./#{@slug}/#{@width}x#{@height}/#{id}.png"
138
+ if !force && File.exist?( outpath )
139
+ next ## note: skip if file already exists
140
+ end
141
+
142
+ center_x = if @center_x.is_a?( Proc ) then @center_x.call( id ); else @center_x; end
143
+ center_y = if @center_y.is_a?( Proc ) then @center_y.call( id ); else @center_y; end
144
+
145
+
146
+
147
+ puts "==> #{id} - reading / decoding #{id} ..."
148
+
149
+
150
+ if faster
151
+ ## note: faster for now only supports
152
+ ## single /one source format
153
+ ## always will use first source format from array for now
154
+ cmd = "./pixelator "
155
+ cmd << "./#{@slug}/token-i/#{id}.png"
156
+ cmd << " " + @sources[0][0].to_s
157
+ cmd << " " + @sources[0][1].to_s
158
+ cmd << " " + outpath
159
+ cmd << " " + @width.to_s
160
+ cmd << " " + @height.to_s
161
+ puts "==> #{cmd}..."
162
+ ret = system( cmd )
163
+ if ret
164
+ puts "OK"
165
+ else
166
+ puts "!! FAIL"
167
+ if ret.nil?
168
+ puts " command not found"
169
+ else
170
+ puts " exit code: #{$?}"
171
+ end
172
+ end
173
+ else
174
+ start = Time.now
175
+
176
+ img = Image.read( "./#{@slug}/token-i/#{id}.png" )
177
+
178
+ stop = Time.now
179
+ diff = stop - start
180
+
181
+ puts " in #{diff} sec(s)\n"
182
+
183
+
184
+ source = nil
185
+ @sources.each do |source_width, source_height|
186
+ if img.width == source_width && img.height == source_height
187
+ source = [source_width, source_height]
188
+ break
189
+ end
190
+ end
191
+
192
+
193
+ if source
194
+ source_width = source[0]
195
+ source_height = source[1]
196
+
197
+ steps_x = Image.calc_sample_steps( source_width-@top_x, @width, center: center_x )
198
+ steps_y = Image.calc_sample_steps( source_height-@top_y, @height, center: center_y )
199
+
200
+ pix = if debug
201
+ img.pixelate_debug( steps_x, steps_y,
202
+ top_x: @top_x,
203
+ top_y: @top_y )
204
+ else
205
+ img.pixelate( steps_x, steps_y,
206
+ top_x: @top_x,
207
+ top_y: @top_y )
208
+ end
209
+ ## todo/check: keep usingu slug e.g. 0001.png or "plain" 1.png - why? why not?
210
+ ## slug = "%04d" % id
211
+ pix.save( outpath )
212
+
213
+ if zoom
214
+ outpath = "./#{@slug}/#{@width}x#{@height}/#{id}@#{zoom}x.png"
215
+ pix.zoom( zoom ).save( outpath )
216
+ end
217
+ else
218
+ puts "!! ERROR - unknown/unsupported dimension - #{img.width}x#{img.height}; sorry - tried:"
219
+ pp @sources
220
+ exit 1
221
+ end
222
+ end
223
+ end
224
+ end
225
+
226
+
227
+
228
+ def meta_url( id: )
229
+ src = @token_base.gsub( '{id}', id.to_s )
230
+
231
+ ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
232
+ src = handle_ipfs( src )
233
+ src
234
+ end
235
+ alias_method :token_url, :meta_url
236
+
237
+
238
+ def image_url( id:,
239
+ direct: @image_base ? true : false )
240
+ src = if direct && @image_base
241
+ ###
242
+ ## todo/fix:
243
+ ## change image_base_id_format
244
+ ## to image_base proc with para id and call proc!!!!
245
+ if @image_base_id_format
246
+ @image_base.gsub( '{id}', @image_base_id_format % id )
247
+ else
248
+ @image_base.gsub( '{id}', id.to_s )
249
+ end
250
+ else
251
+ ## todo/check - change/rename data to meta - why? why not?
252
+ data = Meta.read( "./#{@slug}/token/#{id}.json" )
253
+
254
+ meta_name = data.name
255
+ meta_image = data.image
256
+
257
+ puts "==> #{id} - #{@slug}..."
258
+ puts " name: #{meta_name}"
259
+ puts " image: #{meta_image}"
260
+ meta_image
261
+ end
262
+ src
263
+
264
+ ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
265
+ src = handle_ipfs( src )
266
+ src
267
+ end
268
+
269
+
270
+
271
+ def download_meta( range=_range, force: false )
272
+ start = Time.now
273
+ delay_in_s = 0.3
274
+
275
+ range.each do |id|
276
+ outpath = "./#{@slug}/token/#{id}.json"
277
+ if !force && File.exist?( outpath )
278
+ next ## note: skip if file already exists
279
+ end
280
+
281
+ dirname = File.dirname( outpath )
282
+ FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
283
+
284
+ puts "==> #{id} - #{@slug}..."
285
+
286
+ token_src = meta_url( id: id )
287
+ copy_json( token_src, outpath )
288
+
289
+ stop = Time.now
290
+ diff = stop - start
291
+ puts " download token metadata in #{diff} sec(s)"
292
+
293
+ mins = diff / 60 ## todo - use floor or such?
294
+ secs = diff % 60
295
+ puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
296
+
297
+ puts "sleeping #{delay_in_s}s..."
298
+ sleep( delay_in_s )
299
+ end
300
+ end
301
+
302
+
303
+ ## note: default to direct true if image_base present/availabe
304
+ ## otherwise to false
305
+ ## todo/check: change/rename force para to overwrite - why? why not?
306
+ def download_images( range=_range, force: false,
307
+ direct: @image_base ? true : false )
308
+ start = Time.now
309
+ delay_in_s = 0.3
310
+
311
+ range.each do |id|
312
+
313
+ ## note: skip if (downloaded) file already exists
314
+ skip = false
315
+ if !force
316
+ ['png', 'gif', 'jgp', 'svg'].each do |format|
317
+ if File.exist?( "./#{@slug}/token-i/#{id}.#{format}" )
318
+ skip = true
319
+ break
320
+ end
321
+ end
322
+ end
323
+ next if skip
324
+
325
+ image_src = image_url( id: id, direct: direct )
326
+
327
+ ## note: will auto-add format file extension (e.g. .png, .jpg)
328
+ ## depending on http content type!!!!!
329
+ start_copy = Time.now
330
+ copy_image( image_src, "./#{@slug}/token-i/#{id}" )
331
+
332
+ stop = Time.now
333
+
334
+ diff = stop - start_copy
335
+ puts " download image in #{diff} sec(s)"
336
+
337
+ diff = stop - start
338
+ mins = diff / 60 ## todo - use floor or such?
339
+ secs = diff % 60
340
+ puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
341
+
342
+ puts "sleeping #{delay_in_s}s..."
343
+ sleep( delay_in_s )
344
+ end
345
+ end
346
+
347
+ end # class TokenCollection
@@ -0,0 +1,77 @@
1
+
2
+ class TokenCollection
3
+
4
+ #############
5
+ # (nested) Meta classes
6
+ # read meta data into struct
7
+ class Meta
8
+ def self.read( path )
9
+ new( read_json( path ))
10
+ end
11
+
12
+
13
+ def initialize( data )
14
+ @data = data
15
+ end
16
+
17
+
18
+ def name
19
+ ## note: name might be an integer number e.g. 0/1/2 etc.
20
+ ## e.g. see crypto pudgy punks and others?
21
+ ## always auto-convert to string
22
+ @name ||= _normalize( @data['name'].to_s )
23
+ end
24
+
25
+
26
+ def description
27
+ @description ||= _normalize( @data['description'] )
28
+ end
29
+
30
+ ## note: auto-convert "" (empty string) to nil
31
+ def image() _blank( @data['image'] ); end
32
+ alias_method :image_url, :image ## add image_url alias - why? why not?
33
+
34
+
35
+ def attributes
36
+ @attributes ||= begin
37
+ traits = []
38
+ ## keep traits as (simple)
39
+ ## ordered array of pairs for now
40
+ ##
41
+ ## in a step two make lookup via hash table
42
+ ## or such easier / "automagic"
43
+
44
+ @data[ 'attributes' ].each do |t|
45
+ trait_type = t['trait_type'].strip
46
+ trait_value = t['value'].strip
47
+ traits << [trait_type, trait_value]
48
+ end
49
+
50
+ traits
51
+ end
52
+ end
53
+ alias_method :traits, :attributes ## keep traits alias - why? why not?
54
+
55
+ ### "private" convenience / helper methods
56
+ def _normalize( str )
57
+ return if str.nil? ## check: check for nil - why? why not?
58
+
59
+ ## normalize string
60
+ ## remove leading and trailing spaces
61
+ ## collapse two and more spaces into one
62
+ ## change unicode space to ascii
63
+ str = str.gsub( "\u{00a0}", ' ' )
64
+ str = str.strip.gsub( /[ ]{2,}/, ' ' )
65
+ str
66
+ end
67
+
68
+ def _blank( o ) ## auto-convert "" (empty string) into nil
69
+ if o && o.strip.empty?
70
+ nil
71
+ else
72
+ o
73
+ end
74
+ end
75
+ end # (nested) class Meta
76
+
77
+ end # class TokenCollection
@@ -0,0 +1,205 @@
1
+
2
+
3
+
4
+ def slugify( name )
5
+ name.downcase.gsub( /[^a-z0-9 ()$_-]/ ) do |_|
6
+ puts " !! WARN: asciify - found (and removing) non-ascii char >#{Regexp.last_match}<"
7
+ '' ## remove - use empty string
8
+ end.gsub( ' ', '_')
9
+ end
10
+
11
+
12
+
13
+ =begin
14
+ moved/ use Image.convert !!! remove here
15
+ def convert_images( collection, from: 'jpg',
16
+ to: 'png',
17
+ dir: 'i',
18
+ overwrite: true )
19
+ files = Dir.glob( "./#{collection}/#{dir}/*.#{from}" )
20
+ puts "==> converting #{files.size} image(s) from #{from} to #{to}"
21
+
22
+ files.each_with_index do |file,i|
23
+ dirname = File.dirname( file )
24
+ extname = File.extname( file )
25
+ basename = File.basename( file, extname )
26
+
27
+ ## skip convert if target / dest file already exists
28
+ next if overwrite == false && File.exist?( "#{dirname}/#{basename}.#{to}" )
29
+
30
+
31
+ cmd = "magick convert #{dirname}/#{basename}.#{from} #{dirname}/#{basename}.#{to}"
32
+
33
+ puts " [#{i+1}/#{files.size}] - #{cmd}"
34
+ system( cmd )
35
+
36
+ if from == 'gif'
37
+ ## assume multi-images for gif
38
+ ## save image-0.png to image.png
39
+ path0 = "#{dirname}/#{basename}-0.#{to}"
40
+ path = "#{dirname}/#{basename}.#{to}"
41
+
42
+ puts " saving #{path0} to #{path}..."
43
+
44
+ blob = File.open( path0, 'rb' ) { |f| f.read }
45
+ File.open( path, 'wb' ) { |f| f.write( blob ) }
46
+ end
47
+ end
48
+ end
49
+ =end
50
+
51
+
52
+
53
+
54
+
55
+
56
+
57
+ def copy_json( src, dest )
58
+ uri = URI.parse( src )
59
+
60
+
61
+
62
+ headers = { 'User-Agent' => "ruby v#{RUBY_VERSION}" }
63
+
64
+ redirect_limit = 6
65
+ response = nil
66
+
67
+ until false
68
+ raise ArgumentError, 'HTTP redirect too deep' if redirect_limit == 0
69
+ redirect_limit -= 1
70
+
71
+ puts "[debug] GET #{uri.request_uri} uri=#{uri}"
72
+
73
+ http = Net::HTTP.new( uri.host, uri.port )
74
+
75
+ if uri.instance_of? URI::HTTPS
76
+ # puts "[debug] use SSL (HTTPS); set verify mode to none"
77
+ http.use_ssl = true
78
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
79
+ end
80
+
81
+ request = Net::HTTP::Get.new( uri.request_uri, headers )
82
+ response = http.request( request )
83
+
84
+
85
+ if response.code == '301' ||
86
+ response.code == '302' ||
87
+ response.code == '303' ||
88
+ response.code == '307'
89
+ # 301 = moved permanently
90
+ # 302 = found
91
+ # 303 = see other
92
+ # 307 = temporary redirect
93
+
94
+ puts "[debug] #{response.code} #{response.message}"
95
+ puts "[debug] location: #{response.header['location']}"
96
+
97
+ newuri = URI.parse( response.header['location'] )
98
+ if newuri.relative?
99
+ puts "[debug] url relative; try to make it absolute"
100
+ newuri = uri + response.header['location']
101
+ end
102
+
103
+ uri = newuri
104
+ puts "[debug] #{uri.class.name} new uri.class.name"
105
+ else
106
+ break
107
+ end
108
+ end
109
+
110
+
111
+ if response.code == '200'
112
+ puts "#{response.code} #{response.message}"
113
+ puts " content_type: #{response.content_type}, content_length: #{response.content_length}"
114
+
115
+ text = response.body.to_s
116
+ text = text.force_encoding( Encoding::UTF_8 )
117
+
118
+ data = JSON.parse( text )
119
+
120
+ File.open( dest, "w:utf-8" ) do |f|
121
+ f.write( JSON.pretty_generate( data ) )
122
+ end
123
+ else
124
+ puts "!! error:"
125
+ puts "#{response.code} #{response.message}"
126
+ exit 1
127
+ end
128
+ end
129
+
130
+
131
+ def copy_image( src, dest,
132
+ dump_headers: false )
133
+ uri = URI.parse( src )
134
+
135
+ http = Net::HTTP.new( uri.host, uri.port )
136
+
137
+ puts "[debug] GET #{uri.request_uri} uri=#{uri}"
138
+
139
+ headers = { 'User-Agent' => "ruby v#{RUBY_VERSION}" }
140
+
141
+ request = Net::HTTP::Get.new( uri.request_uri, headers )
142
+ if uri.instance_of? URI::HTTPS
143
+ http.use_ssl = true
144
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
145
+ end
146
+
147
+ response = http.request( request )
148
+
149
+ if response.code == '200'
150
+ puts "#{response.code} #{response.message}"
151
+
152
+ content_type = response.content_type
153
+ content_length = response.content_length
154
+ puts " content_type: #{content_type}, content_length: #{content_length}"
155
+
156
+ if dump_headers ## for debugging dump headers
157
+ headers = response.each_header.to_h
158
+ puts "htttp respone headers:"
159
+ pp headers
160
+ end
161
+
162
+
163
+ format = if content_type =~ %r{image/jpeg}i
164
+ 'jpg'
165
+ elsif content_type =~ %r{image/png}i
166
+ 'png'
167
+ elsif content_type =~ %r{image/gif}i
168
+ 'gif'
169
+ elsif content_type =~ %r{image/svg}i
170
+ 'svg'
171
+ else
172
+ puts "!! error:"
173
+ puts " unknown image format content type: >#{content_type}<"
174
+ exit 1
175
+ end
176
+
177
+ ## make sure path exits - autocreate dirs
178
+ ## make sure path exists
179
+ dirname = File.dirname( "#{dest}.#{format}" )
180
+ FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
181
+
182
+ if format == 'svg'
183
+ ## save as text (note: assume utf-8 encoding for now)
184
+ text = response.body.to_s
185
+ text = text.force_encoding( Encoding::UTF_8 )
186
+
187
+ File.open( "#{dest}.svg", 'w:utf-8' ) do |f|
188
+ f.write( text )
189
+ end
190
+ else
191
+ ## save as binary
192
+ File.open( "#{dest}.#{format}", 'wb' ) do |f|
193
+ f.write( response.body )
194
+ end
195
+ end
196
+ else
197
+ puts "!! error:"
198
+ puts "#{response.code} #{response.message}"
199
+ exit 1
200
+ end
201
+ end
202
+
203
+
204
+
205
+
@@ -0,0 +1,31 @@
1
+ module Pixelart
2
+
3
+ class Image
4
+
5
+ ###
6
+ # add common
7
+ # pixel(ate) steps/offsets (for re/down/sampling)
8
+ DOwNSAMPLING_STEPS = {
9
+ '24x24' => {
10
+ '269x269' => Image.calc_sample_steps( 269, 24 ), # width (269px), new_width (24px)
11
+ '512x512' => Image.calc_sample_steps( 512, 24 ), # width (512px), new_width (24px)
12
+ },
13
+ '32x32' => {
14
+ '320x320' => Image.calc_sample_steps( 320, 32 ),
15
+ '512x512' => Image.calc_sample_steps( 512, 32 ),
16
+ },
17
+ '35x35' => {
18
+ '512x512' => Image.calc_sample_steps( 512, 35 ),
19
+ },
20
+ '60x60' => {
21
+ '512x512' => Image.calc_sample_steps( 512, 60 ),
22
+ },
23
+ '80x80' => {
24
+ '512x512' => Image.calc_sample_steps( 512, 80 ),
25
+ },
26
+ }
27
+
28
+ end # class Image
29
+ end # module Pixelart
30
+
31
+
@@ -0,0 +1,30 @@
1
+ ######################
2
+ # pixelart image extensions
3
+ # move upstream!!!!!
4
+
5
+
6
+ module Pixelart
7
+ class ImageComposite
8
+ def add_glob( glob )
9
+ files = Dir.glob( glob )
10
+ puts "#{files.size} file(s) found matching >#{glob}<"
11
+
12
+
13
+ files = files.sort
14
+ ## puts files.inspect
15
+
16
+ files.each_with_index do |file,i|
17
+ puts "==> [#{i+1}/#{files.size}] - #{file}"
18
+ img = Image.read( file )
19
+
20
+ self << img ## todo/check: use add alias - why? why not?
21
+ end
22
+ end
23
+ end # class ImageComposite
24
+ end # module Pixelart
25
+
26
+
27
+
28
+
29
+
30
+