Skip to content

Instantly share code, notes, and snippets.

@ictrobot
Forked from jbrantly/Pure JS PNG Encoder
Last active June 4, 2016 09:44
Show Gist options
  • Save ictrobot/bc24d73ac3515e3a856a1483e1f229d2 to your computer and use it in GitHub Desktop.
Save ictrobot/bc24d73ac3515e3a856a1483e1f229d2 to your computer and use it in GitHub Desktop.
// based on https://gist.github.com/jbrantly/499486
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, startPos, endPos) {
if (CRC.table == null) {
CRC.make_table();
}
var crc = this.crc;
var table = CRC.table;
len = endPos - startPos;
index = startPos;
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;
};
function pngBytes(b) {
png.set(b, pos);
pos += b.length;
}
function intBytes(i) {
return [(i >>> 24) & 0xff, (i >>> 16) & 0xff, (i >>> 8) & 0xff, i & 0xff];
}
function encodePng(data) {
var width = data[0].length / 4;
var height = data.length;
var length = 8; // magic number
length += 8 + 13 + 4; // ihdr
length += 8 + idatLength(height * (1 + (width * 4))) + 4; // idat
length += 12; // iend
pos = 0;
png = new Uint8ClampedArray(length);
pngBytes([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
createPngIHDR(width, height);
createPngIDAT(data);
createPngIEND();
return png;
};
function createPngIHDR(width, height) {
pngBytes([0, 0, 0, 13]);
posBefore = pos;
pngBytes([0x49, 0x48, 0x44, 0x52]);
pngBytes(intBytes(width));
pngBytes(intBytes(height));
pngBytes([8, 6, 0, 0, 0]);
pngBytes(calcCRC32(posBefore, pos));
};
function createPngIDAT(data) {
var chunkData = [];
for (var i = 0, n = data.length; i < n; i++) {
var scanline = data[i];
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)
pngBytes(intBytes(chunkData.length));
posBefore = pos;
pngBytes([0x49, 0x44, 0x41, 0x54]);
pngBytes(chunkData);
pngBytes(calcCRC32(posBefore, pos));
};
function createPngIEND() {
pngBytes([0, 0, 0, 0]);
pngBytes([0x49, 0x45, 0x4e, 0x44]);
pngBytes(calcCRC32(pos - 4, pos));
};
function calcCRC32(posBefore, posAfter) {
var crc = new CRC();
crc.update(png, posBefore, posAfter);
var checksum = crc.crc ^ 0xffffffff;
return intBytes(checksum);
};
function idatLength(uncompressedLength) {
length = 2; // Refer to RFC1950 Sect 2.2
var maxBlockSize = 65535;
var leftOverBytes = uncompressedLength % maxBlockSize;
var numBlocks = (uncompressedLength - leftOverBytes) / maxBlockSize;
if (leftOverBytes > 0) {
numBlocks++;
}
length += numBlocks * 5;
length += uncompressedLength;
length += 4; //Adler 32
return length;
}
function uncompressedDeflate(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++;
}
var adlers1 = 1;
var s2 = 0;
for (var i = 0; i < numBlocks; i++) {
var lastBlock = (i == numBlocks - 1);
var numBytesInBlock = lastBlock ? leftOverBytes : maxBlockSize;
// 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;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment