mooncats 0.1.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/config/v2/014.txt ADDED
@@ -0,0 +1,27 @@
1
+ ##############################
2
+ # design: 14 - Pouncing Smiling Tortie Left
3
+ # size: 17x22
4
+ ###########################
5
+
6
+ 0 0 0 1 0 0 0 0 0 1 1 0 0 0 0 0 0
7
+ 0 0 1 2 1 0 0 0 1 3 1 0 0 0 0 0 0
8
+ 0 0 1 4 2 1 1 1 3 4 1 0 0 0 0 0 0
9
+ 0 1 1 2 2 2 3 3 3 3 1 1 0 0 0 0 0
10
+ 0 1 2 2 2 2 2 3 3 3 3 1 0 0 0 0 0
11
+ 0 1 2 5 1 2 2 5 1 3 3 1 0 0 0 0 0
12
+ 0 1 2 2 2 2 2 2 3 3 3 1 0 0 0 0 0
13
+ 0 1 2 2 1 2 1 2 1 3 3 1 0 0 0 0 0
14
+ 0 1 2 2 2 1 2 1 3 3 3 1 0 0 0 0 0
15
+ 0 0 1 2 2 2 2 2 3 3 1 1 0 0 0 0 0
16
+ 1 1 3 1 1 1 1 1 1 1 3 1 1 1 0 0 0
17
+ 1 4 3 3 3 3 3 3 3 3 3 3 1 3 1 1 0
18
+ 1 1 1 3 3 1 1 1 2 3 3 3 1 1 3 1 0
19
+ 0 0 1 1 3 1 4 2 2 2 3 3 3 1 3 1 1
20
+ 0 0 0 1 3 3 1 1 2 2 3 3 3 1 1 3 1
21
+ 0 0 0 1 3 4 4 4 3 3 3 3 3 1 1 3 1
22
+ 0 0 0 1 1 4 4 4 3 1 1 1 2 1 2 2 1
23
+ 0 0 0 0 1 4 4 4 3 1 3 2 2 1 2 1 1
24
+ 0 0 0 0 1 2 4 4 3 3 3 2 2 2 1 1 0
25
+ 0 0 0 0 1 2 2 1 1 3 3 2 1 1 1 0 0
26
+ 0 0 0 0 1 1 4 1 1 1 1 2 1 0 0 0 0
27
+ 0 0 0 0 0 1 1 0 0 0 1 1 1 0 0 0 0
data/config/v2/015.txt ADDED
@@ -0,0 +1,29 @@
1
+ ##############################
2
+ # design: 15 - Stalking Smiling Tortie Left
3
+ # size: 20x21
4
+ ###########################
5
+
6
+ 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0
7
+ 0 0 0 0 0 0 0 0 0 0 1 3 3 3 3 1 1 1 0 0
8
+ 0 0 0 0 0 0 0 0 0 0 1 3 1 1 3 3 3 1 0 0
9
+ 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 2 1 0 0
10
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 1 1 0
11
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 2 1 0
12
+ 0 0 1 1 0 0 0 0 1 1 0 0 0 1 1 1 1 1 1 0
13
+ 0 1 2 1 0 0 0 1 3 1 0 0 1 1 2 2 2 2 1 1
14
+ 1 2 4 2 1 1 1 3 4 3 1 1 1 3 3 2 2 2 3 1
15
+ 1 2 2 2 2 3 3 3 3 3 1 1 3 3 3 3 3 3 3 1
16
+ 1 2 2 2 2 2 3 3 3 3 1 3 3 3 3 3 3 3 3 1
17
+ 1 2 5 1 2 2 5 1 3 3 1 3 3 3 3 3 3 3 1 1
18
+ 1 2 2 2 2 2 2 3 3 3 1 3 3 3 3 3 3 3 1 0
19
+ 1 2 2 1 2 1 2 1 3 3 1 2 2 2 3 4 3 3 1 0
20
+ 1 2 2 2 1 2 1 3 3 3 1 2 2 4 4 4 3 3 1 0
21
+ 0 1 2 2 2 2 2 3 3 1 1 2 4 4 4 1 3 3 3 1
22
+ 0 0 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 2 3 1
23
+ 0 1 1 3 3 3 2 2 2 2 1 1 1 3 3 1 1 2 1 1
24
+ 1 1 3 3 3 1 2 2 1 1 1 1 4 3 1 1 2 2 1 0
25
+ 1 4 3 1 1 1 4 1 1 0 0 1 1 1 1 2 2 1 1 0
26
+ 1 1 1 1 0 1 1 1 0 0 0 0 0 0 1 1 1 1 0 0
27
+
28
+
29
+
data/lib/mooncats.rb CHANGED
@@ -1,10 +1,9 @@
1
1
  ## 3rd party
2
- require 'chunky_png'
2
+ require 'pixelart/base'
3
3
  require 'csvreader'
4
4
 
5
5
 
6
6
  ## extra stdlibs
7
- require 'fileutils'
8
7
  require 'optparse'
9
8
 
10
9
 
@@ -13,6 +12,7 @@ require 'optparse'
13
12
  require 'mooncats/version' # note: let version always go first
14
13
  require 'mooncats/designs'
15
14
  require 'mooncats/structs'
15
+ require 'mooncats/design'
16
16
  require 'mooncats/image'
17
17
  require 'mooncats/composite'
18
18
  require 'mooncats/dataset'
@@ -63,15 +63,20 @@ module Mooncats
63
63
  ## note: cut-off optionial 0x
64
64
  cat_id = cat_id[2..-1] if cat_id.start_with?( '0x')
65
65
 
66
+ cat = Image.generate( cat_id )
67
+
66
68
  cat_name = "mooncat-#{cat_id}"
67
69
 
68
70
  ## if zoom - add x2,x4 or such
69
- cat_name << "_x#{opts[:zoom]}" if opts[:zoom] != 1
71
+ if opts[:zoom] != 1
72
+ cat = cat.zoom( opts[:zoom] )
73
+ cat_name << "_x#{opts[:zoom]}"
74
+ end
70
75
 
71
76
  path = "#{opts[:outdir]}/#{cat_name}.png"
72
77
  puts "==> (#{index+1}/#{args.size}) minting mooncat 0x#{cat_id}; writing to >#{path}<..."
73
78
 
74
- Image.generate( cat_id, zoom: opts[:zoom] ).save( path )
79
+ cat.save( path )
75
80
  end
76
81
 
77
82
  puts "done"
@@ -82,11 +87,24 @@ module Mooncats
82
87
  def self.main( args=ARGV )
83
88
  Tool.new.run( args )
84
89
  end
85
- end ## module Mooncats
90
+ end ## module Mooncats
91
+
92
+
93
+
94
+ ### add more built-in (load on demand) design series / collections
95
+ DESIGNS_V2 = Mooncats::DesignSeries.new( "#{Mooncats.root}/config/v2" )
96
+ DESIGNS_CRYPTOCATS = Mooncats::DesignSeries.new( "#{Mooncats.root}/config/cryptocats" )
86
97
 
87
98
 
88
99
  ### add some convenience shortcuts
89
100
  MoonCats = Mooncats
90
101
 
91
102
 
103
+
104
+
105
+ ###
106
+ # note: for convenience auto include Pixelart namespace!!! - why? why not?
107
+ include Pixelart
108
+
109
+
92
110
  puts Mooncats.banner # say hello
@@ -10,7 +10,7 @@ CANVAS_HEIGHT = 24
10
10
  def initialize( cols=100, rows=255 )
11
11
  @composite = ChunkyPNG::Image.new( cols*CANVAS_WIDTH,
12
12
  rows*CANVAS_HEIGHT,
13
- ChunkyPNG::Color::WHITE ) # why? why not? - use TRANSPARENT (is default?)
13
+ ChunkyPNG::Color::TRANSPARENT ) # why? why not? - use TRANSPARENT (is default?)
14
14
 
15
15
  ## todo/check - find a better name for cols/rows - why? why not?
16
16
  @cols = cols
@@ -31,9 +31,17 @@ module Mooncats
31
31
  ## note: skip all derived column from id e.g.
32
32
  ## - r,g,b, etc.
33
33
 
34
- mint = row['mint'].to_i
35
-
36
- mooncats << Metadata.new( id )
34
+ ## add some more (extra) columns
35
+ mint = row['mint'].to_i
36
+ block = row['block'].to_i
37
+ ## expected timestamp format like
38
+ ## 2017-09-06 15:03:43 UTC
39
+ timestamp = DateTime.strptime( row['timestamp'], '%Y-%m-%d %H:%M:%S %z')
40
+
41
+ mooncats << Metadata.new( id,
42
+ mint: mint,
43
+ block: block,
44
+ timestamp: timestamp )
37
45
  end
38
46
  ## print "\n" ## add progress
39
47
 
@@ -0,0 +1,127 @@
1
+
2
+ module Mooncats
3
+
4
+
5
+ class Design
6
+ def self.find( num ) ## pass in design index number (0 to 127)
7
+ ## todo: add cache (memoize) - why? why not?
8
+ design = parse( DESIGNS[ num ] )
9
+
10
+ puts " design ##{num} (#{design.width}x#{design.height})"
11
+ ## pp design.data
12
+ ## puts
13
+
14
+ design
15
+ end
16
+
17
+
18
+ def self.read( path )
19
+ text = File.open( path, 'r:utf-8') { |f| f.read }
20
+ parse( text )
21
+ end
22
+
23
+ def self.parse( str )
24
+ ## support original "classic" compact single-line format
25
+ ## e.g. 00011111100000000.01113333310000000.13533333331110000....
26
+ ## note: this format needs to get rotated by 90 degree!!!
27
+ if str.start_with?( /[0-5]+\.[0-5]+\.[0-5]+/ ) ## quick (and dirty) heuristic check
28
+ data = str.split('.')
29
+
30
+ ## note: map colors encoded as a string to an array of integers - why? why not?
31
+ ## e.g. "00011111133344411"
32
+ ## =>
33
+ ## [0,0,0,1,1,1,1,1,1,3,3,3,4,4,4,1,1]
34
+ data = data.map do |row|
35
+ row.chars.map do |color|
36
+ color.to_i
37
+ end
38
+ end
39
+ data = data.transpose ## note: rotate by 90 degree!!!!!
40
+ else ## assume "modern" pixelart format
41
+ ## todo/check: delegate to pixelart parse or such - why? why not?
42
+
43
+ data = []
44
+ str.each_line do |line|
45
+ line = line.strip
46
+ next if line.empty? ## skipping empty line in pixel art source
47
+ next if line.start_with?( '#' ) ## skipping comment line in pixel art source
48
+
49
+ ## note: allow multiple spaces or tabs to separate pixel codes
50
+ data << line.split( /[ \t]+/)
51
+ end
52
+ ## todo/check: change to use strings (instead of nummbers) in the future?
53
+ ## why? why not? stay "compatible" to pixel art format/machinery?
54
+ data = data.map do |row|
55
+ row.map do |color|
56
+ color.to_i
57
+ end
58
+ end
59
+ end
60
+ new( data )
61
+ end
62
+
63
+
64
+ def initialize( data )
65
+ @data = data
66
+ end
67
+
68
+ def width
69
+ ## todo/check: use/find max - why? why not? lets you you used "unbalanced" / shortcut lines too
70
+ @data[0].size
71
+ end
72
+ def height() @data.size; end
73
+
74
+
75
+ def each_with_index( &blk )
76
+ @data.each_with_index { |row, y| blk.call( row, y ) }
77
+ end
78
+ def each( &blk )
79
+ @data.each { |row| blk.call( row ) }
80
+ end
81
+
82
+
83
+
84
+ def to_txt
85
+ buf = String.new('')
86
+
87
+ @data.each do |row|
88
+ buf << row.join( ' ' )
89
+ buf << "\n"
90
+ end
91
+ buf
92
+ end
93
+ end # class Design
94
+
95
+
96
+
97
+ ###############
98
+ ## todo/check:
99
+ ## find a better way to (auto?) include more designs?
100
+ class DesignSeries ## find a better name for class - why? why not?
101
+ def self.build( dir )
102
+ data = {}
103
+ paths = Dir.glob( "#{dir}/**.txt" )
104
+ paths.each do |path|
105
+ basename = File.basename( path, File.extname( path ) )
106
+ num = basename.to_i( 10 ) ## use base 10 (e.g. 001 => 1, 002 => 2, etc.)
107
+ text = File.open( path, 'r:utf-8' ) { |f| f.read }
108
+ ## todo/check: auto-parse "ahead of time" here
109
+ ## or keep "raw" text - why? why not?
110
+ data[ num ] = text
111
+ end
112
+ data
113
+ end
114
+
115
+ def initialize( dir )
116
+ @dir = dir # e.g. "#{Mooncats.root}/config/v2"
117
+ end
118
+
119
+ def data
120
+ ## note: lazy load / build on first demand only
121
+ @data ||= self.class.build( @dir )
122
+ end
123
+
124
+ def [](index) data[ index ]; end
125
+ def size() data.size; end
126
+ end # class DesignSeries
127
+ end # module Mooncats
@@ -1,13 +1,15 @@
1
1
 
2
2
  module Mooncats
3
- class Image
3
+
4
+
5
+ class Image < Pixelart::Image
4
6
 
5
7
 
6
8
  COLORS_GENESIS_WHITE = ['#555555', '#d3d3d3', '#ffffff', '#aaaaaa', '#ff9999']
7
9
  COLORS_GENESIS_BLACK = ['#555555', '#222222', '#111111', '#bbbbbb', '#ff9999']
8
10
 
9
11
 
10
- def self.generate( id, zoom: 1 )
12
+ def self.generate( id )
11
13
  meta = Metadata.new( id )
12
14
 
13
15
  design = meta.design.to_i # note: meta.design is a struct/object - keep/use a local int !!!
@@ -20,14 +22,14 @@ def self.generate( id, zoom: 1 )
20
22
  COLORS_GENESIS_BLACK
21
23
  end
22
24
  else
23
- derive_palette( meta.r,
24
- meta.g,
25
- meta.b, invert: meta.invert? )
25
+ derive_palette( r: meta.r,
26
+ g: meta.g,
27
+ b: meta.b,
28
+ invert: meta.invert? )
26
29
  end
27
30
 
28
31
  new( design: design,
29
- colors: colors,
30
- zoom: zoom )
32
+ colors: colors )
31
33
  end
32
34
 
33
35
  ### add more (convenience) aliases
@@ -36,80 +38,92 @@ class << self
36
38
  end
37
39
 
38
40
 
41
+ def self.read( path ) ## convenience helper
42
+ img = ChunkyPNG::Image.from_file( path )
43
+ new( img )
44
+ end
39
45
 
40
46
 
41
- def initialize( design: 0,
42
- colors: COLORS_GENESIS_WHITE,
43
- zoom: 1 )
44
-
45
- ## puts "==> [Mooncats] Image.new zoom: #{zoom}"
46
-
47
- design = if design.is_a?( String )
48
- Design.parse( design )
49
- elsif design.is_a?( Array )
50
- Design.new( design )
51
- elsif design.is_a?( Design )
52
- design ## pass through as is 1:1
53
- else ## assume integer nuber
54
- design_num = design ## note: for convenience "porcelain" param is named design (NOT design_num)
55
- Design.find( design_num )
56
- end
57
-
58
- ## note: first color (index 0) is always nil (default/white or transparent)
59
- colors = [ nil ] + parse_colors( colors )
60
-
61
- ## puts " colors:"
62
- ## pp colors
63
-
64
- @cat = ChunkyPNG::Image.new( design.width*zoom,
65
- design.height*zoom,
66
- ChunkyPNG::Color::WHITE ) # why? why not?
67
-
68
- design.each_with_index do |row, x|
69
- row.each_with_index do |color, y|
70
- if color > 0
71
- pixel = colors[ color ]
72
- zoom.times do |n|
73
- zoom.times do |m|
74
- @cat[n+zoom*x,m+zoom*y] = pixel
75
- end
76
- end
77
- end # has color?
78
- end # each row
79
- end # each data
80
- end
81
47
 
82
- #####
83
- # (image) delegates
84
- ## todo/check: add some more??
85
- def save( path, constraints = {} )
86
- @cat.save( path, constraints )
48
+ def initialize( initial=nil, design: nil,
49
+ colors: nil )
50
+ if initial
51
+ ## pass image through as-is
52
+ img = inital
53
+ else
54
+ design ||= 0
55
+ colors ||= COLORS_GENESIS_WHITE
56
+
57
+ design = if design.is_a?( String )
58
+ Design.parse( design )
59
+ elsif design.is_a?( Array )
60
+ Design.new( design )
61
+ elsif design.is_a?( Design )
62
+ design ## pass through as is 1:1
63
+ else ## assume integer nuber
64
+ design_num = design ## note: for convenience "porcelain" param is named design (NOT design_num)
65
+ Design.find( design_num )
66
+ end
67
+
68
+ ## note: first color (index 0) is always nil (default/white or transparent)
69
+ colors = [ nil ] + colors.map { |color| Pixelart::Color.parse( color ) }
70
+
71
+ ## puts " colors:"
72
+ ## pp colors
73
+
74
+ img = ChunkyPNG::Image.new( design.width,
75
+ design.height,
76
+ ChunkyPNG::Color::TRANSPARENT ) # why? why not?
77
+
78
+ design.each_with_index do |row, y|
79
+ row.each_with_index do |color, x|
80
+ if color > 0
81
+ pixel = colors[ color ]
82
+
83
+ ## note: special built-in color palette black & white "hack"
84
+ ## only active if colors 6 & 7 NOT defined
85
+ ## color 6 => black (000000 / ff) rgb / a(lpha)
86
+ ## color 7 => white (ffffff / ff) rgb / a(lpha)
87
+ pixel = 0xff if pixel.nil? && color == 6
88
+ pixel = 0xffffffff if pixel.nil? && color == 7
89
+
90
+ img[x,y] = pixel
91
+ end # has color?
92
+ end # each row
93
+ end # each data
94
+ end
95
+
96
+ super( img.width, img.height, img )
87
97
  end
88
98
 
89
- def width() @cat.width; end
90
- def height() @cat.height; end
91
-
92
- ## return image ref - use a different name - why? why not?
93
- def image() @cat; end
94
99
 
95
100
 
96
101
  ##################
97
102
  # (static) helpers
98
- def self.derive_palette( r, g, b, invert: false )
99
- ## note: Color.rgb returns an Integer (e.g. 34113279 - true color or just hex rgba or?)
100
- rgb = ChunkyPNG::Color.rgb( r, g, b )
101
-
102
- # to_hsl(color, include_alpha = false) ⇒ Array<Fixnum>[0], ...
103
- # Returns an array with the separate HSL components of a color.
104
- hsl = ChunkyPNG::Color.to_hsl( rgb )
105
- #=> [237, 0.9705882352941178, 0.26666666666666666]
106
-
107
- h = hsl[0]
108
- s = hsl[1]
109
- l = hsl[2]
110
-
111
- hx = h % 360
112
- hy = (h + 320) % 360
103
+ def self.derive_palette( r: nil, g: nil, b: nil,
104
+ hue: nil,
105
+ invert: false )
106
+
107
+ if hue
108
+ ## pass through as is 1:1
109
+ else ## assume r, g, b
110
+ ## note: Color.rgb returns an Integer (e.g. 34113279 - true color or just hex rgba or?)
111
+ rgb = ChunkyPNG::Color.rgb( r, g, b )
112
+
113
+ # to_hsl(color, include_alpha = false) ⇒ Array<Fixnum>[0], ...
114
+ # Returns an array with the separate HSL components of a color.
115
+ hsl = ChunkyPNG::Color.to_hsl( rgb )
116
+ #=> [237, 0.9705882352941178, 0.26666666666666666]
117
+
118
+ # h = hsl[0]
119
+ # s = hsl[1]
120
+ # l = hsl[2]
121
+
122
+ hue = hsl[0]
123
+ end
124
+
125
+ hx = hue % 360 ## note: makes sure number is always POSITIVE (e.g. -13 % 360 => 347)
126
+ hy = (hue + 320) % 360
113
127
  #=> e.g. hx: 237, hy: 197
114
128
 
115
129
  c1 = ChunkyPNG::Color.from_hsl( hx, 1, 0.1 )
@@ -131,75 +145,6 @@ def self.derive_palette( r, g, b, invert: false )
131
145
  end
132
146
 
133
147
 
134
- ######
135
- # helpers
136
- def parse_colors( colors )
137
- ## convert into ChunkyPNG::Color
138
- colors.map { |color| parse_color( color ) }
139
- end
140
-
141
- def parse_color( color )
142
- if color.is_a?( Integer ) ## e.g. Assumess ChunkyPNG::Color.rgb() or such
143
- color ## pass through as is 1:1
144
- elsif color.is_a?(String)
145
- ## note: return an Integer !!! (not a Color class or such!!! )
146
- ChunkyPNG::Color.from_hex( color )
147
- else
148
- raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
149
- end
150
- end
151
148
  end # class Image
152
-
153
-
154
-
155
- class Design
156
- def self.find( num ) ## pass in design index number (0 to 127)
157
- ## todo: add cache (memoize) - why? why not?
158
- str = DESIGNS[ num ]
159
- design = parse( str )
160
-
161
- puts " design ##{num} (#{design.width}x#{design.height})"
162
- ## pp design.data
163
- ## puts
164
-
165
- design
166
- end
167
-
168
-
169
- def self.parse( str )
170
- data = str.split('.')
171
- new( data )
172
- end
173
-
174
- def initialize( data )
175
- ## todo: add cache (memoize) - why? why not?
176
-
177
- ## note: map colors encoded as a string to an array of integers - why? why not?
178
- ## e.g. "00011111133344411"
179
- ## =>
180
- ## [0,0,0,1,1,1,1,1,1,3,3,3,4,4,4,1,1]
181
-
182
- @data = data.map do |row|
183
- row.chars.map do |color|
184
- color.to_i
185
- end
186
- end
187
- end
188
-
189
- ## note: design data stored mirrored (data.size is the width NOT height)
190
- def width() @data.size; end
191
- def height() @data[0].size; end
192
-
193
- def data() @data; end
194
-
195
- def each_with_index( &blk )
196
- ## note: y,x is reversed - keep for now
197
- ## (todo/fix later? and "pivot" raw data on init - why? why not?)
198
- @data.each_with_index do |row, x|
199
- blk.call( row, x )
200
- end
201
- end
202
- end # class Design
203
-
204
149
  end # module Mooncats
205
150