mooncats 0.1.2 → 1.1.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.
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