Created
November 18, 2010 05:56
-
-
Save bga/704678 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
# Deflate compression using Canvas 2d API | |
Canvas API provides HTMLCanvasElement#toDataURL, which allows serialize canvas image to png image. I do not know about result png format (how many bits per pixel and there is alpha channel or not, may be palette or grayscale image). WHATWG spec also do not points this ie its UA dependent. Anyway basic rules: | |
+ browsers do not supports images with width or height > 20 000 px => we use square image instead linear(1*X or X*1) | |
+ if alpha component of canvas data eq 0 => browser reset red, green, blue to 0 too even if globalCompositeOperation = 'copy' | |
+ even if globalCompositeOperation = 'copy', globalAlpha = 1.0, dest alpha of pixel eq 255 - browser blends src image data when you perform putImageData and original red, green, blue components corrupts due floating point errors. For example we have one pixel [1,129,130,131], let see putImageData(this pixel); getImageData() -> [1,128,130,131]; drawImage(toDataUrl()); ; getImageData -> [1,126,128,131] => we can not use alpha components to store data => store 3 original bytes in 4 image data bytes | |
Also there are some issues with Opera: | |
+ opera does not supports CanvasRenderingContext2D#createImageData => CanvasRenderingContext2D#use getImageData | |
+ opera does not fires Image#onload or Image#onerror when Image#src is dataURI => check image periodically using setTimeout(_, 250), check image width - its must not eq 0, check our image data "signature" - alpha component of pixel (0, 0) eq 255 | |
What about compression (without base64 post encoding)? For example in Chrome9: | |
+ 1e6/256*[0..255] = 19262.96% | |
+ 1e6*[0] = 37501.05% | |
+ 1e6*pseudo_random(0..255) = -13.99% | |
Compatibility - all UA which supports Canvas API, dataURI and HTMLCanvasElement#toDataURL ie: | |
+ lastest webkit | |
+ lastest ff | |
+ lastest opera | |
*/ | |
// (c) new BSD license | |
var Deflate = (function($G) | |
{ | |
var _compress = function(data) | |
{ | |
var c = document.createElement('canvas'); | |
var dataLen = 2 + Math.ceil(data.length/3); | |
var w = Math.ceil(Math.sqrt(dataLen)); | |
c.width = w; | |
c.height = w; | |
var ctx = c.getContext('2d'); | |
ctx.globalCompositeOperation = 'copy'; | |
var img = ctx.createImageData && ctx.createImageData(w, w) || | |
ctx.getImageData(0, 0, w, w) | |
; | |
var imgData = img.data; | |
var j = -1; | |
// store original data length | |
imgData[++j] = data.length & 0xff; | |
imgData[++j] = (data.length >>> 8) & 0xff; | |
imgData[++j] = (data.length >>> 16) & 0xff; | |
imgData[++j] = 0xff; | |
imgData[++j] = (data.length >>> 24); | |
imgData[++j] = 0xff; | |
imgData[++j] = 0xff; | |
imgData[++j] = 0xff; | |
var len = data.length, subLen = Math.floor(len/3)*3 - 1; | |
var i = -1; while(i < subLen) | |
{ | |
imgData[++j] = data[++i]; | |
imgData[++j] = data[++i]; | |
imgData[++j] = data[++i]; | |
imgData[++j] = 0xff; | |
} | |
var k = 5 - (len - subLen - 1) | |
switch(k) | |
{ | |
case 3: imgData[++j] = data[++i]; --k | |
case 4: imgData[++j] = data[++i]; --k; | |
while(k--) | |
imgData[++j] = 0xff; | |
} | |
ctx.putImageData(img, 0, 0); | |
return c.toDataURL('image/png'); | |
}; | |
var _decompress = function(src, _onOk, _onError) | |
{ | |
var img = new Image(); | |
img.onload = function() | |
{ | |
var c = document.createElement('canvas'); | |
var w = img.naturalWidth || img.width; | |
if(w == 0) | |
return setTimeout(img.onload, 250); | |
c.width = w; | |
c.height = w; | |
var ctx = c.getContext('2d'); | |
ctx.globalCompositeOperation = 'copy'; | |
ctx.drawImage(img, 0, 0); | |
var imgData = ctx.getImageData(0, 0, w, w).data; | |
var len = imgData[0] + (imgData[1] << 8) + (imgData[2] << 16) + (imgData[4] << 24); | |
if($G.opera && imgData[3] != 0xff) | |
return setTimeout(img.onload, 250); | |
var j = 7; | |
var data = []; | |
var subLen = Math.floor(len/3)*3 - 1; | |
var i = -1; while(i < subLen) | |
{ | |
data[++i] = imgData[++j]; | |
data[++i] = imgData[++j]; | |
data[++i] = imgData[++j]; | |
++j; | |
} | |
switch(len - subLen - 1) | |
{ | |
case 2: data[++i] = imgData[++j]; | |
case 1: data[++i] = imgData[++j]; | |
} | |
img = img.onload = null; | |
_onOk(data); | |
}; | |
if(_onError) | |
img.onerror = _onError; | |
img.src = src; | |
if($G.opera) | |
setTimeout(img.onload, 250); | |
}; | |
return { | |
_compress: _compress, | |
_decompress: _decompress | |
}; | |
})(this); | |
(function($G) | |
{ | |
var _fill = function(data) | |
{ | |
var i = data.length; while(i--) | |
data[i] = i & 0xff; | |
//data[i] = 0; | |
//data[i] = ((Math.random()*255)|0); | |
//data[i] = (i%4 == 3) ? 255 : (i + 0x80) & 0xff; | |
return data; | |
}; | |
var _isEq = function(a, b) | |
{ | |
if(a.length == b.length) | |
{ | |
var i = a.length; while(i-- && a[i] === b[i]) | |
; | |
return i == -1; | |
} | |
return false; | |
}; | |
// test | |
var n = 1e6; | |
var data = _fill(new Array(n)); | |
var cData = Deflate._compress(data); | |
console.log('compressed data length = ', cData.length, 'ratio', (100*n/(cData.length/4*3) - 100).toFixed(2)); | |
Deflate._decompress( | |
cData, | |
function(iData) | |
{ | |
console.log('_isEq', _isEq(data, iData)); | |
} | |
); | |
})(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment