102 lines
3 KiB
JavaScript
102 lines
3 KiB
JavaScript
var crc32 = require('crc-32')
|
|
|
|
module.exports = extractChunks
|
|
|
|
// Used for fast-ish conversion between uint8s and uint32s/int32s.
|
|
// Also required in order to remain agnostic for both Node Buffers and
|
|
// Uint8Arrays.
|
|
var uint8 = new Uint8Array(4)
|
|
var int32 = new Int32Array(uint8.buffer)
|
|
var uint32 = new Uint32Array(uint8.buffer)
|
|
|
|
function extractChunks (data) {
|
|
if (data[0] !== 0x89) throw new Error('Invalid .png file header')
|
|
if (data[1] !== 0x50) throw new Error('Invalid .png file header')
|
|
if (data[2] !== 0x4E) throw new Error('Invalid .png file header')
|
|
if (data[3] !== 0x47) throw new Error('Invalid .png file header')
|
|
if (data[4] !== 0x0D) throw new Error('Invalid .png file header: possibly caused by DOS-Unix line ending conversion?')
|
|
if (data[5] !== 0x0A) throw new Error('Invalid .png file header: possibly caused by DOS-Unix line ending conversion?')
|
|
if (data[6] !== 0x1A) throw new Error('Invalid .png file header')
|
|
if (data[7] !== 0x0A) throw new Error('Invalid .png file header: possibly caused by DOS-Unix line ending conversion?')
|
|
|
|
var ended = false
|
|
var chunks = []
|
|
var idx = 8
|
|
|
|
while (idx < data.length) {
|
|
// Read the length of the current chunk,
|
|
// which is stored as a Uint32.
|
|
uint8[3] = data[idx++]
|
|
uint8[2] = data[idx++]
|
|
uint8[1] = data[idx++]
|
|
uint8[0] = data[idx++]
|
|
|
|
// Chunk includes name/type for CRC check (see below).
|
|
var length = uint32[0] + 4
|
|
var chunk = new Uint8Array(length)
|
|
chunk[0] = data[idx++]
|
|
chunk[1] = data[idx++]
|
|
chunk[2] = data[idx++]
|
|
chunk[3] = data[idx++]
|
|
|
|
// Get the name in ASCII for identification.
|
|
var name = (
|
|
String.fromCharCode(chunk[0]) +
|
|
String.fromCharCode(chunk[1]) +
|
|
String.fromCharCode(chunk[2]) +
|
|
String.fromCharCode(chunk[3])
|
|
)
|
|
|
|
// The IHDR header MUST come first.
|
|
if (!chunks.length && name !== 'IHDR') {
|
|
throw new Error('IHDR header missing')
|
|
}
|
|
|
|
// The IEND header marks the end of the file,
|
|
// so on discovering it break out of the loop.
|
|
if (name === 'IEND') {
|
|
ended = true
|
|
chunks.push({
|
|
name: name,
|
|
data: new Uint8Array(0)
|
|
})
|
|
|
|
break
|
|
}
|
|
|
|
// Read the contents of the chunk out of the main buffer.
|
|
for (var i = 4; i < length; i++) {
|
|
chunk[i] = data[idx++]
|
|
}
|
|
|
|
// Read out the CRC value for comparison.
|
|
// It's stored as an Int32.
|
|
uint8[3] = data[idx++]
|
|
uint8[2] = data[idx++]
|
|
uint8[1] = data[idx++]
|
|
uint8[0] = data[idx++]
|
|
|
|
var crcActual = int32[0]
|
|
var crcExpect = crc32.buf(chunk)
|
|
if (crcExpect !== crcActual) {
|
|
throw new Error(
|
|
'CRC values for ' + name + ' header do not match, PNG file is likely corrupted'
|
|
)
|
|
}
|
|
|
|
// The chunk data is now copied to remove the 4 preceding
|
|
// bytes used for the chunk name/type.
|
|
var chunkData = new Uint8Array(chunk.buffer.slice(4))
|
|
|
|
chunks.push({
|
|
name: name,
|
|
data: chunkData
|
|
})
|
|
}
|
|
|
|
if (!ended) {
|
|
throw new Error('.png file ended prematurely: no IEND header was found')
|
|
}
|
|
|
|
return chunks
|
|
}
|