Skip to content

Instantly share code, notes, and snippets.

@jbrantly
Created July 29, 2010 23:13
Show Gist options
  • Save jbrantly/499486 to your computer and use it in GitHub Desktop.
Save jbrantly/499486 to your computer and use it in GitHub Desktop.
// This file is SUPER RAW. Debugging and test code abound!
// This is a pure JS PNG encoder which skips zlib compression. Good enough for small dynamic data URIs though
var testData = [[25, 0, 151, 255, 25, 0, 151, 255, 25, 0, 151, 255], [25, 0, 151, 255, 25, 0, 151, 255, 25, 0, 151, 255], [25, 0, 151, 255, 25, 0, 151, 255, 25, 0, 151, 128]];
var preFilteredPngImage = [[0x19, 0, 0x97, 0xff, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x81]];
var preFilteredPngData = [1, 0x19, 0, 0x97, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x81];
var encodePng = function(data) {
var png = [];
png.push(0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a);
var ihdrChunk = createPngIHDR(data);
for (var i = 0, n = ihdrChunk.length; i<n; i++) {
png.push(ihdrChunk[i]);
}
var idatChunk = createPngIDAT(data);
for (var i = 0, n = idatChunk.length; i<n; i++) {
png.push(idatChunk[i]);
}
var iendChunk = createPngIEND(data);
for (var i = 0, n = iendChunk.length; i<n; i++) {
png.push(iendChunk[i]);
}
return png;
};
var createPngIHDR = function(data) {
var chunkData = [];
var width = data[0].length / 4;
var height = data.length;
chunkData.push((width >>> 24) & 0xff);
chunkData.push((width >>> 16) & 0xff);
chunkData.push((width >>> 8) & 0xf);
chunkData.push(width & 0xff);
chunkData.push((height >>> 24) & 0xff);
chunkData.push((height >>> 16) & 0xff);
chunkData.push((height >>> 8) & 0xf);
chunkData.push(height & 0xff);
chunkData.push(8); // bitDepth
chunkData.push(6); // colorType = RGBA
chunkData.push(0); // compressionMethod = DEFLATE (standard)
chunkData.push(0); // filterMethod = 0 (standard)
chunkData.push(0); // interlace = off
return createPngChunk([0x49, 0x48, 0x44, 0x52], chunkData);
};
var createPngIDAT = function(data) {
var chunkData = [];
for (var i = 0, n = data.length; i<n; i++) {
var scanline = data[i];
// For quick testing purposes only, change to 0
//if (i == 0) {
// chunkData.push(1);
//}
//else {
// chunkData.push(2);
//}
chunkData.push(0);
for (var x = 0, y = scanline.length; x<y; x+=4) {
chunkData.push(scanline[x], scanline[x+1], scanline[x+2], scanline[x+3]);
}
}
chunkData = uncompressedDeflate(chunkData)
return createPngChunk([0x49, 0x44, 0x41, 0x54], chunkData);
};
var createPngIEND = function() {
return createPngChunk([0x49, 0x45, 0x4e, 0x44], []);
};
var createPngChunk = function(type, data) {
var chunk = [];
chunk.push((data.length >>> 24) & 0xFF);
chunk.push((data.length >>> 16) & 0xFF);
chunk.push((data.length >>> 8) & 0xFF);
chunk.push(data.length & 0xFF);
for (var i = 0; i<4; i++) {
chunk.push(type[i]);
}
for (var i = 0, n = data.length; i<n; i++) {
chunk.push(data[i]);
}
// Calc and push CRC
var crc = new CRC();
crc.update(chunk, 4, chunk.length-4);
var checksum = crc.crc ^ 0xffffffff;
chunk.push((checksum >>> 24) & 0xFF);
chunk.push((checksum >>> 16) & 0xFF);
chunk.push((checksum >>> 8) & 0xFF);
chunk.push(checksum & 0xFF);
return chunk;
};
var uncompressedDeflate = function(data) {
var returnData = [];
// Refer to RFC1950 Sect 2.2
returnData.push(0x08); // CMF (CF = 8, CINFO = 0)
returnData.push(0x1D); // FLG (FCHECK = preset, FDICT = 0, FLEVEL = 0)
// Refer to RFC1951 Sects 2, 3.2.3, 3.2.4
var maxBlockSize = 65535;
var leftOverBytes = data.length % maxBlockSize;
var numBlocks = (data.length - leftOverBytes) / maxBlockSize;
if (leftOverBytes > 0) { numBlocks++; }
//console.log(numBlocks);
var adlers1 = 1;
var s2 = 0;
for (var i = 0; i<numBlocks; i++) {
var lastBlock = (i == numBlocks-1);
var numBytesInBlock = lastBlock ? leftOverBytes : maxBlockSize;
//console.log(lastBlock, numBytesInBlock);
// Write block header
returnData.push(lastBlock ? 1 : 0); // BFINAL = lastBlock, BTYPE = 00
// Write block length
var len1 = numBytesInBlock & 0xFF;
var len2 = numBytesInBlock >>> 8;
returnData.push(len1);
returnData.push(len2);
returnData.push(~len1 & 0xFF);
returnData.push(~len2 & 0xFF);
for (var n = maxBlockSize*i, end = n+numBytesInBlock; n<end; n++) {
returnData.push(data[n]);
}
}
// Write adler32 checksum
var adler32 = new Adler32();
adler32.update(data, 0, data.length);
returnData.push((adler32.s2 >>> 8) & 0xFF);
returnData.push(adler32.s2 & 0xFF);
returnData.push((adler32.s1 >>> 8) & 0xFF);
returnData.push(adler32.s1 & 0xFF);
return returnData;
};
var Adler32 = function() {
this.s1 = 1;
this.s2 = 0;
};
Adler32.prototype.update = function(array, index, len) {
var s1 = this.s1;
var s2 = this.s2;
var NMAX = 3792;
var unroll = NMAX / 16;
while (len >= NMAX) {
len -= NMAX;
var n = unroll;
do {
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
} while (--n);
s1 = s1 % 65521;
s2 = s2 % 65521;
}
if (len) {
while (len >= 16) {
len -= 16;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
s1 += (array[index++]);
s2 += s1;
}
while (len--) {
s1 += (array[index++]);
s2 += s1;
}
s1 = s1 % 65521;
s2 = s2 % 65521;
}
this.s1 = s1;
this.s2 = s2;
};
var CRC = function() {
this.crc = 0xffffffff;
};
CRC.table = null;
CRC.make_table = function() {
var table = [];
for (var n = 0; n<256; n++) {
var c = n;
for (var k = 0; k<8; k++) {
if (c & 1) {
c = 0xedb88320 ^ (c >>> 1);
}
else {
c = c >>> 1;
}
}
table[n] = c;
}
CRC.table = table;
};
CRC.prototype.update = function(array, index, len) {
if (CRC.table == null) {
CRC.make_table();
}
var crc = this.crc;
var table = CRC.table;
/*
for (var i = index, n = index+len; i<n; i++) {
crc = CRC.table[(crc ^ array[i]) & 0xff] ^ (crc >>> 8);
}
*/
while (len >= 8) {
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8)
len -= 8;
}
if (len) do {
crc = table[(crc ^ array[index++]) & 0xff] ^ (crc >>> 8);
} while (--len);
this.crc = crc;
return crc;
};
var printByteArray = function(data) {
var str = [];
for (var i = 0, n = data.length; i<n; i++) {
str.push(data[i].toString(16));
}
console.log(str.join(', '));
};
base64Encode = function(array) {
//console.log(array.length);
var sb = [];
//var sb = new Array(Math.ceil(array.length * 1.3333));
//console.log(sb.length);
var map = base64Encode.map;
var i = x = 0;
while (i < array.length) {
origByte1 = array[i++];
origByte2 = array[i++];
origByte3 = array[i++];
encIndex1 = origByte1 >>> 2;
encIndex2 = ((origByte1 & 0x03) << 4) | (origByte2 >>> 0x04);
encIndex3 = ((origByte2 & 0x0f) << 2) | (origByte3 >>> 0x06);
encIndex4 = (origByte3 & 0x3f);
if (isNaN(origByte2)) {
encIndex3 = encIndex4 = 64;
}
else if (isNaN(origByte3)) {
encIndex4 = 64;
}
sb.push(map[encIndex1], map[encIndex2], map[encIndex3], map[encIndex4]);
//sb[x++] = map[encIndex1];
//sb[x++] = map[encIndex2];
//sb[x++] = map[encIndex3];
//sb[x++] = map[encIndex4];
}
return sb.join('');
};
base64Encode.map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
var createTestData = function(width, height) {
var data = new Array(height);
for (var y = 0; y<height; y++) {
var scanline = new Array(width);
for (var x = 0; x<width*4; x++) {
scanline[x] = Math.floor(Math.random()*255);
}
data[y] = scanline;
}
return data;
};
var createTestBase64Data = function(size) {
var data = new Array(size);
for (var i = size; i--;) {
data[i] = Math.floor(Math.random()*255);
}
return data;
};
var test = function() {
var data = createTestBase64Data(1200000);
var start = new Date();
data = base64Encode(data);
var end = new Date();
console.log(end-start);
/*
for (var i = 0; i<1; i++) {
var data = createTestData(640, 480);
var start = new Date();
var png = encodePng(data);
var end = new Date();
alert(end-start);
var imgData = base64Encode(png);
//console.log(imgData);
var img = document.createElement('img');
img.src = "data:image/png;base64," + imgData;
document.getElementById('canvas').appendChild(img);
}
*/
/*
var data = createTestData(16, 16);
var start = new Date();
var png = encodePng(data);
var end = new Date();
//alert(end-start);
var imgData = base64Encode(png);
for (var i = 0; i<1200; i++) {
//console.log(imgData);
var img = document.createElement('img');
img.src = "data:image/png;base64," + imgData;
document.getElementById('canvas').appendChild(img);
}
*/
/*
var img = document.createElement('img');
img.src = "locoloco";
document.getElementById('canvas').appendChild(img);
for (var i in document) {
document.body.appendChild(document.createElement('br'));
document.body.appendChild(document.createTextNode(i));
}
*/
};
@jbrantly
Copy link
Author

jbrantly commented Jun 4, 2016

Per request, this is licensed under MIT.

@ictrobot
Copy link

ictrobot commented Jun 4, 2016

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment