From 037a0ae8072217f7821549bbdfe030e73289330d Mon Sep 17 00:00:00 2001 From: Julie Lala Date: Tue, 10 Dec 2013 00:47:36 -0500 Subject: dither stuff --- gif.js | 1690 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1690 insertions(+) create mode 100644 gif.js (limited to 'gif.js') diff --git a/gif.js b/gif.js new file mode 100644 index 0000000..a2c22fa --- /dev/null +++ b/gif.js @@ -0,0 +1,1690 @@ +// gif.js by @timb :) +;(function(e,t,n,r){function i(r){if(!n[r]){if(!t[r]){if(e)return e(r);throw new Error("Cannot find module '"+r+"'")}var s=n[r]={exports:{}};t[r][0](function(e){var n=t[r][1][e];return i(n?n:e)},s,s.exports)}return n[r].exports}for(var s=0;s 1) { // on("msg", f) + var msgMap = {}; + msgMap[arguments[0]] = arguments[1]; + var opts = arguments[2] || {}; + } else { // on({"msg": f, ...}) + var msgMap = arguments[0]; + var opts = arguments[1] || {}; + } + + for (var string in msgMap){ + + var msgs = string.split(" "); + + var fs = msgMap[string]; + if (!Array.isArray(fs)) fs = [fs]; + + for(var i=0, f; f=fs[i]; i++){ + if (!f.uid) f.uid = Uid(); + } + + for(var i=0, msg; msg=msgs[i]; i++){ + + var listeners = (msg.indexOf("*") === -1) ? + chan.listeners : + chan.globListeners; + + // todo: this probably wastes a lot of memory? + // make a copy of the listener, add to it, and replace the listener + // why not just push directly? + // send might be iterating over it... and that will fuck up the iteration + listeners[msg] = (msg in listeners) ? + listeners[msg].concat(fs) : + fs.concat(); + } + } + + return chan; + +}; + + + +/* +off() +off("a:b:c") +off(f) +off("a:b:c", f) +off("a:b:c d:e:f") +off([f, f2]) +off({"a": f, "b": f2}) +*/ +Tube.proto.off = function(){ var chan = this; + + var listeners, i, msgs, msg; + + // off() : delete all listeners. but replace, instead of delete + if (arguments.length === 0) { + chan.listeners = {}; + chan.globListeners = {}; + return chan; + } + + // off("a:b:c d:e:f") + // remove all matching listeners + if (arguments.length === 1 && typeof arguments[0] === "string"){ + // question... will this fuck up send if we delete in the middle of it dispatching? + msgs = arguments[0].split(" "); + + for (i=0; msg=msgs[i]; i++){ + delete chan.listeners[msg]; + delete chan.globListeners[msg]; + } + return chan; + } + + // off(f) or off([f, f2]) + // remove all matching functions + if (typeof arguments[0] === "function" || Array.isArray(arguments[0])) { + var fs = (typeof arguments[0] === "function") ? + [arguments[0]] : + arguments[0]; + // TODO + return chan; + } + + // off("a:b:c", f) or off({"a": f, "b": f2}) + if (arguments.length > 1) { // off("msg", f) + var msgMap = {}; + msgMap[arguments[0]] = arguments[1]; + } else { // off({"msg": f, ...}) + var msgMap = arguments[0]; + } + + for (var string in msgMap){ + msgs = string.split(" "); + + var fs = msgMap[string]; + if (typeof fs === "function") fs = [fs]; + + for(var i=0; msg=msgs[i]; i++){ + + if (msg in chan.listeners) + listeners = chan.listeners; + else if (msg in chan.globListeners) + listeners = chan.globListeners; + else + continue; + + // gotta do this carefully in case we are still iterating through the listener in send + // build a new array and assign it to the property, instead of mutating it. + + // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length) + // console.log(listeners[msg].join(",")); + // console.log(fs.join(",")); + + listeners[msg] = listeners[msg].filter( + function(f){ return fs.indexOf(f) === -1 } + ); + // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length) + + } + } + + return chan; + +}; + + +/* + +c = Tube() +c.on("foo", fn) +c("foo", "bar", []) + +will call fn("bar", [], "foo") + +*/ +Tube.proto.send = function(msgString /*, data... */){ + + // todo: don't do this? + if (!Tube.total[msgString]) Tube.total[msgString] = 0 + Tube.total[msgString]+=1; + + var listener, + listeners = this.listeners, + globListeners = this.globListeners, + //args = Array.prototype.splice.call(arguments, 1), + msgs = tokenize(msgString), + msg, f; + + if (arguments.length) { + var args = Array.prototype.splice.call(arguments, 1); + args.push(msgString); + + } else { + var args = []; + } + + for (var m=0; msg=msgs[m]; m++){ + + var fsToRun = []; + var uidKeyFnValue = {}; + var uidKeyMsgStringValue = {}; + + // note this will die on errors + // todo: implement http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/ + // exact matches + if (listener = listeners[msg]) { + for (var i=0; f=listener[i]; i++){ + // fsToRun.push([f, msg]); + uidKeyFnValue[f.uid] = f; + uidKeyMsgStringValue[f.uid] = msg; + } + } + + // glob matches + var msgSplit = msg.split(":"); + + for (var pattern in globListeners){ + + if (pattern !== "*") { // * always matches + var patternSplit = globcache[pattern] || (globcache[pattern] = pattern.split(":")); + if (!globber(patternSplit, msgSplit)) continue; + } + + listener = globListeners[pattern]; + + for (var i=0; f=listener[i]; i++){ + //f.apply(window, args); // hm possibly pass the actual message to the func + // fsToRun.push([f, msg]); + uidKeyFnValue[f.uid] = f; + uidKeyMsgStringValue[f.uid] = msg; + } + } + + var fns = []; + for (var f in uidKeyFnValue) fns.push(uidKeyFnValue[f]); + + for (var i=0, f; f=fns[i]; i++) + f.apply(f, args); + + } + + return this; + +}; + +module.exports = Tube; +},{"./object/setproto":7,"./string/tokenize":8,"./string/globber":9,"./uid":10,"./nexttick":11}],7:[function(require,module,exports){// TODO: replace all uses of +// setproto(foo, proto) +// with +// foo.__proto__ = proto +// when IE is no longer shit + +var setproto = function(obj, proto){ + if (obj.__proto__) + obj.__proto__ = proto; + else + for (var key in proto) + obj[key] = proto[key]; +}; +module.exports = setproto; +},{}],8:[function(require,module,exports){// trimmed string into array of strings +var tokenize = function(str, splitOn){ + return str + .trim() + .split(splitOn || tokenize.default); +}; +tokenize.default = /\s+/g; + +module.exports = tokenize; +},{}],9:[function(require,module,exports){// can use * to match 0 or more : separated msgs +// globber("*".split(":"), "a:b:c".split(":")) => true +// globber("*:c".split(":"), "a:b:c".split(":")) => true +// globber("a:*".split(":"), "a:b:c".split(":")) => true +// globber("a:*:c".split(":"), "a:b:c".split(":")) => true + +// based on codegolf.stackexchange.com/questions/467/implement-glob-matcher +var globber = function(patterns, strings) { + // console.log("globber called with: " + patterns.join(":"), strings.join(":")) + var first = patterns[0], + rest = patterns.slice(1), + len = strings.length, + matchFound; + + if(first === '*') { + for(var i = 0; i <= len; ++i) { + // console.log("* " + i + " trying " + rest.join(":") + " with " + strings.slice(i).join(":")) + if(globber(rest, strings.slice(i))) return true; + } + return false; + } else { + matchFound = (first === strings[0]); + // console.log ("literal matching " + first + " " + strings[0] + " " + !!matched) + } + + return matchFound && ((!rest.length && !len) || globber(rest, strings.slice(1))); +}; +module.exports = globber; +},{}],10:[function(require,module,exports){var Uid = function(){ + return (Uid.counter++ + ""); +} + +Uid.counter = 1; + +module.exports = Uid; +},{}],11:[function(require,module,exports){// based on https://github.com/timoxley/next-tick/blob/master/index.js + +// postMessage behaves badly on IE8 +if (window.ActiveXObject || !window.postMessage) { + + var nextTick = function(fn) { + setTimeout(fn, 0); + } + +} else { + + // based on setZeroTimeout by David Baron + // - http://dbaron.org/log/20100309-faster-timeouts + var timeouts = [] + , name = 'next-tick-zero-timeout' + + window.addEventListener('message', function(e){ + if (e.source == window && e.data == name) { + if (e.stopPropagation) e.stopPropagation(); + if (timeouts.length) timeouts.shift()(); + } + }, true); + + var nextTick = function(fn){ + timeouts.push(fn); + window.postMessage(name, '*'); + } + +} + +module.exports = nextTick; +},{}],4:[function(require,module,exports){var Benchmark = require('./benchmark'); +var setproto = require('./object/setproto'); +var extend = require('./object/extend'); +var Tube = require('./tube'); + +// usage: Buffer(url || File || ArrayBuffer) +var BufferLoader = function(src, opts){ + + var loader = Tube(); + setproto(loader, BufferLoader.proto); + + if (opts && opts.benchmark) loader.benchmark = opts.benchmark; + + //buf.remember("load error") + // loader.on("load", function(abuf){ setupBuffer(loader, abuf) }); // <-- hm, needs ref to buf + + loader.src = src; + + return loader; +} + +BufferLoader.proto = {}; +extend(BufferLoader.proto, Tube.proto); + +BufferLoader.proto.load = function(){ var loader = this; + var src = loader.src; + if (typeof src === "string") loader.loadFromUrl(src) + else if (src instanceof File) loader.loadFromFile(src); + else if (src instanceof ArrayBuffer) loader("load", src); +} + +BufferLoader.proto.loadFromFile = function(file){ var loader = this; + + var r = new FileReader(); + + r.addEventListener('load', function(e){ + if (loader.benchmark) loader.benchmark.stop("fetch-from-disk"); + loader('load', r.result, e); + // console.log(r.file.name + " 100%: " + r.result.byteLength + " bytes") + }); + + r.addEventListener('error', function(e){ + // console.log(arguments); + loader('error', e, r) + }); + + r.addEventListener('progress', function(e){ + //console.log(r.file.name + " " + (e.loaded / e.total * 100) + "%: " + e.loaded + " bytes") + loader('progress', e) + }); + + if (loader.benchmark) loader.benchmark.start("fetch-from-disk"); + r.readAsArrayBuffer(file); + +}; + + +BufferLoader.proto.loadFromUrl = function(url){ var loader=this; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", url); + xhr.responseType = "arraybuffer"; + + xhr.addEventListener('load', function(e){ + if (loader.benchmark) loader.benchmark.stop("fetch-from-network"); + // console.log(r.file.name + " 100%: " + r.result.byteLength + " bytes") + loader('load', xhr.response, e); + }); + + xhr.addEventListener('error', function(e){ + // console.log(arguments); + loader('error', e, xhr); + }); + + xhr.addEventListener('progress', function(e){ + //console.log(r.file.name + " " + (e.loaded / e.total * 100) + "%: " + e.loaded + " bytes") + loader('progress', e) + }); + + if (loader.benchmark) loader.benchmark.start("fetch-from-network"); + // xhr.open("GET", url); + + xhr.send(); + +}; + +module.exports = BufferLoader; +},{"./benchmark":2,"./object/setproto":7,"./object/extend":12,"./tube":3}],12:[function(require,module,exports){module.exports = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; +},{}],5:[function(require,module,exports){(function(){var blockSigs = require('./spec').blockSigs; +var extSigs = require('./spec').extSigs; +var palette = require('./palette'); +var makeCurrentFrame = require('./animate').makeCurrentFrame; + +var BinarySpecReader = require('../../binaryspec'); +var spec = require('./spec').spec; +var specReader = BinarySpecReader(spec); + +var decode = function(gif, opts){ + decodeNextPart(gif); +}; + + +// this gets called when more binary data is ready to process +// it gets set as BufferedReader's onload function +/* +var moreDataInBufferEvent = function(gif){ + if (gif.waitingForFileBuffer) decodeNextPart(gif); +}; +*/ + +/* +loads the next part of the gif, eventually calling the gif's onload function when every part has been loaded + +this function gets called when a previous part has finished loading, +and when there's more data in the buffered reader + +if the next part has a fixed size according to spec, we know whether we have to wait on the buffer to fill or not +if the next part has a variable size, call the howToDecode function which will try to load it +*/ +var decodeNextPart = function(gif, nextPart){ + //gif.waitingForFileBuffer = false; + + nextPart = nextPart || "header"; + var buf = gif.buf; + + while (nextPart !== "done" && nextPart !== "error"){ + + if (nextPart in howToDecode && typeof howToDecode[nextPart] === "function") { // dont know size + nextPart = howToDecode[nextPart](gif); + } else { // we know exact size of next part + + var partSize = specReader.parts[nextPart].byteSize; + + if (buf.abuf.byteLength < partSize + buf.cursor){ + gif.waitingForFileBuffer = true; + return; + } else { + var fields = specReader.decodeBinaryFieldsToJSON(nextPart, buf.cursor, buf); + buf.cursor += specReader.parts[nextPart].byteSize; + nextPart = handleLoadedPart[nextPart](gif, fields); + } + } + // todo: maybe do this when and return unknown from dataBlock read + // if (nextPart === "unknown") nextPart = "dataBlock" + + } + + if (nextPart === "done"){ + // create palette from all frames' palettes + + if (gif.benchmark) gif.benchmark.start("palette"); + gif.paletteTotal = palette.create(gif); + if (gif.benchmark) gif.benchmark.stop("palette"); + + // todo: move elsewhere + makeCurrentFrame.bind(gif)(); +// GIF.decode.doneDecodingCleanup(gif); + //gif.tube("progress", "done"); + gif.decoded = true; + gif.tube("decoded"); + return; + } + +}; + + +var howToDecode = { + "globalPalette": function(gif){ var buf = gif.buf; + var paletteSize = gif.paletteSize * 3; // r,g,b bytes + + if (buf.abuf.byteLength < buf.cursor + paletteSize) { + gif.waitingForFileBuffer = true; + return; + } + + //gif.palette = GIF.palette.binary2rgba(gif.reader.buffer.slice(gif.cursor, gif.cursor + paletteSize)); + gif.palette = palette.binary2rgba(new Uint8Array(buf.abuf, buf.cursor, paletteSize)); + buf.cursor += paletteSize; + + return "dataBlock"; + }, + "localPalette": function(gif){ var buf = gif.buf; + var paletteSize = gif.frames[gif.frames.length - 1].paletteSize * 3; // r,g,b bytes + + if (buf.abuf.byteLength < buf.cursor + paletteSize) { + gif.waitingForFileBuffer = true; + return; + } + + gif.frames[gif.frames.length - 1].palette = palette.binary2rgba(new Uint8Array(buf.abuf, buf.cursor, paletteSize)); + buf.cursor += paletteSize; + return "imageData"; + }, + "dataBlock": function(gif){ var buf = gif.buf; + + if (buf.abuf.byteLength < buf.cursor + 1) { + gif.waitingForFileBuffer = true; + return; + } + + var blockType = "unknown", + extType = "unknown", + nextPart; + + var blockSig = buf.u8[buf.cursor]; + + if (blockSig in blockSigs) { + blockType = blockSigs[blockSig]; + } else { + //console.log("unknown data block type ("+ Number(blockSig).toString(16) +")!"); + nextPart = "done"; + } + + if (blockType === "extension"){ // we need to determine what kind of extension the block is + // need next two bytes to find out what kind of extension the data block is + if (buf.abuf.byteLength < buf.cursor + 2) { + gif.waitingForFileBuffer = true; + return; + console.log("ran out of buffer") + } + var extSig = buf.u8[buf.cursor+1]; + if (extSig in extSigs) { + extType = extSigs[extSig]; + nextPart = extType; + } else { + //console.log("unknown extension type ("+ Number(extSig).toString(16) +")") + nextPart = "done"; + } + } else { + if (blockType === "unknown") { blockType = "dataBlock"; buf.cursor += 1 } + nextPart = blockType; + } + + return nextPart; + + }, + "trailer": function(gif){ + return "done"; + } +}; + +var handleLoadedPart = { + "header": function(gif, fields){ + + if (fields.signature != "GIF"){ + gif.tube("error", "file doesn't seem to be a gif (file signature: '"+fields.signature+"')"); + return "error"; + } + + // gif.version = fields.version + + return "screenDesc"; + }, + "screenDesc": function(gif, fields){ + for (var field in fields) gif[field] = fields[field]; + // todo: make nicer + // this should be a u3 not this bit[3] bit-shifted horseshit + gif.paletteSize = (gif.paletteSize[0] << 2) + (gif.paletteSize[1] << 1) + (gif.paletteSize[2]); + gif.paletteSize = Math.pow(2, gif.paletteSize + 1) + + if (gif.paletteExists) return "globalPalette"; + else return "dataBlock"; + }, + "imageDesc": function(gif, fields){ + // make a blank image frame if none exists or the last frame already has image data + // we don't know if a blank frame has already been created because a graphic control block might + // have come before this block and made one + if (!gif.frames.length || ("w" in gif.frames[gif.frames.length - 1])) + gif.frames.push({}) + + var frame = gif.frames[gif.frames.length - 1] + + for (var field in fields) frame[field] = fields[field]; + frame.paletteSize = (frame.paletteSize[0] << 2) + (frame.paletteSize[1] << 1) + (frame.paletteSize[2]); + frame.paletteSize = Math.pow(2, frame.paletteSize + 1) + + if (frame.paletteExists) return "localPalette"; + else return "imageData"; + }, + // if sublocks was able to read: + // increase cursor from subblocks + // increase cursor from fields + // else + // set waiting + // subtract 2 from cursor (for the 2 that was read to get the data block) + // subtract part.bytesize from cursor (not needed in this case?) + "applicationExtension": function(gif, fields){ + var blockinfo = readSubBlocks(gif); + if (blockinfo === false) { + gif.waitingForFileBuffer = true + gif.buf.cursor -= specReader.parts.applicationExtension.byteSize + return; + } else { + var extension = {"data": blockinfo}; + for(var field in fields) + extension[field] = fields[field] + gif.extensions.push(extension) + return "dataBlock"; + } + }, + "comment": function(gif, fields){ + var blockinfo = readSubBlocks(gif); + if (blockinfo === false) { + gif.waitingForFileBuffer = true; + gif.buf.cursor -= specReader.parts.comment.byteSize; + return; + } else { + var extension = {"comment": blockinfo}; + for(var field in fields) + extension[field] = fields[field]; + gif.extensions.push(extension); + return "dataBlock"; + } + }, + "plainText": function(gif, fields){ + var blockinfo = readSubBlocks(gif); + if (blockinfo === false) { + gif.waitingForFileBuffer = true; + gif.buf.cursor -= specReader.parts.plainText.byteSize; + return; + } else { + var extension = {"plainText": blockinfo}; + for(var field in fields) + extension[field] = fields[field]; + gif.extensions.push(extension); + return "dataBlock"; + } + }, + "graphicControl": function(gif, fields){ + var dm = (fields.disposalMethod[0] << 2) + (fields.disposalMethod[1] << 1) + (fields.disposalMethod[2]); + gif.frames.push({"delay": fields.delay, + "transparentIndex": fields.transparentColor ? fields.transparentIndex : -1, + "disposalMethod": dm}) + return "dataBlock"; + }, + "imageData": function(gif, fields){ + var blockinfo = readSubBlocks(gif); + if (blockinfo === false) { + gif.waitingForFileBuffer = true + gif.buf.cursor -= specReader.parts.imageData.byteSize + console.log("fucked") + return; + } else { + +// TODO: ENABLE THIS! +// gif.tube("progress", "found " + gif.frames.length + " frames"); + + var frame = gif.frames[gif.frames.length - 1]; + //var palette = ("palette" in frame) ? frame.palette : gif.palette // local palette otherwise global palette + frame.lzwCodeSize = fields.lzwCodeSize; + frame.blockinfo = blockinfo; +// var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1 + return "dataBlock"; + } + + } +}; + +// read subblocks out of a gif's buffer... +// reads block sizes and returns an object with a start cursor and an array of block ends +// returns false if there's not enough data +var readSubBlocks = function(gif){ + + if (gif.benchmark) gif.benchmark.start("read-subblocks"); + + var blockEnds = []; + + var buf = gif.buf, + u8 = buf.u8, + byteLength = u8.byteLength; + + // gif.cursor is for whole file... pos is a cursor for just this blob + var startBlockCursor = buf.cursor; + var pos = buf.cursor; + startBlockCursor += 1; + var byteSize = 0; + var outOfData = false + + // only actually advance cursor if we can read in all the sub blocks from the buffer + var cursorTemp = 0; + + while(!outOfData){ + // get block size + if (byteLength < pos + 1) { outOfData = true; break;} + byteSize = u8[pos]; + pos += 1; + + // a sub block with size 0 indicates end of sub blocks + if (byteSize === 0) { cursorTemp += 1; break;} + + // read block + if (byteLength < pos + byteSize) { outOfData = true; break;} + blockEnds.push(pos + byteSize); + + pos += byteSize + cursorTemp += byteSize + 1 + // gif.subBlocksRead += 1 + } + + // TODO? CLEAN UP! + if (outOfData) { + // gif.bufferMisses += 1 + // gif.benchmark.wasted += (Date.now() - start) / 1000 + console.log("out of data") + return false + + } else { // end of sub blocks happened + buf.cursor += cursorTemp + //if ("onprogress" in gif) gif.onprogress(gif, Math.floor(gif.cursor / gif.file.size * 100)) +// gif.benchmark.subblocks += (Date.now() - start) / 1000; + if (gif.benchmark) gif.benchmark.stop("read-subblocks"); + return {start: startBlockCursor, blockEnds: blockEnds}; + + } +}; + +module.exports = decode; +})() +},{"./spec":13,"./palette":14,"./animate":15,"../../binaryspec":16}],13:[function(require,module,exports){var blockSigs = { + 0x21: "extension", + 0x2c: "imageDesc", + 0x3b: "trailer" +}; + +exports.blockSigs = blockSigs; + +var extSigs = { + 0xf9: "graphicControl", + 0xfe: "comment", + 0x01: "plainText", + 0xff: "applicationExtension" +}; + +exports.extSigs = extSigs; + +/* +GIF.getGeneralBlockType = function(sig){ + if (sig == 0x3b) + return "trailer" + else if (sig < 0x7F) + return "graphic rendering block" + else if (sig < 0xF9) + return "control block" + else return "special purpose block" +} + +*/ + +var spec = { + "header": [ + "str[3] signature", + "str[3] version" + ], + "screenDesc": [ + "u16 w", + "u16 h", + "bit paletteExists", + "bit[3] resolution ignore", + "bit sortFlag ignore", + "bit[3] paletteSize", + "u8 bgColorIndex", + "u8 aspectRatio ignore" + ], + "imageDesc": [ + "u8 sig ignore", + "u16 x", + "u16 y", + "u16 w", + "u16 h", + "bit paletteExists", + "bit interlaced", + "bit sortFlag", + "bit[2] reserved ignore", + "bit[3] paletteSize" + ], + "applicationExtension": [ + "u8 sig ignore", + "u8 extSig ignore", + "u8 blockSize ignore", + "str[8] identifier", + "str[3] authCode ignore" + ], + "graphicControl": [ + "u8 sig ignore", + "u8 extSig ignore", + "u8 blockSize ignore", + "bit[3] reserved ignore", + "bit[3] disposalMethod", + "bit userInput ignore", + "bit transparentColor", + "u16 delay", + "u8 transparentIndex", + "u8 blockTerminator ignore" + ], + "comment": [ + "u8 sig ignore", + "u8 extSig ignore" + ], + "plainText": [ + "u8 sig ignore", + "u8 extSig ignore", + "u8 blockSize", + "u16 textGridLeft", + "u16 textGridTop", + "u16 textGridWidth", + "u16 textGridHeight", + "u8 charCellWidth", + "u8 charCellHeight", + "u8 fgColorIndex", + "u8 bgColorIndex" + ], + "imageData": [ + "u8 lzwCodeSize" + ] +}; + +exports.spec = spec; +},{}],14:[function(require,module,exports){(function(){var rgba2css = require('../../color/rgba2css'); +var create2d = require('../../create/2d'); +var createImageData = require('../../create/imagedata'); + +var palette = {}; + +// flat typed array of r g b a values from binary GIF palette +palette.binary2rgba = function(abuf /*, transparentIndex */){ + var table = new Uint8Array(abuf.byteLength/3*4); + var counter = 0 + for(var i = 0, length = abuf.byteLength/3*4; i 0) ? frame.delay * 10 : defaultDelay; + totalDelay += delay; + delays.push(totalDelay); + } + + this.currentFrame = makeCurrentFrameFunction(delays); +}; + +var makeCurrentFrameFunction = function(delays){ + var totalTime = delays[delays.length - 1]; + return function(timestamp){ + var r = (timestamp || Date.now()) % totalTime; + for(var i=0; i 1); + // char(2) is 16 bits. uint(16) is 16 bits + var bitSize = bitSizes[fieldType] * fieldLength; + + parsedSpec.fields.push({"name": instruction[1], + "type": fieldType, + "ignore": ignore, + "bitSize": bitSize, + "isArray": isArray}); + size += bitSize; + } + + parsedSpec.bitSize = size; + parsedSpec.byteSize = size / 8; + + return parsedSpec; +}; + +// decodes a chunk according to data types in gif.spec.js +// todo: rewrite the binary decoding stuff to not be so shit +BinarySpecReader.proto.decodeBinaryFieldsToJSON = function(partName, cursor, buf){ var reader = this; + + var part = reader.parts[partName]; + + var fields = {}, numFields = part.fields.length, bitPos = 0; + + for(var i = 0; i < numFields; i++){ + + var field = part.fields[i]; + if (!field.ignore) { + var bitOffset = bitPos % 8; + var decodeByteStart = Math.floor((bitPos - bitOffset) / 8); + var decodeByteEnd = decodeByteStart + Math.ceil(field.bitSize / 8); + + switch(field.type){ + case "u8": + fields[field.name] = buf.u8[cursor + decodeByteStart]; break; + case "i8": + fields[field.name] = buf.dv.getInt8(cursor + decodeByteStart); break; + case "u16": + fields[field.name] = buf.dv.getUint16(cursor + decodeByteStart, true); break; + case "i16": + fields[field.name] = buf.dv.getInt16(cursor + decodeByteStart, true); break; + case "u32": + fields[field.name] = buf.dv.getUint32(cursor + decodeByteStart, true); break; + case "i32": + fields[field.name] = buf.dv.getInt32(cursor + decodeByteStart, true); break; + case "str": + fields[field.name] = abuf2str(buf.abuf, cursor + decodeByteStart, field.bitSize >> 3); break; + case "bit": + if (!field.isArray) { + fields[field.name] = new BitView(buf.abuf, cursor + decodeByteStart).getBit(bitOffset); + } else { + var bv = new BitView(buf.abuf, cursor + decodeByteStart); + var bits = []; + for (var bb=bitOffset; bb> 3]; + var off = idx & 0x7; + return (v >> (7-off)) & 1; +}; + +module.exports = BitView; +},{}],6:[function(require,module,exports){var create2d = require('../../create/2d'); +var createImageData = require('../../create/imagedata'); +//import queueOrNextTick from 'lib/nexttick4'; +var nextTick = require('../../nexttick'); + +var lzwImageData = require('./decode-lzw'); + +/* + this has two rendering types... canvas and webgl + both build gif image frames from binary data that was decoded when the gif loaded + + canvas method will build full-frame canvas objects and place them into each frame of the gif. + eg, render.canvas(gif) will create gif.frames[0].ctx and so on + + webgl method builds imagedata textures and tries to pack multiple frames efficiently into channels if it can. + + in gifs, each frame stored might just be a rectangle that changed from the previous frame. + these are referred to as "raw frames" in this code +*/ + +var render = function(gif, config){ + + config = config || {}; + + var bench = gif.benchmark || false; + + var frameNum = config.frameNum || 0; + + if (frameNum === 0){ // preallocate all frames + for (var i=0; i= gif.frames.length) { // done making frames + //gif.tube("progress", "done") + gif.rendered = true; + gif.tube("rendered"); + return + } + + var frame = gif.frames[frameNum]; + var pixeldata = gif.buf.pixeldata; + + // gif.percentLoaded = frameNum / gif.frames.length + //gif.tube("decompressing frame " + (frameNum+1)) + + + + // lzw + if (bench) bench.start("decompress-lzw"); + // var pixeldata = new Uint8Array(frame.w * frame.h); + //pixeldata.area = frame.w * frame.h; + lzwImageData(frame.blockinfo, gif.buf.u8, frame.lzwCodeSize, frame.w, frame.h, pixeldata); + if (bench) bench.stop("decompress-lzw"); + + // deinterlace + if (frame.interlaced) { + if (bench) bench.start("deinterlace"); + pixeldata = deinterlacePixels(pixeldata, frame.w, frame.h) + if (bench) bench.stop("deinterlace"); + } + + // canvas-ize + if (bench) bench.start("pixeldata-to-canvas"); + makeFullFrame(pixeldata, gif, frameNum); + if (bench) bench.stop("pixeldata-to-canvas"); + + // todo: queue this better + var func = render.bind(undefined, gif, {"frameNum": frameNum+1}); + // queueOrNextTick(func); + nextTick(func); + // setZeroTimeout(func); + //setTimeout(func, 1) // otherwise progress won't show in chrome + +}; + +var makeFullFrame = function(pixeldata, gif, frameNum){ + + var frame = gif.frames[frameNum], + ctx = frame.ctx; + + if (frameNum === 0){ // don't need previous frame info to do disposal if it's the first frame + ctx.putImageData(pixelDataToImageData(pixeldata, gif, frame), frame.x, frame.y, + 0,0,frame.w,frame.h); + return; + } + + var prevFrameNum = frameNum-1, + prevFrame = gif.frames[prevFrameNum], + prevCanvas = prevFrame.ctx.canvas, + rawCtx; + + // disposal method is 0 (unspecified) or 1 (do not dispose) + // do nothing, paste new frame image over old one + if (prevFrame.disposalMethod === 0 || prevFrame.disposalMethod === 1){ + rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata); + ctx.drawImage(prevCanvas, 0, 0) + ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x,frame.y,frame.w,frame.h) + } + + // disposal method is 2 (restore to background color) + // but everyone just restores to transparency + // see notes on http://www.imagemagick.org/Usage/anim_basics/#background + if (prevFrame.disposalMethod === 2){ + // fast path... whole frame cleared + if (prevFrame.x === 0 && prevFrame.y === 0 && prevFrame.w === gif.w && prevFrame.h === gif.h) { + // var rawContext = makeRawFrameAsContext(gif, frameNum) + // frame.context.drawImage(rawContext.canvas, frame.x, frame.y); + ctx.putImageData(makeRawFrameAsImageData(gif, frameNum, pixeldata), frame.x, frame.y, + 0,0,frame.w,frame.h); + } else { // draw the edges of the previous frame and then draw the current frame overtop + /* + .__________. + |__________| + | | | | + | | | | + |_|______|_| + |__________| + */ + //top + if (prevFrame.y > 0) + ctx.drawImage(prevCanvas, 0,0, gif.w,prevFrame.y, + 0,0, gif.w,prevFrame.y); + //left + if (prevFrame.x > 0) + ctx.drawImage(prevCanvas, 0,prevFrame.y, prevFrame.x,prevFrame.h, + 0,prevFrame.y, prevFrame.x,prevFrame.h); + // right + if (prevFrame.x+prevFrame.w < gif.w) + ctx.drawImage(prevCanvas, prevFrame.x+prevFrame.w, prevFrame.y, (gif.w-prevFrame.x-prevFrame.w),prevFrame.h, + prevFrame.x+prevFrame.w, prevFrame.y, (gif.w-prevFrame.x-prevFrame.w),prevFrame.h); + // bottom + if (prevFrame.y+prevFrame.h < gif.h) + ctx.drawImage(prevCanvas, 0,prevFrame.y+prevFrame.h, gif.w,(gif.h-prevFrame.y-prevFrame.h), + 0,prevFrame.y+prevFrame.h, gif.w,(gif.h-prevFrame.y-prevFrame.h)) + + rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata); + ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x,frame.y,frame.w,frame.h); + + } + } + + // disposal method is 3 (restore to previous) + if (prevFrame.disposalMethod === 3){ + // look for last previous frame that doesn't have "previous" disposal method + while(prevFrameNum > 0 && gif.frames[prevFrameNum].disposalMethod === 3) prevFrameNum -= 1; + prevFrame = gif.frames[prevFrameNum] + // console.log(prevFrameNum) + if (prevFrame.disposalMethod != 3) ctx.drawImage(prevFrame.ctx.canvas, 0, 0) + rawCtx = makeRawFrameAsContext(gif, frameNum, pixeldata); + ctx.drawImage(rawCtx.canvas, 0,0,frame.w,frame.h, frame.x, frame.y,frame.w,frame.h) + } + +} + +var makeRawFrameAsContext = function(gif, frameNum, pixeldata){ + // cache a context to reuse + if (makeRawFrameAsContext.ctx && + makeRawFrameAsContext.ctx.canvas.width === gif.w && + makeRawFrameAsContext.ctx.canvas.height === gif.h) { + var ctx = makeRawFrameAsContext.ctx; + } else { + var ctx = makeRawFrameAsContext.ctx = create2d(gif.w, gif.h); + } + + var frame = gif.frames[frameNum]; + pixeldata = pixeldata || frame.pixelData; + var palette = ("palette" in frame) ? frame.palette : gif.palette; + var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1; + + if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0; + + var rawImageData = pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex); + + ctx.putImageData(rawImageData, 0, 0, 0,0,frame.w,frame.h) + + return ctx + + // return imageData2contextDirty(rawImageData, 0,0,frame.w,frame.h); +}; + +var makeRawFrameAsImageData = function(gif, frameNum, pixeldata){ + var frame = gif.frames[frameNum]; + pixeldata = pixeldata || frame.pixelData; + var palette = ("palette" in frame) ? frame.palette : gif.palette; + var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1; + + if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0; + + return pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex) +}; + +var pixelDataToImageData = function(pixeldata, gif, frame){ + // var frame = gif.frames[frameNum]; + var palette = ("palette" in frame) ? frame.palette : gif.palette; + var transparentIndex = ("transparentIndex" in frame) ? frame.transparentIndex : -1; + + if (transparentIndex > -1) palette[(transparentIndex*4)+3] = 0; + + return pixelData2imageData(gif, palette, pixeldata, frame.w, frame.h, transparentIndex) +}; + + +var imageData2context = function(imageData){ + var ctx = create2d(imageData.width, imageData.height) + ctx.putImageData(imageData, 0, 0) + return ctx +}; +var imageData2contextDirty = function(imageData, dx,dy,dw,dh){ + var ctx = create2d(imageData.width, imageData.height) + ctx.putImageData(imageData, 0, 0, dx,dy,dw,dh) + return ctx +}; + +var pixelData2imageData = function(gif, palette, pixeldata, w, h, transparentIndex){ + if (pixelData2imageData.imagedata && + pixelData2imageData.imagedata.width === gif.w && + pixelData2imageData.imagedata.height === gif.h) { + var imagedata = pixelData2imageData.imagedata; + } else { + var imagedata = pixelData2imageData.imagedata = createImageData(gif.w, gif.h); + } + + var data = imagedata.data + // var i = pixeldata.length; + var i = 0; + + for (var y=0; y>= code_size; + bits -= code_size; + + if (code > available) { + console.log(":("); + // console.log(num2bin(code)); + // break; + } // if we get here something bad happened ;( + if (code === end_of_information) { console.log("fuck"); break}; + if (code === clear) { + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = NullCode; + continue; + } + if (old_code === NullCode){ + pixelStack[top++] = suffix[code]; + old_code = code; + first = code; + continue; + } + in_code = code; + if (code === available){ + pixelStack[top++] = first; + code = old_code; + } + while (code > clear){ + pixelStack[top++] = suffix[code]; + code = prefix[code]; + } + first = (suffix[code]) // & 0xff; + // ^^ timb: not needed? + // Add a new string to the string table, + if (available >= MaxStack) { /*console.log("maxstack!");*/ /*break;*/ } + pixelStack[top++] = first; + prefix[available] = old_code; + suffix[available] = first; + available++; + /* why does == have higher precedence than & ? + i just looked it up and i think it is because javascript + has basically the same precedence as C and + "once upon a time, C didn't have the logical operators && and ||, + and the bitwise operators & and | did double duty." + */ + if ((available & code_mask) === 0 && available < MaxStack){ + code_size++; + code_mask += available; + } + old_code = in_code; + } + + top--; + if (paletteRemap) + pixels[pi++] = paletteRemap[pixelStack[top]]; + else + pixels[pi++] = pixelStack[top]; + i++; + } + + // not needed: typed arrays init'd to 0 already + // for (i = pi, len=pixels.length; i < len; i++) + // pixels[i] = 0; // clear missing pixels + + + return pixels +}; + +var MaxStack = 4096; +// lzwImageData.prefix = new Uint16Array(MaxStack*2) +// lzwImageData.suffix = new Uint8Array(MaxStack) + +module.exports = lzwImageData; +},{}]},{},[1]); -- cgit v1.2.3-70-g09d2