Skip to content

Instantly share code, notes, and snippets.

@rummelonp
Created February 4, 2012 16:25
Show Gist options
  • Save rummelonp/1738790 to your computer and use it in GitHub Desktop.
Save rummelonp/1738790 to your computer and use it in GitHub Desktop.
GIF アニメ分解して Canvas で再生するやつ

GIF アニメ分解して Canvas で再生するやつ

GifVJ
これがクールだったのでどうにかこれを JavaScript で作れないか頑張ってみた(頑張ってる途中)

実験途中経過報告

Burrn!
ランダムで GIF アニメを全画面表示
Enter で再生/停止
Space 2回以上押してスピード調整
停止中は矢印キーまたは j/k で次/前のフレームに進む

データの取り扱い方が上手くいってなくて崩れてるものが多い
追記:直した
透過処理の判定部分がミスってた

ついでなので元の coffee も置いとく

やってること

GIF アニメ分解して Canvas で切り替え

jsgif

GIF アニメの解析は jsgif を使った
jsgif
gif.js がパーサー, html.js がパーサーの呼び出しとプレイヤーの構築をしている
この html.js を参考にしながら作った

その他

GifVJ 作るには大量の画像のパースが必要で
また生のデータが必要で Ajax で取りに行く必要がある
なので同じドメイン上の画像データしか扱えないし
パフォーマンスが出るのかどうかも分からない
サーバーサイドで解析してメタデータを JavaScript に渡すほうが良さそうな気もする

class GifPlayer
constructor: (url) ->
@modal = $('.modal').modal(backdrop: false)
@progressbar = @modal.find('.progress .bar')
@canvas = $('canvas').get(0)
@frames = []
@handler =
hdr: @proxy(@parserHandlers.header)
gce: @proxy(@parserHandlers.graphicControl)
img: @proxy(@parserHandlers.image)
eof: @proxy(@parserHandlers.end)
@load(url)
proxy: (callback) ->
$.proxy(callback, this)
load: (url) ->
self = this
$.ajax
url: url
beforeSend: (req) ->
req.overrideMimeType 'text/plain; charset=x-user-defined'
complete: (req) ->
self.stream = new Stream(req.responseText)
parseGIF(self.stream, self.handler)
progress: ->
percent = Math.round(@stream.pos / @stream.data.length * 100)
@progressbar.width(percent + '%')
parserHandlers:
header: (header) ->
@progress()
@header = header
@canvas.width = @header.width
@canvas.height = @header.height
graphicControl: (gce) ->
@progress()
if @context
@frames.push(@context.getImageData(0, 0, @header.width, @header.height))
@context = null
@transparency = if gce.transparencyGiven then gce.transparencyIndex else null
@disposal_method = gce.disposalMethod
image: (image) ->
@progress()
self = this
unless @context
@context = @canvas.getContext('2d')
color_table = if image.lctFlag then image.lct else @header.gct
image_data = @context.getImageData(image.leftPos, image.topPos, image.width, image.height)
$.each image.pixels, (index, pixel) ->
if self.transparency != pixel
image_data.data[index * 4 + 0] = color_table[pixel][0]
image_data.data[index * 4 + 1] = color_table[pixel][1]
image_data.data[index * 4 + 2] = color_table[pixel][2]
image_data.data[index * 4 + 3] = 255
else if self.disposal_method == 2 || self.disposal_method == 3
image_data.data[i * 4 + 3] = 0
@context.putImageData(image_data, image.leftPos, image.topPos)
end: ->
@progress()
if @context
@frames.push(@context.getImageData(0, 0, @header.width, @header.height))
@modal.hide()
new Player(@frames).play()
class Player
constructor: (frames)->
@canvas = $('canvas').get(0)
@context = @canvas.getContext('2d')
@frames = frames
@index = 0
@playing = false
@delay = 100
@before = null
$(document).keydown(@proxy(@action))
proxy: (callback) ->
$.proxy(callback, this)
set: ->
@context.putImageData(@frames[@index], 0, 0)
step: ->
return unless @playing
@set(@index)
@index += 1
if @index >= @frames.length
@index = 0
setTimeout(@proxy(@step), @delay)
play: ->
@playing = true
@step()
stop: ->
@playing = false
toggle: ->
if @playing
@stop()
else
@play()
next: ->
return if @playing
@index += 1
if @index >= @frames.length
@index = 0
@set()
prev: ->
return if @playing
@index -= 1
if @index < 0
@index = @frames.length - 1
@set()
setDelay: ->
if @before
time = new Date() - @before
if time <= 1000
@delay = time / 8
@before = new Date()
action: (event) ->
switch event.which
when 13
event && event.preventDefault()
@toggle()
when 39, 74
event && event.preventDefault()
@next()
when 37, 75
event && event.preventDefault()
@prev()
when 32
event && event.preventDefault()
@setDelay()
$(document).ready ->
images = ["baaaaa.gif", "dog.gif", "driver.gif", "hoshi.gif", "imamachine.gif", "madohomu.gif", "nuko.gif", "sporty.gif", "circle.gif", "dokan.gif", "facebook.gif", "hutomomo.gif", "kawaii.gif", "mine.gif", "perfume.gif", "tile.gif"]
new GifPlayer('/images/' + images[Math.round(Math.random() * images.length)])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment