artbase-cocos 0.0.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.
@@ -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
+