#!/usr/bin/env ruby require 'rmagick' require 'yaml' require 'erb' data = YAML.load(File.read('spritesheet.yml')) class Sprite def initialize(image:, sx:, sy:, width:, height:) @image = image @sx = sx @sy = sy @width = width @height = height end def pixel_data @height.times.each_with_object([]) do |y, obj| obj << @width.times.map do |x| color = @image.pixel_color(x + @sx, y + @sy) colormap.find_index { |c| c == color } end end end private def colormap @colormap ||= @image.colors.times.map do |i| Magick::Pixel.from_color(@image.colormap(i)) end end end MovBytePtr = Data.define(:offset, :data) ASM_TEMPLATE = <<~ASM <% sprites.each do |sprite| %> PUBLIC <%= sprite.exported_name %><% end %> .386 .model flat,c .CODE <% sprites.each do |sprite| %> <%= sprite.exported_name %>: push ebp mov ebp, esp push ebx mov eax, [ebp + 8] <% sprite.bytes.each do |byte| %> mov BYTE PTR [eax + <%= byte.offset %>], <%= byte.data %><% end %> pop ebx pop ebp ret <% end %> end ASM C_TEMPLATE = <<~C <% sprites.each do |sprite| %> extern void <%= sprite.function_name %>(byte *); #pragma aux (__cdecl) <%= sprite.function_name %> "<%= sprite.exported_name %>" <% end %> C class AssemblerSprite def initialize( name:, pixel_data:, screen_width:, transparent_color:, offset_x:, offset_y:, prefix: ) @name = name @pixel_data = pixel_data @screen_width = screen_width @transparent_color = transparent_color @offset_x = offset_x @offset_y = offset_y @prefix = prefix end def exported_name "#{function_name}_" end def function_name "#{@prefix}_#{@name}" end def bytes return @bytes if defined?(@bytes) bytes = [] @pixel_data.each_with_index do |row, y| row.each_with_index do |column, x| next if @pixel_data[y][x] == @transparent_color bytes << MovBytePtr.new( data: @pixel_data[y][x], offset: x - @offset_x + (y - @offset_y) * @screen_width ) end end bytes end end Screen = Data.define(:width, :transparent_color) SpriteData = Data.define(:image, :name, :position, :dimensions, :offset, :prefix) screen = Screen.new( width: data["screen"]["width"], transparent_color: data["screen"]["transparentColor"] ) sprite_data = [] data["files"].each do |spritesheet| image = Magick::Image.read(spritesheet["file"]).first spritesheet["sprites"].each do |name, details| sprite_data << SpriteData.new( image:, name:, prefix: spritesheet["prefix"], position: details["position"], dimensions: details["dimensions"], offset: details["offset"] ) end end sprites = sprite_data.map do |sd| sprite = Sprite.new( image: sd.image, sx: sd.position[0], sy: sd.position[1], width: sd.dimensions[0], height: sd.dimensions[1], ) AssemblerSprite.new( name: sd.name, prefix: sd.prefix, pixel_data: sprite.pixel_data, screen_width: screen.width, transparent_color: screen.transparent_color, offset_x: sd.offset[0], offset_y: sd.offset[1], ) end File.open('sprites.asm', 'w') do |fh| fh.puts ERB.new(ASM_TEMPLATE).result_with_hash(sprites:) end File.open('sprites.h', 'w') do |fh| fh.puts ERB.new(C_TEMPLATE).result_with_hash(sprites:) end puts "sprites.{asm,h} written"