artbase-cocos 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/Manifest.txt +15 -0
- data/README.md +27 -0
- data/Rakefile +40 -0
- data/lib/artbase-cocos/attributes.rb +83 -0
- data/lib/artbase-cocos/collection/base.rb +332 -0
- data/lib/artbase-cocos/collection/image.rb +39 -0
- data/lib/artbase-cocos/collection/token.rb +347 -0
- data/lib/artbase-cocos/collection/token_meta.rb +77 -0
- data/lib/artbase-cocos/helper.rb +205 -0
- data/lib/artbase-cocos/image/sample.rb +31 -0
- data/lib/artbase-cocos/image.rb +30 -0
- data/lib/artbase-cocos/retry.rb +41 -0
- data/lib/artbase-cocos/version.rb +25 -0
- data/lib/artbase-cocos.rb +82 -0
- metadata +140 -0
@@ -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
|
+
|