formats.exposed/public/gif/parseGif.js

103 lines
2.8 KiB
JavaScript

// @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 };
};