// @ts-check import { GifNodeType } from "./constants.js"; /** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */ /** * @typedef {object} LogicalScreenDescriptorPackedField * @prop {boolean} globalColorTableFlag * @prop {number} colorResolution * @prop {boolean} sortFlag * @prop {number} sizeOfGlobalColorTable */ /** * @param {number} byte * @returns {LogicalScreenDescriptorPackedField} */ export const parseLogicalScreenDescriptorPackedField = (byte) => ({ globalColorTableFlag: (byte & 0b10000000) !== 0, colorResolution: ((byte & 0b01110000) >> 4) + 1, sortFlag: (byte & 0b00001000) !== 0, sizeOfGlobalColorTable: 2 ** ((byte & 0b00000111) + 1), }); /** * @param {Uint8Array} bytes * @returns {null | GifNode} The root node of the GIF tree, or null if the GIF is invalid. */ export default (bytes) => { const widthBytes = bytes.subarray(6, 6 + 2); const heightBytes = bytes.subarray(8, 8 + 2); const packedFieldBytes = bytes.subarray(10, 10 + 1); /** @type {GifNode[]} */ const children = [ { type: GifNodeType.header, bytes: bytes.subarray(0, 6), children: [ { type: GifNodeType.headerSignature, bytes: bytes.subarray(0, 3), }, { type: GifNodeType.headerVersion, bytes: bytes.subarray(3, 3 + 3), }, ], }, { type: GifNodeType.logicalScreenDescriptor, bytes: bytes.subarray(6, 6 + 6), children: [ { type: GifNodeType.logicalScreenWidth, bytes: widthBytes, }, { type: GifNodeType.logicalScreenHeight, bytes: heightBytes, }, { type: GifNodeType.logicalScreenDescriptorPackedFields, bytes: packedFieldBytes, }, { type: GifNodeType.logicalScreenBackgroundColorIndex, bytes: bytes.subarray(11, 11 + 1), }, { type: GifNodeType.logicalScreenPixelAspectRatio, bytes: bytes.subarray(12, 12 + 1), }, ], }, ]; let offset = 13; const packedField = parseLogicalScreenDescriptorPackedField( packedFieldBytes[0], ); if (packedField.globalColorTableFlag) { /** @type {GifNode[]} */ const colorNodes = []; for (let i = 0; i < packedField.sizeOfGlobalColorTable; i++) { colorNodes.push({ type: GifNodeType.globalColorTableColor, bytes: bytes.subarray(13 + (i * 3), 13 + (i * 3) + 3), }); } const sizeOfGlobalColorTableInBytes = packedField.sizeOfGlobalColorTable * 3; children.push({ type: GifNodeType.globalColorTable, bytes: bytes.subarray(13, 13 + sizeOfGlobalColorTableInBytes), children: colorNodes, }); offset += sizeOfGlobalColorTableInBytes; } return { type: GifNodeType.root, bytes, children }; };