#!/usr/bin/env ruby require 'rubygems' require 'rmagick' require 'pp' ### # Converts a gif file to c code to enable displaying animations. # # The data format is described in animations.h ### DEBUG = true def compress(data, use_cutoff=true) cutoff = 3 escape = 255 last = data[0] result = [] count = 0 data.each do |x| if x == last count += 1 end if x!=last || count==255 if use_cutoff if count <= cutoff count.times { result << last } else result << escape << count << last end else result << count << last end count = 1 last = x end end if use_cutoff if count < cutoff count.times { result << last } else result << escape << count << last end else result << count << last end return result end unless ARGV[0] puts "Usage:" puts "#{$0} [OPTIONS]" puts "e.g. '#{$0} mario.gif'" puts puts "OPTIONS:" puts " --full-frames Don't use optimiziation; output each frame as full frame." puts puts "Resulting data format is described in animations.h" exit 1 end image_file=ARGV[0] full_frames = ARGV.include?("--full-frames") name=File.basename(image_file, File.extname(image_file)) frames = Magick::ImageList.new(image_file) STDERR.puts "Found #{frames.count} frames." ticks_per_second = frames.ticks_per_second total_x, total_y = frames.first.columns, frames.first.rows STDERR.print "Getting colors..." colors = [] frames.each do |frame| if frame.columns!=total_x || frame.rows!=total_y raise "The gif file has frames with different sizes. Such gifs are not supported at the moment, sorry." end frame.columns.times do |x| frame.rows.times do |y| color = frame.pixel_color(x, y).to_color(Magick::AllCompliance, true, 8, true)[1,8].to_i(16) colors << color end end end colors = colors.uniq STDERR.puts " Found #{colors.count} colors." transparent = colors.select{|c| c & 0xFF == 0} STDERR.puts "#{transparent.count} color(s) being transparent." # color[0] is "keep the color from the previous frame" # color[1] is "background color" colors = ([0x00000012, 0x00000013] + (colors - transparent)).uniq STDERR.puts "Using #{colors.count} colors." raise "Number of colors has to be 255 or less!" if colors.count>255 STDERR.puts p_frame = nil frames_data = [] times = [] frames.each_with_index do |frame, index| data = [] if index==0 # first frame frame.rows.times do |y| frame.columns.times do |x| color = frame.pixel_color(x, y).to_color(Magick::AllCompliance, true, 8, true)[1,8].to_i(16) if transparent.include? color data << 1 else data << colors.index(color) end end end else frame.rows.times do |y| frame.columns.times do |x| color = frame.pixel_color(x, y).to_color(Magick::AllCompliance, true, 8, true)[1,8].to_i(16) p_color = p_frame.pixel_color(x, y).to_color(Magick::AllCompliance, true, 8, true)[1,8].to_i(16) if color==p_color && !full_frames data << 0 elsif transparent.include? color data << 1 else data << colors.index(color) end end end end time = (1000.0 / ticks_per_second * frame.delay).round if frame==0 || data.uniq!=[0] times << time frames_data << data else old_time = times.pop times << old_time + time end p_frame = frame if DEBUG STDERR.puts "Frame ##{index}:" total_y.times do |y| total_x.times do |x| STDERR.print data[x + y*total_x]==0 ? "." : "X" end STDERR.puts end end end data = frames_data.map{|d| compress(d, true)} s=0 d = "PIA\x01__" + [total_x, total_y].pack("C*") + [frames_data.count, colors.count-2].pack("C*") + colors[2..-1].map{|c| [c>>24 & 0xFF, c>>16 & 0xFF, c>>8 & 0xFF]}.flatten.pack("C*") + times.pack("n*") + (data.map{|d| d.count}).pack("n*") + data.flatten.pack("C*") dd = "PIA\x01__" + "XY:" + [total_x, total_y].pack("C*") + "FC,CC:" + [frames_data.count, colors.count-2].pack("C*") + "COLORS:" + colors[2..-1].map{|c| [c>>24 & 0xFF, c>>16 & 0xFF, c>>8 & 0xFF]}.flatten.pack("C*") + "TIME:" + times.pack("n*") + "LENGTHS:" + (data.map{|d| d.count}).pack("n*") + "DATA:" + data.flatten.pack("C*") size = d.length d[4] = (size >> 8 & 0xFF).chr d[5] = (size & 0xFF).chr STDERR.puts STDERR.puts "Space usage:" STDERR.puts " Colors: %6d bytes." % [s1=(colors.count-2) * 3] # colors are 3-bytes, but we have to use uint32_t, which takes up 4 bytes. STDERR.puts " Data: %6d bytes." % [s2=data.flatten.count] STDERR.puts " Delays: %6d bytes." % [s5=times.count * 2 + 1] STDERR.puts " Offsets: %6d bytes." % [s3=data.count * 2] STDERR.puts " TOTAL: %6d bytes." % [s1+s2+s3+s5] STDERR.puts " REALLY: %6d bytes." % d.size STDERR.puts "Original size: %6d bytes." % [s4=File.new(image_file).size] STDERR.puts "Difference: %6d bytes." % [s1+s2+s3+s5 - s4] STDERR.puts # File format # Bytes 0-2: P I A (magic number) # Byte 3: Version (currently 1) # Bytes 4-5: Total file size # Byte 6: width # Byte 7: height # Byte 8: frame_count # Byte 9: color_count # Color data, 3*color_count bytes # Frame times, 2*frame_count bytes # frame offsets, 2*frame_count bytes STDERR.puts "Writing data to #{name}.pia..." File.new("#{name}.pia", "w").write(d) File.new("#{name}.pia.debug", "w").write(dd)