Class: Pixelart::Spritesheet

Inherits:
Object
  • Object
show all
Defined in:
lib/spritesheet/packer.rb,
lib/spritesheet/spritesheet.rb

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(img, recs, width: 24, height: 24) ⇒ Spritesheet

Returns a new instance of Spritesheet.



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/spritesheet/spritesheet.rb', line 99

def initialize( img,
                recs,
                width: 24,
                height: 24 )
   @width  = width
   @height = height

   ## todo: check if img is a ImageComposite or plain Image?

   ##              if plain Image "auto-wrap" into ImageComposite - why? why not?

   @image  = img
   @recs   = recs

   ## lookup by "case/space-insensitive" name / key

   @attributes_by_name = _build_attributes_by_name( @recs )
end

Class Method Details

._build_recs(recs) ⇒ Object

build and normalize (meta data) records



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/spritesheet/spritesheet.rb', line 43

def self._build_recs( recs )   ## build and normalize (meta data) records

   ## sort by id

   recs = recs.sort do |l,r|
                      l['id'].to_i( 10 ) <=> r['id'].to_i( 10 )   # use base10 (decimal)

                    end

   ## assert all recs are in order by id (0 to size)

   recs.each_with_index do |rec, exp_id|
      id = rec['id'].to_i(10)
      if id != exp_id
         puts "!! ERROR -  meta data record ids out-of-order - expected id #{exp_id}; got #{id}"
         exit 1
      end
   end

   ## convert to "wrapped / immutable" kind-of struct

   recs = recs.map do |rec|
            id         = rec['id'].to_i( 10 )
            name       = normalize_name( rec['name'] )

            ## todo:  double check if rec['type'] returns nil or empty string if column/field not present?

            type       = rec['type'] || rec['category']

            more_names = (rec['more_names'] || '').split( '|' )
            more_names = more_names.map {|str| normalize_name( str ) }

            ::Sprite.new(
              id:         id,
              name:       name,
              type:       type,
              more_names: more_names )
          end
   recs
end

.normalize_key(str) ⇒ Object

static helpers - (turn into "true" static self.class methods - why? why not?)



26
27
28
29
30
# File 'lib/spritesheet/spritesheet.rb', line 26

def self.normalize_key( str )
  ## add & e.g. B&W

  ## add ' e.g. McDonald's Red

   str.downcase.gsub(/[ ()&°'_-]/, '').strip
end

.normalize_name(str) ⇒ Object



32
33
34
35
# File 'lib/spritesheet/spritesheet.rb', line 32

def self.normalize_name( str )
  ## normalize spaces in more names

  str.strip.gsub( /[ ]{2,}/, ' ' )
end

.pack(path, cols: 10, dir: '.', width: 24, height: 24) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/spritesheet/packer.rb', line 7

def self.pack( path,
                 cols: 10,
                 dir: '.',
                 width: 24,
                 height: 24 )

  ## check - change dir to image_dir or such - why? why not?


  attributes = CsvHash.read( path )
  puts "  #{attributes.size} record(s)"  #=> ??



  total = attributes.size

  rows = total/cols
  rows += 1  if (total % cols) != 0     ## add extra row for remainder


  sheet = ImageComposite.new( cols, rows,
                                width: width,
                                height: height )

  meta = []  ## output meta(data) records


  ####

  #  add attributes

  attributes.each do |rec|
    img_path = rec['path']
    sheet << Image.read( "#{dir}/#{img_path}" )

    name       = rec['name']
    more_names = (rec['more_names'] || '').split( '|' )

    if name.nil? || name.empty?
       name = File.basename( img_path, File.extname( img_path ))
    end

    ## normalize spaces in more names

    names = [name] + more_names
    names = names.map {|str| str.strip.gsub(/[ ]{2,}/, ' ' )}


    meta << { 'id'         => meta.size.to_s,   ## note: always strings expected (NOT integer numbers)

              'category'   => rec['type'] || rec['category'],
              'name'       => names[0],
              'more_names' => names[1..-1].join( ' | ')}

  end

   ## pp meta

   ## puts "  #{meta.size} record(s)"


  new( sheet, _build_recs( meta ),
        width: width,
        height: height )
end

.read(image_path = "./spritesheet.png", meta_path = "./spritesheet.csv", width: 24, height: 24) ⇒ Object



87
88
89
90
91
92
93
94
95
# File 'lib/spritesheet/spritesheet.rb', line 87

def self.read( image_path="./spritesheet.png",
               meta_path="./spritesheet.csv",
                width: 24,
                height: 24)
  img = ImageComposite.read( image_path, width: width, height: height )
  recs = read_records( meta_path )

  new( img, recs, width: width, height: height )
end

.read_records(path) ⇒ Object Also known as: read_meta

method build_recs



78
79
80
81
# File 'lib/spritesheet/spritesheet.rb', line 78

def self.read_records( path )
  recs = CsvHash.read( path )
  _build_recs( recs )
end

Instance Method Details

#_build_attributes_by_name(recs) ⇒ Object

helpers



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/spritesheet/spritesheet.rb', line 149

def _build_attributes_by_name( recs )
   h = {}
   recs.each_with_index do |rec|
     names = [rec.name] + rec.more_names

     names.each do |name|
       key = normalize_key( name )

       if h[ key ]
         puts "!!! ERROR - attribute name is not unique:"
         pp rec
         puts "duplicate:"
         pp h[key]
         exit 1
       end
       h[ key ] = rec
     end
  end
   ## pp h

   h
end

#_find(name) ⇒ Object Also known as: []



143
# File 'lib/spritesheet/spritesheet.rb', line 143

def _find( name )  find_by( name: name, warn: false ); end

#export(path = './spritesheet.csv', format: 'csv') ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/spritesheet/packer.rb', line 64

def export( path='./spritesheet.csv', format: 'csv' )
  if format.downcase.to_s == 'csv'
    headers = ['id', 'category', 'name', 'more_names']

    ## todo/fix: make sure dirname path exists!!!

    File.open( path, "w:utf-8" ) do |f|
       f.write( headers.join( ', '))
       f.write( "\n")
       records.each do |rec|
         values = [rec.id,
                   rec.type,
                   rec.name,
                   rec.more_names.join( ' | ')]
         f.write( values.join( ', ' ))
         f.write( "\n" )
       end
    end
  else
    puts "!! ERROR - unsupported export format >#{format}<; sorry"
    exit 1
  end
end

#find_by(name:, warn: true) ⇒ Object



136
137
138
139
140
141
# File 'lib/spritesheet/spritesheet.rb', line 136

def find_by( name:, warn: true )
  rec = find_meta_by( name: name, warn: warn )

  ## return (cropped/sliced) image from image composite if record found

  rec ? @image[ rec.id ] : nil
end

#find_meta_by(name:, warn: true) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/spritesheet/spritesheet.rb', line 123

def find_meta_by( name:, warn: true )
   key = normalize_key( name )  ## normalize q(uery) string/symbol


   rec = @attributes_by_name[ key ]
   if rec
      puts " lookup >#{key}< => #{rec.id}: #{rec.name} / #{rec.type}"
   else
      puts "!! WARN - no lookup found for key >#{key}<"   if warn
   end

   rec
end

#imageObject Also known as: composite



116
# File 'lib/spritesheet/spritesheet.rb', line 116

def image() @image; end

#normalize_key(str) ⇒ Object



37
# File 'lib/spritesheet/spritesheet.rb', line 37

def normalize_key( str )     self.class.normalize_key( str ); end

#normalize_name(str) ⇒ Object



38
# File 'lib/spritesheet/spritesheet.rb', line 38

def normalize_name( str )    self.class.normalize_name( str ); end

#recordsObject Also known as: meta



119
# File 'lib/spritesheet/spritesheet.rb', line 119

def records() @recs; end