Skip to content

Instantly share code, notes, and snippets.

@sczizzo
Created January 15, 2013 05:39
Show Gist options
  • Save sczizzo/4536468 to your computer and use it in GitHub Desktop.
Save sczizzo/4536468 to your computer and use it in GitHub Desktop.
Convert a gzipped LBM file into a GIF animation (not totally working...)
#!/usr/bin/env ruby
#
# Offset | Length | Description
# ========================================
# 0 | 1 | minor width offset
# 1 | 1 | major width offset
# 2 | 1 | minor height offset
# 3 | 1 | major height offset
# 4 | 768 | 256-color pallet
# 772 | 1 | number of cycles (N)
# 773 | 6N | cycle information
# 773+6N | W*H | image information
#
require 'zlib'
require 'rmagick'
include Magick
module CycleImage
# Load raw drawing information
def self.load path
drawing = {
width: 0,
height: 0,
cycles: [],
pallet: [],
data: [],
colors: [],
pixels: []
}
File.open(path, 'rb') do |f|
img = Zlib::GzipReader.new(f)
drawing[:width] = img.getbyte + 256 * img.getbyte
drawing[:height] = img.getbyte + 256 * img.getbyte
256.times do
drawing[:pallet] << {
red: img.getbyte,
green: img.getbyte,
blue: img.getbyte
}
end
img.getbyte.times do
drawing[:cycles] << {
reversed: img.getbyte + 256 * img.getbyte,
rate: img.getbyte + 256 * img.getbyte,
low: img.getbyte,
high: img.getbyte
}
end
(drawing[:width] * drawing[:height]).times do
drawing[:data] << img.getbyte
end
# Identify pixels that can change
drawing[:colors] = Array.new(256, false)
drawing[:cycles].each do |cycle|
next if cycle[:rate] == 0
cycle[:low].upto(cycle[:high]) do |j|
drawing[:colors][j] = true
end
end
n = 0
(drawing[:width] * drawing[:height]).times do |i|
pidx = drawing[:data][i]
if drawing[:colors][pidx]
drawing[:pixels][n] = i
n += 1
end
end
end
return drawing
end
# Create an image from a drawing
def self.from drawing
img = Image.new(drawing[:width], drawing[:height]) do
self.depth = 8
end
# Recreate the drawing pixel-by-pixel
drawing[:height].times do |y|
row = []
drawing[:width].times do |x|
# Look up the pixel color in our pallet
off = y * drawing[:width] + x
pidx = drawing[:data][off]
color = drawing[:pallet][pidx]
# Convert the 8-bit colors to 16-bit
red = 257 * color[:red]
green = 257 * color[:green]
blue = 257 * color[:blue]
row << Pixel.new(red, green, blue, QuantumRange)
# img.store_pixels(x, y, 1, 1, [])
end
img.store_pixels(0, y, drawing[:width], 1, row)
end
return img
end
# Create a new image after cycling over time T
def self.update drawing, t
new_pallet = Marshal.load(Marshal.dump(drawing[:pallet]))
drawing[:cycles].each do |cycle|
low, high, rate = cycle[:low], cycle[:high], cycle[:rate]
next if rate == 0
amt = t * rate
range = high - low + 1
amount = amt.floor % range
amt -= amt.floor
range.times do |j|
c1 = drawing[:pallet][(low + j + 1) % range]
c2 = drawing[:pallet][low + j]
# Lerping by hand...
r = amt * c1[:red] + (1 - amt) * c2[:red]
g = amt * c1[:green] + (1 - amt) * c2[:green]
b = amt * c1[:blue] + (1 - amt) * c2[:blue]
idx = cycle[:low] + (j + amount) % range
new_pallet[idx] = { :red => r.floor, :green => g.floor, :blue => b.floor }
end
end
drawing[:pallet] = new_pallet
return from(drawing)
end
end
# Create frames and animate in a GIF
exit(-1) if ARGV.length != 1
`rm -rf frames && mkdir frames`
drawing = CycleImage.load ARGV.first
t, n, dt, delay = 0, 10, 0.0001, 25
n.times do |i|
CycleImage.update(drawing, t).write("frames/frame-#{i}.jpg")
t += dt
end
anim = ImageList.new(*Dir["frames/*.jpg"]) { self.delay = delay }
anim.write("out.gif")
`rm -rf frames`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment