Skip to content

Instantly share code, notes, and snippets.

@jaburns
Created May 13, 2020 00:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaburns/5b207c4d69de3ac25010cab23e71953d to your computer and use it in GitHub Desktop.
Save jaburns/5b207c4d69de3ac25010cab23e71953d to your computer and use it in GitHub Desktop.
node.js code for embedded data in a png
const fs = require('fs');
const testPng = fs.readFileSync('input.png');
// ===== crc-32 ==============================================
// https://github.com/SheetJS/js-crc32/blob/master/crc32.js
function signed_crc_table() {
var c = 0, table = new Array(256);
for(var n =0; n != 256; ++n){
c = n;
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
c = ((c&1) ? (-306674912 ^ (c >>> 1)) : (c >>> 1));
table[n] = c;
}
return typeof Int32Array !== 'undefined' ? new Int32Array(table) : table;
}
var T = signed_crc_table();
function crc32_buf_8(buf) {
var C = -1, L = buf.length - 7;
for(var i = 0; i < L;) {
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
}
while(i < L+7) C = (C>>>8) ^ T[(C^buf[i++])&0xFF];
return C ^ -1;
}
// ===========================================================
const readPNG = buff =>
{
let i = 8;
const chunks = [];
while( i < buff.length )
{
const len = buff.readUInt32BE(i);
i+= 4;
const type = buff.slice(i,i+4).toString('ascii');
const typeAndData = buff.slice(i,i+4+len);
const data = buff.slice( i+4, i+4+len );
i += 4 + len;
const crc = buff.readInt32BE(i);
i += 4;
const crcGen = crc32_buf_8( typeAndData );
let textValue = null;
if( type === 'tEXt' )
{
const splitter = data.indexOf( 0 );
textValue = {
key: data.slice( 0, splitter ).toString('ascii'),
value: data.slice( splitter+1, data.length ).toString('ascii'),
};
}
chunks.push({ len, type, data, typeAndData, crc, crcGen, textValue });
}
return chunks;
};
// type Chunk = { text: string, data: Uint8Array }
const writePNG = chunks =>
{
let result = [];
result.push(Uint8Array.of( 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ));
chunks.forEach( chunk =>
{
const buff0 = Buffer.alloc( 4 );
buff0.writeUInt32BE( chunk.data.length, 0 );
const buff1 = Buffer.alloc( 4 + chunk.data.length );
buff1.write( chunk.type, 0, 4, 'ascii' );
chunk.data.copy( buff1, 4, 0, chunk.data.length );
const buff2 = Buffer.alloc( 4 );
buff2.writeInt32BE( crc32_buf_8( buff1 ));
result.push( buff0 );
result.push( buff1 );
result.push( buff2 );
});
return Buffer.concat(result);
};
const buildTextChunk = ( key, value ) =>
{
const data = Buffer.alloc( key.length + value.length + 1 );
data.write( key, 0, 'ascii' );
data.writeUInt8( 0, key.length );
data.write( value, key.length + 1, 'ascii' );
return { type: 'tEXt', data };
};
const buildEndChunk = () => ({ type: 'IEND', data: Buffer.alloc( 0 ) });
const inpng = readPNG( testPng );
const outpng = writePNG( inpng
.filter( x => x.type !== 'tEXt' && x.type !== 'IEND' )
.concat([
buildTextChunk('custom::data', 'hello world!'),
buildEndChunk()
])
);
console.log(inpng);
fs.writeFileSync( 'output.png', outpng );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment