Created
April 26, 2025 06:28
-
-
Save jneen/f0ce7111190f016503c2b18dd93c26ff to your computer and use it in GitHub Desktop.
Revisions
-
jneen created this gist
Apr 26, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,298 @@ require 'pry' require 'chunky_png' require 'zlib' PLAYERSPRITE_DIR = File.expand_path('../resources/patches/player_sprite', __dir__) class AseParser def self.parse_file(fname) File.open(fname, 'rb', encoding: 'ASCII-8BIT') do |file| new(file).parse end end def initialize(file) @file = file end class Layer attr_reader :height, :width, :pixels def initialize(height, width, pixels) @height = height @width = width @pixels = pixels end def pix(row, col) @pixels[row * @width + col] end end def layer(name) layer = @layers.find { |layer| layer[:name] == name } or return nil Layer.new(@height, @width, layer[:pixels]) end def parse @fsize = read_dword @magic = read_word raise 'oh no' unless @magic == 0xA5E0 @frame_count = read_word raise 'oh no' unless @frame_count == 1 @width = read_word @height = read_word @depth = read_word raise 'oh no' unless @depth == 8 # (indexed) @flags = read_dword @speed = read_word read_dword read_dword @transparent_idx = read_byte @file.read(3) @col_count = read_word @pxwidth = read_byte @pxheight = read_byte @xpos = read_short @ypos = read_short @grid_width = read_word @grid_height = read_word @file.read(84) read_frame self end def read_frame frame_size = read_dword magic = read_word raise 'oh no' unless magic == 0xF1FA old_chunk_count = read_word duration = read_word @file.read(2) chunk_count = read_dword chunk_count = old_chunk_count if chunk_count == 0 @layers = [] chunks = (0...chunk_count).map do read_chunk end chunks end def read_chunk size = read_dword type = read_word fin = @file.pos - 6 + size case type when 0x2004 # Layer chunk @layers << read_layer when 0x2005 # Cel chunk read_cel(fin) else puts "(skipping chunk #{sprintf("%04x", type)})" end @file.seek(fin) end def read_cel(fin) layer_index = read_word raise 'oh no' unless @layers[layer_index] xpos = read_short ypos = read_short opacity = read_byte cel_type = read_word raise 'oh no' unless cel_type == 2 # compressed image zindex = read_short @file.read(5) # cel type 2 width = read_word height = read_word compressed = @file.read(fin - @file.pos) pixels = Zlib::Inflate.inflate(compressed).unpack('C*') @layers[layer_index][:pixels] = pixels end def read_layer flags = read_word type = read_word child_level = read_word read_word read_word blend_mode = read_word opacity = read_byte @file.read(3) name = read_string { flags: flags, type: type, child_level: child_level, blend_mode: blend_mode, opacity: opacity, name: name } end def read_byte @file.read(1).unpack1('C') end def read_word @file.read(2).unpack1('S<') end def read_short @file.read(2).unpack1('s<') end def read_dword @file.read(4).unpack1('l<') end def read_long @file.read(4).unpack1('L<') end def read_string size = read_word @file.read(size) end def read_point x = read_long y = read_long [x, y] end def read_size read_point end def read_rect x, y = read_point w, h = read_size [x, y, w, h] end def read_indexed_pixel read_byte end end module GFX class Palette def initialize(colors) @colors = colors binding.pry unless colors.sort.uniq.size == 16 end def self.parse(text) pal = [] text.scan /(\d+) (\d+) (\d+)\n/ do next pal << 0 if [$1, $2, $3].uniq == ['0'] pal << sprintf("%02x%02x%02xff", $1, $2, $3).to_i(16) end raise 'oh no' unless pal.sort.uniq.size == 16 new(pal) end def get(color) idx = @colors.index(color) binding.pry if idx.nil? idx end end class Renderer def initialize(layer) @layer = layer raise 'oh no' unless @layer.width % 8 == 0 raise 'oh no' unless @layer.height % 8 == 0 end def render out = [] each_8x8 do |row8, col8| [[0, 1], [2, 3]].each do |group| (0...8).each do |row| group.each do |plane| mask = (1 << plane) num = 0 (0...8).each do |col| num <<= 1 num |= ((pix(row8 + row, col8 + col) & mask) >> plane) end out << num end end end end out.pack('c*') end private def pix(row, col) @layer.pix(row, col) end def each_8x8(&b) (0...@layer.height/8).each do |row8| (0...@layer.width/8).each do |col8| yield row8 * 8, col8 * 8 end end end end end def cli(argv) mode = argv.shift case mode when 'render' infile = argv.shift outfile = argv.shift puts "parsing aseprite file #{infile}..." ase = AseParser.parse_file(infile) layer = ase.layer('OUTPUT') puts "done" bytes = GFX::Renderer.new(layer).render print "writing gfx bin to #{outfile}..." File.binwrite(outfile, bytes) puts "done" else binding.pry end end cli(ARGV.to_a)