gif.js
Version:
JavaScript GIF encoding library
210 lines (171 loc) • 5.89 kB
text/coffeescript
{EventEmitter} = require 'events'
browser = require './browser.coffee'
class GIF extends EventEmitter
defaults =
workerScript: 'gif.worker.js'
workers: 2
repeat: 0 # repeat forever, -1 = repeat once
background: '#fff'
quality: 10 # pixel sample interval, lower is better
width: null # size derermined from first frame if possible
height: null
transparent: null
debug: false
dither: false # see GIFEncoder.js for dithering options
frameDefaults =
delay: 500 # ms
copy: false
constructor: (options) ->
= false
= {}
= []
= []
= []
options
for key, value of defaults
[key] ?= value
setOption: (key, value) ->
[key] = value
if ? and key in ['width', 'height']
[key] = value
setOptions: (options) ->
key, value for own key, value of options
addFrame: (image, options={}) ->
frame = {}
frame.transparent = .transparent
for key of frameDefaults
frame[key] = options[key] or frameDefaults[key]
# use the images width and height for options unless already set
'width', image.width unless .width?
'height', image.height unless .height?
if ImageData? and image instanceof ImageData
frame.data = image.data
else if (CanvasRenderingContext2D? and image instanceof CanvasRenderingContext2D) or (WebGLRenderingContext? and image instanceof WebGLRenderingContext)
if options.copy
frame.data = image
else
frame.context = image
else if image.childNodes?
if options.copy
frame.data = image
else
frame.image = image
else
throw new Error 'Invalid image'
.push frame
render: ->
throw new Error 'Already running' if
if not .width? or not .height?
throw new Error 'Width and height must be set prior to rendering'
= true
= 0
= 0
= (null for i in [0... .length])
numWorkers =
# we need to wait for the palette
if .globalPalette == true
else
for i in [0...numWorkers]
'start'
'progress', 0
abort: ->
loop
worker = .shift()
break unless worker?
'killing active worker'
worker.terminate()
= false
'abort'
# private
spawnWorkers: ->
numWorkers = Math.min( .workers, .length)
[ .length...numWorkers].forEach (i) =>
"spawning worker #{ i }"
worker = new Worker .workerScript
worker.onmessage = (event) =>
.splice .indexOf(worker), 1
.push worker
event.data
.push worker
return numWorkers
frameFinished: (frame) ->
"frame #{ frame.index } finished - #{ @activeWorkers.length } active"
++
'progress', / .length
[frame.index] = frame
# remember calculated palette, spawn the rest of the workers
if .globalPalette == true
.globalPalette = frame.globalPalette
'global palette analyzed'
for i in [1... .length] if .length > 2
if null in
else
finishRendering: ->
len = 0
for frame in
len += (frame.data.length - 1) * frame.pageSize + frame.cursor
len += frame.pageSize - frame.cursor
"rendering finished - filesize #{ Math.round(len / 1000) }kb"
data = new Uint8Array len
offset = 0
for frame in
for page, i in frame.data
data.set page, offset
if i is frame.data.length - 1
offset += frame.cursor
else
offset += frame.pageSize
image = new Blob [data],
type: 'image/gif'
'finished', image, data
renderNextFrame: ->
throw new Error 'No free workers' if .length is 0
return if >= .length # no new frame to render
frame = [ ++]
worker = .shift()
task = frame
"starting frame #{ task.index + 1 } of #{ @frames.length }"
.push worker
worker.postMessage task#, [task.data.buffer]
getContextData: (ctx) ->
return ctx.getImageData(0, 0, .width, .height).data
getImageData: (image) ->
if not ?
= document.createElement 'canvas'
.width = .width
.height = .height
ctx = .getContext '2d'
ctx.setFill = .background
ctx.fillRect 0, 0, .width, .height
ctx.drawImage image, 0, 0
return ctx
getTask: (frame) ->
index = .indexOf frame
task =
index: index
last: index is ( .length - 1)
delay: frame.delay
transparent: frame.transparent
width: .width
height: .height
quality: .quality
dither: .dither
globalPalette: .globalPalette
repeat: .repeat
canTransfer: (browser.name is 'chrome')
if frame.data?
task.data = frame.data
else if frame.context?
task.data = frame.context
else if frame.image?
task.data = frame.image
else
throw new Error 'Invalid frame'
return task
log: (args...) ->
return unless .debug
console.log args...
module.exports = GIF