From 8a2109f3f423f0506086e92507aef8c2e8f80bcf Mon Sep 17 00:00:00 2001 From: Philip <17368112+vHeemstra@users.noreply.github.com> Date: Mon, 12 Aug 2024 12:47:58 +0200 Subject: [PATCH] perf: Some refactoring --- src/index.ts | 65 +++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6c58e4e..0a3ef0e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,23 +2,18 @@ function convertToInt(bytes: Uint8Array) { return bytes.reduce((value, byte) => (value << 8) + byte) } -function isEqual(first, second) { - return ( - first[0] === second[0] && - first[1] === second[1] && - first[2] === second[2] && - first[3] === second[3] - ) -} - -const encoder = new TextEncoder() -const chunkTypes = { - animationControl: encoder.encode('acTL'), - imageData: encoder.encode('IDAT'), +function isEqual(first, second, length = 4) { + while (length > 0) { + length-- + if (first[length] !== second[length]) { + return false + } + } + return true; } /** - * @see http://www.libpng.org/pub/png/spec/1.2/PNG-Structure.html + * @see https://www.w3.org/TR/png/#5DataRep */ const headerSizes = { /** Number of bytes reserved for PNG signature */ @@ -31,55 +26,53 @@ const headerSizes = { CRC: 4, } +const chunkTypes = { + signature: [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], + animationControl: [0x61, 0x63, 0x54, 0x4c], // 'acTL' + imageData: [0x49, 0x44, 0x41, 0x54], // 'IDAT' +} + export default function isApng(buffer: Buffer | Uint8Array): boolean { + const minChunkSize = headerSizes.LENGTH + headerSizes.TYPE + headerSizes.CRC + if ( !buffer || !( (typeof Buffer !== 'undefined' && Buffer.isBuffer(buffer)) || buffer instanceof Uint8Array ) || - buffer.length < 16 + buffer.length < headerSizes.SIGNATURE + minChunkSize ) { return false } - const isPNG = - buffer[0] === 0x89 && - buffer[1] === 0x50 && - buffer[2] === 0x4e && - buffer[3] === 0x47 && - buffer[4] === 0x0d && - buffer[5] === 0x0a && - buffer[6] === 0x1a && - buffer[7] === 0x0a - - if (!isPNG) { + /** Check for PNG signature */ + if (!isEqual(buffer, chunkTypes.signature, headerSizes.SIGNATURE)) { return false } - // APNGs have an animation control chunk (acTL) preceding any IDAT(s). - // See: https://en.wikipedia.org/wiki/APNG#File_format - buffer = buffer.subarray(headerSizes.SIGNATURE) - const minBufferSize = headerSizes.LENGTH + headerSizes.TYPE - while (buffer.length >= minBufferSize) { - const chunkLength = convertToInt(buffer.subarray(0, headerSizes.LENGTH)) + /** + * APNGs have an animation control (acTL) chunk preceding any image data (IDAT) chunks. + * @see: https://www.w3.org/TR/png/#5ChunkOrdering + */ + + while (buffer.length >= minChunkSize) { const chunkType = buffer.subarray( headerSizes.LENGTH, headerSizes.LENGTH + headerSizes.TYPE, ) - if (isEqual(chunkType, chunkTypes.animationControl)) { + if (isEqual(chunkType, chunkTypes.animationControl, headerSizes.TYPE)) { return true } - if (isEqual(chunkType, chunkTypes.imageData)) { + if (isEqual(chunkType, chunkTypes.imageData, headerSizes.TYPE)) { return false } - const nextChunkPosition = - headerSizes.LENGTH + headerSizes.TYPE + chunkLength + headerSizes.CRC + const nextChunkPosition = minChunkSize + convertToInt(buffer.subarray(0, headerSizes.LENGTH)) buffer = buffer.subarray(nextChunkPosition) }