class Object

Public Instance Methods

writeGIF(**_kwargs) { |lambda| ... } click to toggle source

TODO do not store first frame – use background color

this would help with add[diff:...] for the first frame
# File lib/simple_gif/size.rb, line 5
def writeGIF **_kwargs
    kwargs = {filename: "temp.gif", background: [255,255,255]}.merge _kwargs
    file = File.open kwargs[:filename], "wb:ASCII-8BIT"

    current = history = width = height = nil
    yield lambda{ |frame|
        width, height = frame.is_a?(Array) ? [frame.first.size, frame.size] : [kwargs[:width], kwargs[:height]]
        history, current = [], Array.new(width * height, kwargs[:background]) unless history
        step = {}
        if frame.is_a? Array
            current = frame.flatten(1).zip(current).map.with_index{ |(color, cur), i|
                step[i] = color if cur != color
                color
            }
        else
            for (i, j), color in frame
                k = i * width + j
                step[k] = current[k] = color if current[k] != color
            end
        end
        history << step unless step.empty?
        # p history.size
    }
ensure
    file.write "GIF89a"

    # Logical Screen Descriptor
    file.write [width, height].pack "SS"
    file.write "\xF7\x00\x00"

    # Global Color Table
    color_table = history.flat_map(&:values).uniq
    raise "sorry, can't have more than 255 colors" if color_table.size > 255
    color_table[255] = [([*0..255].reverse - color_table.map(&:first))[0]] * 3
    color_table.each{ |i| file.write (i || [255,255,255]).pack "CCC" }

    # Comment Extension block
    file.write "\x21\xFE\x1F"
    file.write "SimpleGIF (c) Nakilon@gmail.com"
    file.write "\x00"

    # Application Extension block
    file.write "\x21\xFF\x0B"
    file.write "NETSCAPE2.0"
    file.write "\x03\x01\x00\x00\x00"

    for step in history#.drop 1

        ### Graphic Control Extension
        file.write "\x21\xF9\x04\x01"
        file.write [100/(kwargs[:fps] || 2)].pack "S"
        file.write "\xFF\x00"

        left, right = step.map{ |k,| k % width }.minmax
        top, bottom = step.map{ |k,| k / width }.minmax

        # Image Descriptor
        file.write "\x2C"
        file.write [left, top, right-left+1, bottom-top+1].pack "S*"
        file.write "\x00"

        # Image Data
        file.write "\x08"

        indices = Array.new((right-left+1) * (bottom-top+1), 255)
        step.each{ |k,v| indices[
            k - width*top - left - \
            (left + width - right - 1)*(k/width - top)
        ] = color_table.index v }

        result = []
        table = (0..257).zip
        tree = (0..257).map{[]}
        s = []
        indices.zip do |b| sb = s + b
            next s = sb if sb.inject(tree){ |t,i| t && t[i] }
            result << [table.index(s), (table.size - 1).to_s(2).size]
            table << sb
            sb.inject(tree){ |i,j| i[j] ||= [] }
            s = b
        end
        x = [[256, 9], *result, table.index(s), 257].
            map{ |i, bs| "%0#{bs || result.last.last}b" % i }.
            reverse.join.reverse.
            scan(/.{1,8}/).map{ |i| i.reverse.to_i 2 }

        begin
            chunk = x[0,255]
            file.write [chunk.size, *chunk].pack "C*"
        end while x = x[255..-1]

        file.write "\x00"

    end

    file.write "\x3B"
    file.close

    `open -a "/Applications/Google Chrome.app" '#{kwargs[:filename]}'` \
        if kwargs[:open]
end