mooncats 0.1.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +4 -1
- data/README.md +19 -18
- data/Rakefile +1 -1
- data/lib/mooncats.rb +19 -5
- data/lib/mooncats/composite.rb +69 -0
- data/lib/mooncats/dataset.rb +53 -0
- data/lib/mooncats/design.rb +92 -0
- data/lib/mooncats/image.rb +54 -140
- data/lib/mooncats/structs.rb +242 -0
- data/lib/mooncats/version.rb +1 -1
- metadata +7 -5
- data/LICENSE.md +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bf22f607c257a0cef8877f44594782f1c6f704a9c580c01af96a39d49f985209
|
4
|
+
data.tar.gz: f6b10d2506c5b3dc05477ad4d8ba18c24eada0382cbcc29fdbfa0c572a04c892
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e20d5394cd29624a64d0b7a830fbf7bc0b0890c021a25c077a7c51771600b9d7187220abd2e76c5a94b2f1a50d66fae040727950fca4be859db8a821791082a
|
7
|
+
data.tar.gz: 6028eadc62a9861e3e9c5be6215ec0bb142701cffd15e88d681ce4c30cfc137df4ee102f9b09de0c41f9d1a4715771d631d488a1b49326a8fba96bf211615e08
|
data/Manifest.txt
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
CHANGELOG.md
|
2
|
-
LICENSE.md
|
3
2
|
Manifest.txt
|
4
3
|
README.md
|
5
4
|
Rakefile
|
6
5
|
bin/mooncat
|
7
6
|
lib/mooncats.rb
|
7
|
+
lib/mooncats/composite.rb
|
8
|
+
lib/mooncats/dataset.rb
|
9
|
+
lib/mooncats/design.rb
|
8
10
|
lib/mooncats/designs.rb
|
9
11
|
lib/mooncats/image.rb
|
12
|
+
lib/mooncats/structs.rb
|
10
13
|
lib/mooncats/version.rb
|
data/README.md
CHANGED
@@ -25,12 +25,12 @@ resulting in:
|
|
25
25
|
|
26
26
|
```
|
27
27
|
Usage: mooncat [options] IDs
|
28
|
-
|
28
|
+
Mint mooncats from original designs - for IDs use 5 byte hexstrings (e.g 0x004fc21270)
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
Options:
|
31
|
+
-z, --zoom=ZOOM Zoom factor x2, x4, x8, etc. (default: 1)
|
32
|
+
-d, --dir=DIR Output directory (default: .)
|
33
|
+
-h, --help Prints this help
|
34
34
|
```
|
35
35
|
|
36
36
|
|
@@ -55,9 +55,9 @@ printing:
|
|
55
55
|
|
56
56
|
And voila!
|
57
57
|
|
58
|
-
![](i/mooncat-00000800fa.png)
|
59
|
-
![](i/mooncat-0077c8278d.png)
|
60
|
-
![](i/mooncat-ff5f000ca7.png)
|
58
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-00000800fa.png)
|
59
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-0077c8278d.png)
|
60
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-ff5f000ca7.png)
|
61
61
|
|
62
62
|
|
63
63
|
|
@@ -86,23 +86,24 @@ printing:
|
|
86
86
|
|
87
87
|
And voila!
|
88
88
|
|
89
|
-
![](i/mooncat-00000800fa_x2.png)
|
90
|
-
![](i/mooncat-0077c8278d_x2.png)
|
91
|
-
![](i/mooncat-ff5f000ca7_x2.png)
|
89
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-00000800fa_x2.png)
|
90
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-0077c8278d_x2.png)
|
91
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-ff5f000ca7_x2.png)
|
92
92
|
|
93
93
|
|
94
94
|
And x4:
|
95
95
|
|
96
|
-
![](i/mooncat-00000800fa_x4.png)
|
97
|
-
![](i/mooncat-0077c8278d_x4.png)
|
98
|
-
![](i/mooncat-ff5f000ca7_x4.png)
|
96
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-00000800fa_x4.png)
|
97
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-0077c8278d_x4.png)
|
98
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-ff5f000ca7_x4.png)
|
99
99
|
|
100
100
|
|
101
101
|
And x8:
|
102
102
|
|
103
|
-
![](i/mooncat-00000800fa_x8.png)
|
104
|
-
![](i/mooncat-0077c8278d_x8.png)
|
105
|
-
![](i/mooncat-ff5f000ca7_x8.png)
|
103
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-00000800fa_x8.png)
|
104
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-0077c8278d_x8.png)
|
105
|
+
![](https://github.com/cryptocopycats/mooncats/raw/master/mooncats/i/mooncat-ff5f000ca7_x8.png)
|
106
|
+
|
106
107
|
|
107
108
|
|
108
109
|
|
@@ -115,7 +116,7 @@ And so on.
|
|
115
116
|
Yes, you can mint mooncats in your own scripts
|
116
117
|
and much more.
|
117
118
|
See the
|
118
|
-
[**Programming
|
119
|
+
[**Programming MoonCats & MarsCats Step-by-Step Booklet / Guide »**](https://github.com/cryptocopycats/programming-mooncats)
|
119
120
|
|
120
121
|
|
121
122
|
|
data/Rakefile
CHANGED
data/lib/mooncats.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
## 3rd party
|
2
|
-
require '
|
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
|
|
@@ -12,8 +11,11 @@ require 'optparse'
|
|
12
11
|
## our own code
|
13
12
|
require 'mooncats/version' # note: let version always go first
|
14
13
|
require 'mooncats/designs'
|
14
|
+
require 'mooncats/structs'
|
15
|
+
require 'mooncats/design'
|
15
16
|
require 'mooncats/image'
|
16
|
-
|
17
|
+
require 'mooncats/composite'
|
18
|
+
require 'mooncats/dataset'
|
17
19
|
|
18
20
|
|
19
21
|
|
@@ -61,15 +63,20 @@ module Mooncats
|
|
61
63
|
## note: cut-off optionial 0x
|
62
64
|
cat_id = cat_id[2..-1] if cat_id.start_with?( '0x')
|
63
65
|
|
66
|
+
cat = Image.generate( cat_id )
|
67
|
+
|
64
68
|
cat_name = "mooncat-#{cat_id}"
|
65
69
|
|
66
70
|
## if zoom - add x2,x4 or such
|
67
|
-
|
71
|
+
if opts[:zoom] != 1
|
72
|
+
cat = cat.zoom( opts[:zoom] )
|
73
|
+
cat_name << "_x#{opts[:zoom]}"
|
74
|
+
end
|
68
75
|
|
69
76
|
path = "#{opts[:outdir]}/#{cat_name}.png"
|
70
77
|
puts "==> (#{index+1}/#{args.size}) minting mooncat 0x#{cat_id}; writing to >#{path}<..."
|
71
78
|
|
72
|
-
|
79
|
+
cat.save( path )
|
73
80
|
end
|
74
81
|
|
75
82
|
puts "done"
|
@@ -87,4 +94,11 @@ module Mooncats
|
|
87
94
|
MoonCats = Mooncats
|
88
95
|
|
89
96
|
|
97
|
+
|
98
|
+
|
99
|
+
###
|
100
|
+
# note: for convenience auto include Pixelart namespace!!! - why? why not?
|
101
|
+
include Pixelart
|
102
|
+
|
103
|
+
|
90
104
|
puts Mooncats.banner # say hello
|
@@ -0,0 +1,69 @@
|
|
1
|
+
|
2
|
+
module Mooncats
|
3
|
+
class Image
|
4
|
+
class Composite ## nest Composite inside Image - why? why not?
|
5
|
+
|
6
|
+
|
7
|
+
CANVAS_WIDTH = 24 ## sorry - for now "hard-coded" / fixed - 24x24
|
8
|
+
CANVAS_HEIGHT = 24
|
9
|
+
|
10
|
+
def initialize( cols=100, rows=255 )
|
11
|
+
@composite = ChunkyPNG::Image.new( cols*CANVAS_WIDTH,
|
12
|
+
rows*CANVAS_HEIGHT,
|
13
|
+
ChunkyPNG::Color::TRANSPARENT ) # why? why not? - use TRANSPARENT (is default?)
|
14
|
+
|
15
|
+
## todo/check - find a better name for cols/rows - why? why not?
|
16
|
+
@cols = cols
|
17
|
+
@rows = rows
|
18
|
+
|
19
|
+
@i = 0 # (track) current index (of added images)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def add( image )
|
24
|
+
y, x = @i.divmod( @cols )
|
25
|
+
|
26
|
+
puts " width: #{image.width}, height: #{image.height}"
|
27
|
+
|
28
|
+
## try to center image (identicon) in 24x24 canvas
|
29
|
+
## the 4 formats are
|
30
|
+
## - 21×17 - Standing
|
31
|
+
## - 20×14 - Sleeping
|
32
|
+
## - 17×22 - Pouncing
|
33
|
+
## - 20×21 - Stalking
|
34
|
+
## e.g. add left padding (x_center) and
|
35
|
+
## top padding (y_center)
|
36
|
+
x_center, y_center = case [image.width, image.height]
|
37
|
+
when [21,17] then [1,3]
|
38
|
+
when [20,14] then [2,5]
|
39
|
+
when [17,22] then [3,1]
|
40
|
+
when [20,21] then [2,1]
|
41
|
+
## todo/fix: report unknown image format/size!!!!
|
42
|
+
end
|
43
|
+
|
44
|
+
## note: image.image - "unwrap" the "raw" ChunkyPNG::Image
|
45
|
+
@composite.compose!( image.image, x*CANVAS_WIDTH+x_center, y*CANVAS_HEIGHT+y_center )
|
46
|
+
@i += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
alias_method :<<, :add
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
#####
|
54
|
+
# (image) delegates
|
55
|
+
## todo/check: add some more??
|
56
|
+
def save( path, constraints = {} )
|
57
|
+
@composite.save( path, constraints )
|
58
|
+
end
|
59
|
+
|
60
|
+
def width() @composite.width; end
|
61
|
+
def height() @composite.height; end
|
62
|
+
|
63
|
+
## return image ref - use a different name - why? why not?
|
64
|
+
def image() @composite; end
|
65
|
+
|
66
|
+
|
67
|
+
end # class Composite
|
68
|
+
end # class Image
|
69
|
+
end # module Mooncats
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Mooncats
|
4
|
+
module Dataset
|
5
|
+
|
6
|
+
def self.read( path='./datasets/mooncats/*.csv' )
|
7
|
+
|
8
|
+
datasets = Dir.glob( path )
|
9
|
+
## pp datasets
|
10
|
+
#=> ["./datasets/mooncats/00.csv",
|
11
|
+
# "./datasets/mooncats/01.csv",
|
12
|
+
# "./datasets/mooncats/02.csv",
|
13
|
+
# "./datasets/mooncats/03.csv",
|
14
|
+
# "./datasets/mooncats/04.csv",
|
15
|
+
# ...]
|
16
|
+
|
17
|
+
rows = []
|
18
|
+
datasets.each do |dataset|
|
19
|
+
rows += CsvHash.read( dataset )
|
20
|
+
end
|
21
|
+
|
22
|
+
## puts " #{rows.size} rows(s)"
|
23
|
+
#=> 25440 rows(s)
|
24
|
+
|
25
|
+
|
26
|
+
### wrap in mooncat struct for easier access
|
27
|
+
mooncats = []
|
28
|
+
rows.each_with_index do |row,i|
|
29
|
+
## print "." if i % 100
|
30
|
+
id = row['id']
|
31
|
+
## note: skip all derived column from id e.g.
|
32
|
+
## - r,g,b, etc.
|
33
|
+
|
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 )
|
45
|
+
end
|
46
|
+
## print "\n" ## add progress
|
47
|
+
|
48
|
+
mooncats
|
49
|
+
end
|
50
|
+
end # module Dataset
|
51
|
+
end # module Mooncat
|
52
|
+
|
53
|
+
|
@@ -0,0 +1,92 @@
|
|
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
|
+
|
19
|
+
def self.parse( str )
|
20
|
+
## support original "classic" compact single-line format
|
21
|
+
## e.g. 00011111100000000.01113333310000000.13533333331110000....
|
22
|
+
## note: this format needs to get rotated by 90 degree!!!
|
23
|
+
if str.start_with?( /[0-5]+\.[0-5]+\.[0-5]+/ ) ## quick (and dirty) heuristic check
|
24
|
+
data = str.split('.')
|
25
|
+
|
26
|
+
## note: map colors encoded as a string to an array of integers - why? why not?
|
27
|
+
## e.g. "00011111133344411"
|
28
|
+
## =>
|
29
|
+
## [0,0,0,1,1,1,1,1,1,3,3,3,4,4,4,1,1]
|
30
|
+
data = data.map do |row|
|
31
|
+
row.chars.map do |color|
|
32
|
+
color.to_i
|
33
|
+
end
|
34
|
+
end
|
35
|
+
data = data.transpose ## note: rotate by 90 degree!!!!!
|
36
|
+
else ## assume "modern" pixelart format
|
37
|
+
## todo/check: delegate to pixelart parse or such - why? why not?
|
38
|
+
|
39
|
+
data = []
|
40
|
+
str.each_line do |line|
|
41
|
+
line = line.strip
|
42
|
+
next if line.empty? ## skipping empty line in pixel art source
|
43
|
+
next if line.start_with?( '#' ) ## skipping comment line in pixel art source
|
44
|
+
|
45
|
+
## note: allow multiple spaces or tabs to separate pixel codes
|
46
|
+
data << line.split( /[ \t]+/)
|
47
|
+
end
|
48
|
+
## todo/check: change to use strings (instead of nummbers) in the future?
|
49
|
+
## why? why not? stay "compatible" to pixel art format/machinery?
|
50
|
+
data = data.map do |row|
|
51
|
+
row.map do |color|
|
52
|
+
color.to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
new( data )
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def initialize( data )
|
61
|
+
@data = data
|
62
|
+
end
|
63
|
+
|
64
|
+
def width
|
65
|
+
## todo/check: use/find max - why? why not? lets you you used "unbalanced" / shortcut lines too
|
66
|
+
@data[0].size
|
67
|
+
end
|
68
|
+
def height() @data.size; end
|
69
|
+
|
70
|
+
|
71
|
+
def each_with_index( &blk )
|
72
|
+
@data.each_with_index { |row, y| blk.call( row, y ) }
|
73
|
+
end
|
74
|
+
def each( &blk )
|
75
|
+
@data.each { |row| blk.call( row ) }
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
def to_txt
|
81
|
+
buf = String.new('')
|
82
|
+
|
83
|
+
@data.each do |row|
|
84
|
+
buf << row.join( ' ' )
|
85
|
+
buf << "\n"
|
86
|
+
end
|
87
|
+
buf
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
end # class Design
|
92
|
+
end # module Mooncats
|
data/lib/mooncats/image.rb
CHANGED
@@ -1,46 +1,46 @@
|
|
1
1
|
|
2
2
|
module Mooncats
|
3
|
-
|
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(
|
11
|
-
|
12
|
-
|
13
|
-
genesis = bytes[0] != 0 ## note: convert to bool (if zero assume NOT genesis)
|
14
|
-
k = bytes[1]
|
15
|
-
r = bytes[2]
|
16
|
-
g = bytes[3]
|
17
|
-
b = bytes[4]
|
12
|
+
def self.generate( id )
|
13
|
+
meta = Metadata.new( id )
|
18
14
|
|
19
|
-
|
20
|
-
design = k % 128
|
15
|
+
design = meta.design.to_i # note: meta.design is a struct/object - keep/use a local int !!!
|
21
16
|
|
22
|
-
colors = if genesis
|
23
|
-
if design % 2
|
24
|
-
design % 2
|
17
|
+
colors = if meta.genesis?
|
18
|
+
if design % 2 == 0 && meta.invert? ||
|
19
|
+
design % 2 == 1 && !meta.invert?
|
25
20
|
COLORS_GENESIS_WHITE
|
26
21
|
else
|
27
22
|
COLORS_GENESIS_BLACK
|
28
23
|
end
|
29
24
|
else
|
30
|
-
derive_palette( r
|
25
|
+
derive_palette( r: meta.r,
|
26
|
+
g: meta.g,
|
27
|
+
b: meta.b,
|
28
|
+
invert: meta.invert? )
|
31
29
|
end
|
32
30
|
|
33
31
|
new( design: design,
|
34
|
-
colors: colors
|
35
|
-
zoom: zoom )
|
32
|
+
colors: colors )
|
36
33
|
end
|
37
34
|
|
35
|
+
### add more (convenience) aliases
|
36
|
+
class << self
|
37
|
+
alias_method :mint, :generate
|
38
|
+
end
|
38
39
|
|
39
|
-
def initialize( design: 0,
|
40
|
-
colors: COLORS_GENESIS_WHITE,
|
41
|
-
zoom: 1 )
|
42
40
|
|
43
|
-
|
41
|
+
|
42
|
+
def initialize( design: 0,
|
43
|
+
colors: COLORS_GENESIS_WHITE )
|
44
44
|
|
45
45
|
design = if design.is_a?( String )
|
46
46
|
Design.parse( design )
|
@@ -54,72 +54,55 @@ def initialize( design: 0,
|
|
54
54
|
end
|
55
55
|
|
56
56
|
## note: first color (index 0) is always nil (default/white or transparent)
|
57
|
-
colors = [ nil ] +
|
57
|
+
colors = [ nil ] + colors.map { |color| Pixelart::Color.parse( color ) }
|
58
58
|
|
59
59
|
## puts " colors:"
|
60
60
|
## pp colors
|
61
61
|
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
img = ChunkyPNG::Image.new( design.width,
|
63
|
+
design.height,
|
64
|
+
ChunkyPNG::Color::TRANSPARENT ) # why? why not?
|
65
65
|
|
66
|
-
design.each_with_index do |row,
|
67
|
-
row.each_with_index do |color,
|
66
|
+
design.each_with_index do |row, y|
|
67
|
+
row.each_with_index do |color, x|
|
68
68
|
if color > 0
|
69
69
|
pixel = colors[ color ]
|
70
|
-
|
71
|
-
zoom.times do |m|
|
72
|
-
@cat[n+zoom*x,m+zoom*y] = pixel
|
73
|
-
end
|
74
|
-
end
|
70
|
+
img[x,y] = pixel
|
75
71
|
end # has color?
|
76
72
|
end # each row
|
77
73
|
end # each data
|
78
|
-
end
|
79
74
|
|
80
|
-
|
81
|
-
|
82
|
-
## todo/check: add some more??
|
83
|
-
def save( path ) @cat.save( path ); end
|
84
|
-
def width() @cat.width; end
|
85
|
-
def height() @cat.height; end
|
75
|
+
super( img.width, img.height, img )
|
76
|
+
end
|
86
77
|
|
87
78
|
|
88
79
|
|
89
80
|
##################
|
90
81
|
# (static) helpers
|
91
|
-
def self.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
##
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
#=> [237, 0.9705882352941178, 0.26666666666666666]
|
116
|
-
|
117
|
-
h = hsl[0]
|
118
|
-
s = hsl[1]
|
119
|
-
l = hsl[2]
|
120
|
-
|
121
|
-
hx = h % 360
|
122
|
-
hy = (h + 320) % 360
|
82
|
+
def self.derive_palette( r: nil, g: nil, b: nil,
|
83
|
+
hue: nil,
|
84
|
+
invert: false )
|
85
|
+
|
86
|
+
if hue
|
87
|
+
## pass through as is 1:1
|
88
|
+
else ## assume r, g, b
|
89
|
+
## note: Color.rgb returns an Integer (e.g. 34113279 - true color or just hex rgba or?)
|
90
|
+
rgb = ChunkyPNG::Color.rgb( r, g, b )
|
91
|
+
|
92
|
+
# to_hsl(color, include_alpha = false) ⇒ Array<Fixnum>[0], ...
|
93
|
+
# Returns an array with the separate HSL components of a color.
|
94
|
+
hsl = ChunkyPNG::Color.to_hsl( rgb )
|
95
|
+
#=> [237, 0.9705882352941178, 0.26666666666666666]
|
96
|
+
|
97
|
+
# h = hsl[0]
|
98
|
+
# s = hsl[1]
|
99
|
+
# l = hsl[2]
|
100
|
+
|
101
|
+
hue = hsl[0]
|
102
|
+
end
|
103
|
+
|
104
|
+
hx = hue % 360 ## note: makes sure number is always POSITIVE (e.g. -13 % 360 => 347)
|
105
|
+
hy = (hue + 320) % 360
|
123
106
|
#=> e.g. hx: 237, hy: 197
|
124
107
|
|
125
108
|
c1 = ChunkyPNG::Color.from_hsl( hx, 1, 0.1 )
|
@@ -141,75 +124,6 @@ def self.derive_palette( r, g, b, invert: false )
|
|
141
124
|
end
|
142
125
|
|
143
126
|
|
144
|
-
######
|
145
|
-
# helpers
|
146
|
-
def parse_colors( colors )
|
147
|
-
## convert into ChunkyPNG::Color
|
148
|
-
colors.map { |color| parse_color( color ) }
|
149
|
-
end
|
150
|
-
|
151
|
-
def parse_color( color )
|
152
|
-
if color.is_a?( Integer ) ## e.g. Assumess ChunkyPNG::Color.rgb() or such
|
153
|
-
color ## pass through as is 1:1
|
154
|
-
elsif color.is_a?(String)
|
155
|
-
## note: return an Integer !!! (not a Color class or such!!! )
|
156
|
-
ChunkyPNG::Color.from_hex( color )
|
157
|
-
else
|
158
|
-
raise ArgumentError, "unknown color format; cannot parse - expected rgb hex string e.g. d3d3d3"
|
159
|
-
end
|
160
|
-
end
|
161
127
|
end # class Image
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
class Design
|
166
|
-
def self.find( num ) ## pass in design index number (0 to 127)
|
167
|
-
## todo: add cache (memoize) - why? why not?
|
168
|
-
str = DESIGNS[ num ]
|
169
|
-
design = parse( str )
|
170
|
-
|
171
|
-
puts " design ##{num} (#{design.width}x#{design.height})"
|
172
|
-
## pp design.data
|
173
|
-
## puts
|
174
|
-
|
175
|
-
design
|
176
|
-
end
|
177
|
-
|
178
|
-
|
179
|
-
def self.parse( str )
|
180
|
-
data = str.split('.')
|
181
|
-
new( data )
|
182
|
-
end
|
183
|
-
|
184
|
-
def initialize( data )
|
185
|
-
## todo: add cache (memoize) - why? why not?
|
186
|
-
|
187
|
-
## note: map colors encoded as a string to an array of integers - why? why not?
|
188
|
-
## e.g. "00011111133344411"
|
189
|
-
## =>
|
190
|
-
## [0,0,0,1,1,1,1,1,1,3,3,3,4,4,4,1,1]
|
191
|
-
|
192
|
-
@data = data.map do |row|
|
193
|
-
row.chars.map do |color|
|
194
|
-
color.to_i
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
## note: design data stored mirrored (data.size is the width NOT height)
|
200
|
-
def width() @data.size; end
|
201
|
-
def height() @data[0].size; end
|
202
|
-
|
203
|
-
def data() @data; end
|
204
|
-
|
205
|
-
def each_with_index( &blk )
|
206
|
-
## note: y,x is reversed - keep for now
|
207
|
-
## (todo/fix later? and "pivot" raw data on init - why? why not?)
|
208
|
-
@data.each_with_index do |row, x|
|
209
|
-
blk.call( row, x )
|
210
|
-
end
|
211
|
-
end
|
212
|
-
end # class Design
|
213
|
-
|
214
128
|
end # module Mooncats
|
215
129
|
|
@@ -0,0 +1,242 @@
|
|
1
|
+
|
2
|
+
module Mooncats
|
3
|
+
|
4
|
+
### wrap metadata (e.g. pose, fur, facing, color, etc.)
|
5
|
+
## in structs for easy/easier access)
|
6
|
+
|
7
|
+
POSES = [
|
8
|
+
'Standing', ## 00
|
9
|
+
'Sleeping', ## 01
|
10
|
+
'Pouncing', ## 10
|
11
|
+
'Stalking', ## 11
|
12
|
+
]
|
13
|
+
|
14
|
+
|
15
|
+
OLD_FACES = [ ## old names for face (expressions)
|
16
|
+
'Smile', ## 00
|
17
|
+
'Frown (Look Down)', ## 01
|
18
|
+
'Frown (Look Up)', ## 10
|
19
|
+
'Flat Whiskers', ## 11
|
20
|
+
]
|
21
|
+
|
22
|
+
FACES = [ ## face expressions
|
23
|
+
'Smiling', ## 00
|
24
|
+
'Grumpy', ## 01
|
25
|
+
'Pouting', ## 10
|
26
|
+
'Shy', ## 11
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
OLD_FURS = [ ## old names for fur (patterns)
|
31
|
+
'Solid', ## 00
|
32
|
+
'Striped', ## 01
|
33
|
+
'Eyepatch', ## 10
|
34
|
+
'Half/Half', ## 11
|
35
|
+
]
|
36
|
+
|
37
|
+
FURS = [ ## fur (patterns)
|
38
|
+
'Pure', ## 00
|
39
|
+
'Tabby', ## 01
|
40
|
+
'Spotted', ## 10
|
41
|
+
'Tortie', ## 11
|
42
|
+
]
|
43
|
+
|
44
|
+
|
45
|
+
FACINGS = [
|
46
|
+
'Left', # 0
|
47
|
+
'Right', # 1
|
48
|
+
]
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
class Metadata
|
53
|
+
|
54
|
+
class Design ## nested classed - why? lets you use Metadata::Design "standalone", that is, without 5-byte id
|
55
|
+
def initialize( num ) # 0-127 design num(ber)
|
56
|
+
@num = num
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_i() @num; end
|
60
|
+
|
61
|
+
def bits ## keep private / internal - why? why not?
|
62
|
+
## keep 128 possible designs 0 to 127
|
63
|
+
## as 7 bit string e.g. 01010111 for now - why? why not?
|
64
|
+
@bits ||= '%08b' % @num
|
65
|
+
end
|
66
|
+
|
67
|
+
def facing
|
68
|
+
@facing ||= FACINGS[ bits[1,1].to_i(2) ] ## use desgin > 63 instead - why? why not?
|
69
|
+
end
|
70
|
+
def face ## face (expression)
|
71
|
+
@face ||= FACES[ bits[2,2].to_i(2) ]
|
72
|
+
end
|
73
|
+
alias_method :expression, :face
|
74
|
+
|
75
|
+
def fur ## fur (pattern) - add pattern alias - why? why not?
|
76
|
+
@fur ||= FURS[ bits[4,2].to_i(2) ]
|
77
|
+
end
|
78
|
+
|
79
|
+
def pose
|
80
|
+
@poses ||= POSES[ bits[6,2].to_i(2) ] ## use design % 4 instead - why? why not?
|
81
|
+
end
|
82
|
+
end ## (nested) class Metadata::Design
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
def initialize( id, **more )
|
88
|
+
@bytes = self.class.hex_to_bytes( id )
|
89
|
+
|
90
|
+
## add support for more "external" meta data
|
91
|
+
## ## e.g. mint, mint_block, etc.
|
92
|
+
@more = more
|
93
|
+
end
|
94
|
+
|
95
|
+
def id
|
96
|
+
@id ||= @bytes.map { |byte| '%02x' % byte }.join
|
97
|
+
end
|
98
|
+
|
99
|
+
def genesis?
|
100
|
+
@bytes[0] != 0 ## note: convert to bool (if zero assume NOT genesis)
|
101
|
+
end
|
102
|
+
def k() @bytes[1]; end
|
103
|
+
def r() @bytes[2]; end
|
104
|
+
def g() @bytes[3]; end
|
105
|
+
def b() @bytes[4]; end
|
106
|
+
|
107
|
+
def rgb() [r,g,b]; end ## add rgb shortcut helper - why? why not?
|
108
|
+
|
109
|
+
def invert?() k >= 128; end
|
110
|
+
alias_method :pale?, :invert?
|
111
|
+
|
112
|
+
|
113
|
+
def pattern() k % 64; end ## treat facing left|right as the same - note: conflicts with fur (pattern) - find a better/different name?
|
114
|
+
|
115
|
+
|
116
|
+
def hue
|
117
|
+
@hue ||= begin
|
118
|
+
## note: hsl[0], that is, hue MIGHT BE NEGATIVE!
|
119
|
+
## e.g. try rbg( 91, 27, 41 )
|
120
|
+
## resulting in
|
121
|
+
## hsl( -13, 0.5423728813559322, 0.23137254901960785 )
|
122
|
+
## remember: always use % 360 to make positive!!!
|
123
|
+
## e.g. -13 % 360 => 347
|
124
|
+
## -25 % 360 => 335
|
125
|
+
rgb = ChunkyPNG::Color.rgb( r, g, b )
|
126
|
+
hsl = ChunkyPNG::Color.to_hsl( rgb )
|
127
|
+
hsl[0] % 360 ## make sure number is always POSITIVE!!!
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def color
|
133
|
+
case hue
|
134
|
+
when 345..359,
|
135
|
+
0..14 then 'Red'
|
136
|
+
when 15..44 then 'Orange'
|
137
|
+
when 45..74 then 'Yellow'
|
138
|
+
when 75..104 then 'Chartreuse'
|
139
|
+
when 105..134 then 'Green'
|
140
|
+
when 135..164 then 'Teal'
|
141
|
+
when 165..194 then 'Cyan'
|
142
|
+
when 195..224 then 'Sky Blue'
|
143
|
+
when 225..254 then 'Blue'
|
144
|
+
when 255..284 then 'Purple'
|
145
|
+
when 285..314 then 'Magenta'
|
146
|
+
when 315..344 then 'Fuchsia'
|
147
|
+
else
|
148
|
+
puts "!! ERROR - unexpected hue (in degress); got #{hue} - expected 0 to 359"
|
149
|
+
exit 1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
def xxx_color_old_formula ## remove - move to attic??
|
155
|
+
case hue
|
156
|
+
when 0..29 then 'Red'
|
157
|
+
when 30..59 then 'Orange'
|
158
|
+
when 60..89 then 'Yellow'
|
159
|
+
when 90..119 then 'Chartreuse'
|
160
|
+
when 120..149 then 'Green'
|
161
|
+
when 150..179 then 'Lime Green' ## now renamed to Teal
|
162
|
+
when 180..209 then 'Cyan'
|
163
|
+
when 210..239 then 'Sky Blue'
|
164
|
+
when 240..269 then 'Blue'
|
165
|
+
when 270..299 then 'Purple'
|
166
|
+
when 300..329 then 'Magenta'
|
167
|
+
when 330..359 then 'Fuchsia'
|
168
|
+
else
|
169
|
+
puts "!! ERROR - unexpected hue (in degress); got #{hue} - expected 0 to 359"
|
170
|
+
exit 1
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def design
|
176
|
+
@design ||= Design.new( k % 128 )
|
177
|
+
end
|
178
|
+
|
179
|
+
def facing() design.facing; end
|
180
|
+
def face() design.face; end
|
181
|
+
alias_method :expression, :face
|
182
|
+
def fur() design.fur; end
|
183
|
+
def pose() design.pose; end
|
184
|
+
|
185
|
+
|
186
|
+
####
|
187
|
+
# more "external" attributes
|
188
|
+
def mint() @more[:mint]; end
|
189
|
+
def block() @more[:block]; end
|
190
|
+
def timestamp() @more[:timestamp]; end
|
191
|
+
def year() timestamp ? timestamp.year : nil; end
|
192
|
+
|
193
|
+
#####
|
194
|
+
# enable array-like access to - why? why not?
|
195
|
+
def []( key )
|
196
|
+
case key.to_sym
|
197
|
+
when :id then id
|
198
|
+
when :genesis then genesis?
|
199
|
+
when :k then k
|
200
|
+
when :r then r
|
201
|
+
when :g then g
|
202
|
+
when :b then b
|
203
|
+
when :rgb then rgb
|
204
|
+
when :invert,
|
205
|
+
:pale then invert?
|
206
|
+
when :hue then hue
|
207
|
+
when :color then color
|
208
|
+
when :design then design.to_i
|
209
|
+
when :pattern then pattern
|
210
|
+
when :facing then facing
|
211
|
+
when :face,
|
212
|
+
:expression then face
|
213
|
+
when :fur then fur
|
214
|
+
when :pose then pose
|
215
|
+
when :year then year # note: from more via timestamp
|
216
|
+
else
|
217
|
+
@more[ key ]
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
##################
|
223
|
+
# (static) helpers
|
224
|
+
def self.hex_to_bytes( str_or_num )
|
225
|
+
if str_or_num.is_a?( Integer ) ## allow passing in of integer to e.g. 0x... etc.
|
226
|
+
num = str_or_num
|
227
|
+
str = '%010x' % num # 5 bytes (10 hex digits/chars)
|
228
|
+
else ## assume string
|
229
|
+
## cut-off optionial 0x
|
230
|
+
str = str_or_num
|
231
|
+
str = str.downcase
|
232
|
+
str = str[2..-1] if str.start_with?( '0x')
|
233
|
+
end
|
234
|
+
|
235
|
+
raise ArgumentError, "expected 5 byte hex string (10 digits/chars); got #{str_or_num}" if str.size != 10
|
236
|
+
|
237
|
+
bytes = [str].pack('H*').bytes
|
238
|
+
bytes
|
239
|
+
end
|
240
|
+
end # class Metadata
|
241
|
+
|
242
|
+
end # module Mooncats
|
data/lib/mooncats/version.rb
CHANGED
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mooncats
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-04-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: pixelart
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
@@ -81,19 +81,21 @@ executables:
|
|
81
81
|
extensions: []
|
82
82
|
extra_rdoc_files:
|
83
83
|
- CHANGELOG.md
|
84
|
-
- LICENSE.md
|
85
84
|
- Manifest.txt
|
86
85
|
- README.md
|
87
86
|
files:
|
88
87
|
- CHANGELOG.md
|
89
|
-
- LICENSE.md
|
90
88
|
- Manifest.txt
|
91
89
|
- README.md
|
92
90
|
- Rakefile
|
93
91
|
- bin/mooncat
|
94
92
|
- lib/mooncats.rb
|
93
|
+
- lib/mooncats/composite.rb
|
94
|
+
- lib/mooncats/dataset.rb
|
95
|
+
- lib/mooncats/design.rb
|
95
96
|
- lib/mooncats/designs.rb
|
96
97
|
- lib/mooncats/image.rb
|
98
|
+
- lib/mooncats/structs.rb
|
97
99
|
- lib/mooncats/version.rb
|
98
100
|
homepage: https://github.com/cryptocopycats/mooncats
|
99
101
|
licenses:
|
data/LICENSE.md
DELETED
@@ -1,116 +0,0 @@
|
|
1
|
-
CC0 1.0 Universal
|
2
|
-
|
3
|
-
Statement of Purpose
|
4
|
-
|
5
|
-
The laws of most jurisdictions throughout the world automatically confer
|
6
|
-
exclusive Copyright and Related Rights (defined below) upon the creator and
|
7
|
-
subsequent owner(s) (each and all, an "owner") of an original work of
|
8
|
-
authorship and/or a database (each, a "Work").
|
9
|
-
|
10
|
-
Certain owners wish to permanently relinquish those rights to a Work for the
|
11
|
-
purpose of contributing to a commons of creative, cultural and scientific
|
12
|
-
works ("Commons") that the public can reliably and without fear of later
|
13
|
-
claims of infringement build upon, modify, incorporate in other works, reuse
|
14
|
-
and redistribute as freely as possible in any form whatsoever and for any
|
15
|
-
purposes, including without limitation commercial purposes. These owners may
|
16
|
-
contribute to the Commons to promote the ideal of a free culture and the
|
17
|
-
further production of creative, cultural and scientific works, or to gain
|
18
|
-
reputation or greater distribution for their Work in part through the use and
|
19
|
-
efforts of others.
|
20
|
-
|
21
|
-
For these and/or other purposes and motivations, and without any expectation
|
22
|
-
of additional consideration or compensation, the person associating CC0 with a
|
23
|
-
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
24
|
-
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
25
|
-
and publicly distribute the Work under its terms, with knowledge of his or her
|
26
|
-
Copyright and Related Rights in the Work and the meaning and intended legal
|
27
|
-
effect of CC0 on those rights.
|
28
|
-
|
29
|
-
1. Copyright and Related Rights. A Work made available under CC0 may be
|
30
|
-
protected by copyright and related or neighboring rights ("Copyright and
|
31
|
-
Related Rights"). Copyright and Related Rights include, but are not limited
|
32
|
-
to, the following:
|
33
|
-
|
34
|
-
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
35
|
-
and translate a Work;
|
36
|
-
|
37
|
-
ii. moral rights retained by the original author(s) and/or performer(s);
|
38
|
-
|
39
|
-
iii. publicity and privacy rights pertaining to a person's image or likeness
|
40
|
-
depicted in a Work;
|
41
|
-
|
42
|
-
iv. rights protecting against unfair competition in regards to a Work,
|
43
|
-
subject to the limitations in paragraph 4(a), below;
|
44
|
-
|
45
|
-
v. rights protecting the extraction, dissemination, use and reuse of data in
|
46
|
-
a Work;
|
47
|
-
|
48
|
-
vi. database rights (such as those arising under Directive 96/9/EC of the
|
49
|
-
European Parliament and of the Council of 11 March 1996 on the legal
|
50
|
-
protection of databases, and under any national implementation thereof,
|
51
|
-
including any amended or successor version of such directive); and
|
52
|
-
|
53
|
-
vii. other similar, equivalent or corresponding rights throughout the world
|
54
|
-
based on applicable law or treaty, and any national implementations thereof.
|
55
|
-
|
56
|
-
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
57
|
-
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
58
|
-
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
59
|
-
and Related Rights and associated claims and causes of action, whether now
|
60
|
-
known or unknown (including existing as well as future claims and causes of
|
61
|
-
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
62
|
-
duration provided by applicable law or treaty (including future time
|
63
|
-
extensions), (iii) in any current or future medium and for any number of
|
64
|
-
copies, and (iv) for any purpose whatsoever, including without limitation
|
65
|
-
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
66
|
-
the Waiver for the benefit of each member of the public at large and to the
|
67
|
-
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
68
|
-
shall not be subject to revocation, rescission, cancellation, termination, or
|
69
|
-
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
70
|
-
by the public as contemplated by Affirmer's express Statement of Purpose.
|
71
|
-
|
72
|
-
3. Public License Fallback. Should any part of the Waiver for any reason be
|
73
|
-
judged legally invalid or ineffective under applicable law, then the Waiver
|
74
|
-
shall be preserved to the maximum extent permitted taking into account
|
75
|
-
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
76
|
-
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
77
|
-
non transferable, non sublicensable, non exclusive, irrevocable and
|
78
|
-
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
79
|
-
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
80
|
-
provided by applicable law or treaty (including future time extensions), (iii)
|
81
|
-
in any current or future medium and for any number of copies, and (iv) for any
|
82
|
-
purpose whatsoever, including without limitation commercial, advertising or
|
83
|
-
promotional purposes (the "License"). The License shall be deemed effective as
|
84
|
-
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
85
|
-
License for any reason be judged legally invalid or ineffective under
|
86
|
-
applicable law, such partial invalidity or ineffectiveness shall not
|
87
|
-
invalidate the remainder of the License, and in such case Affirmer hereby
|
88
|
-
affirms that he or she will not (i) exercise any of his or her remaining
|
89
|
-
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
90
|
-
and causes of action with respect to the Work, in either case contrary to
|
91
|
-
Affirmer's express Statement of Purpose.
|
92
|
-
|
93
|
-
4. Limitations and Disclaimers.
|
94
|
-
|
95
|
-
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
96
|
-
surrendered, licensed or otherwise affected by this document.
|
97
|
-
|
98
|
-
b. Affirmer offers the Work as-is and makes no representations or warranties
|
99
|
-
of any kind concerning the Work, express, implied, statutory or otherwise,
|
100
|
-
including without limitation warranties of title, merchantability, fitness
|
101
|
-
for a particular purpose, non infringement, or the absence of latent or
|
102
|
-
other defects, accuracy, or the present or absence of errors, whether or not
|
103
|
-
discoverable, all to the greatest extent permissible under applicable law.
|
104
|
-
|
105
|
-
c. Affirmer disclaims responsibility for clearing rights of other persons
|
106
|
-
that may apply to the Work or any use thereof, including without limitation
|
107
|
-
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
108
|
-
disclaims responsibility for obtaining any necessary consents, permissions
|
109
|
-
or other rights required for any use of the Work.
|
110
|
-
|
111
|
-
d. Affirmer understands and acknowledges that Creative Commons is not a
|
112
|
-
party to this document and has no duty or obligation with respect to this
|
113
|
-
CC0 or use of the Work.
|
114
|
-
|
115
|
-
For more information, please see
|
116
|
-
<http://creativecommons.org/publicdomain/zero/1.0/>
|