Object.prototype.inherit = function() { for (var v in this) { this[v] = this[v]; } }; function hex(number, leading, usePrefix) { if (typeof(usePrefix) === 'undefined') { usePrefix = true; } if (typeof(leading) === 'undefined') { leading = 8; } var string = (number >>> 0).toString(16).toUpperCase(); leading -= string.length; if (leading < 0) return string; return (usePrefix ? '0x' : '') + new Array(leading + 1).join('0') + string; } Serializer = { TAG_INT: 1, TAG_STRING: 2, TAG_STRUCT: 3, TAG_BLOB: 4, TAG_BOOLEAN: 5, TYPE: 'application/octet-stream', pointer: function() { this.index = 0; this.top = 0; this.stack = []; }, pack: function(value) { var object = new DataView(new ArrayBuffer(4)); object.setUint32(0, value, true); return object.buffer; }, pack8: function(value) { var object = new DataView(new ArrayBuffer(1)); object.setUint8(0, value, true); return object.buffer; }, prefix: function(value) { return new Blob([Serializer.pack(value.size || value.length || value.byteLength), value], { type: Serializer.TYPE }); }, serialize: function(stream) { var parts = []; var size = 4; for (i in stream) { if (stream.hasOwnProperty(i)) { var tag; var head = Serializer.prefix(i); var body; switch (typeof(stream[i])) { case 'number': tag = Serializer.TAG_INT; body = Serializer.pack(stream[i]); break; case 'string': tag = Serializer.TAG_STRING; body = Serializer.prefix(stream[i]); break; case 'object': if (stream[i].type == Serializer.TYPE) { tag = Serializer.TAG_BLOB; body = stream[i]; } else { tag = Serializer.TAG_STRUCT; body = Serializer.serialize(stream[i]); } break; case 'boolean': tag = Serializer.TAG_BOOLEAN; body = Serializer.pack8(stream[i]); break; default: console.log(stream[i]); break; } size += 1 + head.size + (body.size || body.byteLength || body.length); parts.push(Serializer.pack8(tag)); parts.push(head); parts.push(body); } } parts.unshift(Serializer.pack(size)); return new Blob(parts); }, deserialize: function(blob, callback) { var reader = new FileReader(); reader.onload = function(data) { callback(Serializer.deserealizeStream(new DataView(data.target.result), new Serializer.pointer)); } reader.readAsArrayBuffer(blob); }, deserealizeStream: function(view, pointer) { pointer.push(); var object = {}; var remaining = view.getUint32(pointer.advance(4), true); while (pointer.mark() < remaining) { var tag = view.getUint8(pointer.advance(1)); var head = pointer.readString(view); var body; switch (tag) { case Serializer.TAG_INT: body = view.getUint32(pointer.advance(4), true); break; case Serializer.TAG_STRING: body = pointer.readString(view); break; case Serializer.TAG_STRUCT: body = Serializer.deserealizeStream(view, pointer); break; case Serializer.TAG_BLOB: var size = view.getUint32(pointer.advance(4), true); body = view.buffer.slice(pointer.advance(size), pointer.advance(0)); break; case Serializer.TAG_BOOLEAN: body = !!view.getUint8(pointer.advance(1)); break; } object[head] = body; } if (pointer.mark() > remaining) { throw "Size of serialized data exceeded"; } pointer.pop(); return object; }, serializePNG: function(blob, base, callback) { var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); var pixels = base.getContext('2d').getImageData(0, 0, base.width, base.height); var transparent = 0; for (var y = 0; y < base.height; ++y) { for (var x = 0; x < base.width; ++x) { if (!pixels.data[(x + y * base.width) * 4 + 3]) { ++transparent; } } } var bytesInCanvas = transparent * 3 + (base.width * base.height - transparent); for (var multiplier = 1; (bytesInCanvas * multiplier * multiplier) < blob.size; ++multiplier); var edges = bytesInCanvas * multiplier * multiplier - blob.size; var padding = Math.ceil(edges / (base.width * multiplier)); canvas.setAttribute('width', base.width * multiplier); canvas.setAttribute('height', base.height * multiplier + padding); var reader = new FileReader(); reader.onload = function(data) { var view = new Uint8Array(data.target.result); var pointer = 0; var pixelPointer = 0; var newPixels = context.createImageData(canvas.width, canvas.height + padding); for (var y = 0; y < canvas.height; ++y) { for (var x = 0; x < canvas.width; ++x) { var oldY = (y / multiplier) | 0; var oldX = (x / multiplier) | 0; if (oldY > base.height || !pixels.data[(oldX + oldY * base.width) * 4 + 3]) { newPixels.data[pixelPointer++] = view[pointer++]; newPixels.data[pixelPointer++] = view[pointer++]; newPixels.data[pixelPointer++] = view[pointer++]; newPixels.data[pixelPointer++] = 0; } else { var byte = view[pointer++]; newPixels.data[pixelPointer++] = pixels.data[(oldX + oldY * base.width) * 4 + 0] | (byte & 7); newPixels.data[pixelPointer++] = pixels.data[(oldX + oldY * base.width) * 4 + 1] | ((byte >> 3) & 7); newPixels.data[pixelPointer++] = pixels.data[(oldX + oldY * base.width) * 4 + 2] | ((byte >> 6) & 7); newPixels.data[pixelPointer++] = pixels.data[(oldX + oldY * base.width) * 4 + 3]; } } } context.putImageData(newPixels, 0, 0); callback(canvas.toDataURL('image/png')); } reader.readAsArrayBuffer(blob); return canvas; }, deserializeCurrentPNG: function(image, callback) { var canvas = document.createElement('canvas'); canvas.setAttribute('height', image.height); canvas.setAttribute('width', image.width); var context = canvas.getContext('2d'); context.drawImage(image, 0, 0); var pixels = context.getImageData(0, 0, canvas.width, canvas.height); var data = []; for (var y = 0; y < canvas.height; ++y) { for (var x = 0; x < canvas.width; ++x) { if (!pixels.data[(x + y * canvas.width) * 4 + 3]) { data.push(pixels.data[(x + y * canvas.width) * 4 + 0]); data.push(pixels.data[(x + y * canvas.width) * 4 + 1]); data.push(pixels.data[(x + y * canvas.width) * 4 + 2]); } else { var byte = 0; byte |= pixels.data[(x + y * canvas.width) * 4 + 0] & 7; byte |= (pixels.data[(x + y * canvas.width) * 4 + 1] & 7) << 3; byte |= (pixels.data[(x + y * canvas.width) * 4 + 2] & 7) << 6; data.push(byte); } } } var newBlob = new Blob(data.map(function (byte) { var array = new Uint8Array(1); array[0] = byte; return array; }), { type: Serializer.TYPE}); Serializer.deserialize(newBlob, callback); }, deserializePNG: function(blob, callback) { var reader = new FileReader(); reader.onload = function(dataImg) { var image = document.createElement('img'); image.setAttribute('src', dataImg.target.result); image.onload = function () { var canvas = document.createElement('canvas'); canvas.setAttribute('height', image.height); canvas.setAttribute('width', image.width); var context = canvas.getContext('2d'); context.drawImage(image, 0, 0); var pixels = context.getImageData(0, 0, canvas.width, canvas.height); var data = []; for (var y = 0; y < canvas.height; ++y) { for (var x = 0; x < canvas.width; ++x) { if (!pixels.data[(x + y * canvas.width) * 4 + 3]) { data.push(pixels.data[(x + y * canvas.width) * 4 + 0]); data.push(pixels.data[(x + y * canvas.width) * 4 + 1]); data.push(pixels.data[(x + y * canvas.width) * 4 + 2]); } else { var byte = 0; byte |= pixels.data[(x + y * canvas.width) * 4 + 0] & 7; byte |= (pixels.data[(x + y * canvas.width) * 4 + 1] & 7) << 3; byte |= (pixels.data[(x + y * canvas.width) * 4 + 2] & 7) << 6; data.push(byte); } } } var newBlob = new Blob(data.map(function (byte) { var array = new Uint8Array(1); array[0] = byte; return array; }), { type: Serializer.TYPE}); Serializer.deserialize(newBlob, callback); } } reader.readAsDataURL(blob); } }; Serializer.pointer.prototype.advance = function(amount) { var index = this.index; this.index += amount; return index; }; Serializer.pointer.prototype.mark = function() { return this.index - this.top; }; Serializer.pointer.prototype.push = function() { this.stack.push(this.top); this.top = this.index; }; Serializer.pointer.prototype.pop = function() { this.top = this.stack.pop(); }; Serializer.pointer.prototype.readString = function(view) { var length = view.getUint32(this.advance(4), true); var bytes = []; for (var i = 0; i < length; ++i) { bytes.push(String.fromCharCode(view.getUint8(this.advance(1)))); } return bytes.join(''); };