208 lines
5.1 KiB
Ruby
Executable File
208 lines
5.1 KiB
Ruby
Executable File
#!/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} <gif_file> [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)
|