From 025e45f9d23297c0909b57f1d3a4aa28de752802 Mon Sep 17 00:00:00 2001 From: Evan Hahn Date: Wed, 16 Aug 2023 10:46:31 -0500 Subject: [PATCH] GIF: parse Logical Screen Descriptor --- public/gif/constants.js | 7 ++++++ public/gif/getNodeUi.js | 51 ++++++++++++++++++++++++++++++++++++++++- public/gif/parseGif.js | 28 +++++++++++++++++++++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/public/gif/constants.js b/public/gif/constants.js index fb45fdd..6339a23 100644 --- a/public/gif/constants.js +++ b/public/gif/constants.js @@ -7,6 +7,13 @@ export const GifNodeType = [ "header", "headerSignature", "headerVersion", + + "logicalScreenDescriptor", + "logicalScreenWidth", + "logicalScreenHeight", + "logicalScreenDescriptorPackedFields", + "logicalScreenBackgroundColorIndex", + "logicalScreenPixelAspectRatio", ].reduce((result, id) => { result[id] = id; return result; diff --git a/public/gif/getNodeUi.js b/public/gif/getNodeUi.js index 9cebf81..efe5339 100644 --- a/public/gif/getNodeUi.js +++ b/public/gif/getNodeUi.js @@ -4,6 +4,7 @@ import { GifNodeType } from "./constants.js"; import crel, { fragment } from "../common/crel.js"; import getOwn from "../common/getOwn.js"; import { areBytesEqual } from "../common/bytes.js"; +import pluralize from "../common/pluralize.js"; /** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */ const textEncoder = new TextEncoder(); @@ -16,6 +17,15 @@ const VERSION_89 = textEncoder.encode("89a"); */ const p = (...children) => crel("p", {}, ...children); +/** + * @param {Uint8Array} bytes + * @returns {number} + */ +const readGifUint = (bytes) => { + if (bytes.length !== 2) throw new Error("Expected 2 bytes"); + return (bytes[1] << 8) | bytes[0]; +}; + /** * @typedef {object} NodeUi * @prop {string} title @@ -57,6 +67,43 @@ const NODE_UI_FNS = { ), }; }, + + // Logical screen descriptor + + [GifNodeType.logicalScreenDescriptor]: () => ({ + title: "Logical Screen Descriptor", + description: p( + "The logical screen descriptor contains information about the overall GIF, such as its dimensions.", + ), + }), + [GifNodeType.logicalScreenWidth]: ({ bytes }) => ({ + title: "Logical Screen Width", + description: p( + `The width of the image stored as a 16-bit little endian unsigned integer. In this case, the width is ${ + pluralize(readGifUint(bytes), "byte") + }.`, + ), + }), + [GifNodeType.logicalScreenHeight]: ({ bytes }) => ({ + title: "Logical Screen Height", + description: p( + `The height of the image stored as a 16-bit little endian unsigned integer. In this case, the height is ${ + pluralize(readGifUint(bytes), "byte") + }.`, + ), + }), + [GifNodeType.logicalScreenDescriptorPackedFields]: () => ({ + title: "Logical Screen Descriptor packed fields", + description: p("TODO"), + }), + [GifNodeType.logicalScreenBackgroundColorIndex]: () => ({ + title: "Background Color Index", + description: p("TODO"), + }), + [GifNodeType.logicalScreenPixelAspectRatio]: () => ({ + title: "Pixel Aspect Ratio", + description: p("TODO"), + }), }; /** @@ -65,6 +112,8 @@ const NODE_UI_FNS = { */ export default (node) => { const uiFn = getOwn(NODE_UI_FNS, node.type); - if (!uiFn) throw new Error("Found a node with no matching UI function"); + if (!uiFn) { + throw new Error(`Found a node (${node.type}) with no matching UI function`); + } return uiFn(node); }; diff --git a/public/gif/parseGif.js b/public/gif/parseGif.js index 759eaed..6409e5c 100644 --- a/public/gif/parseGif.js +++ b/public/gif/parseGif.js @@ -20,7 +20,33 @@ export default (bytes) => { }, { type: GifNodeType.headerVersion, - bytes: bytes.subarray(3, 6), + bytes: bytes.subarray(3, 3 + 3), + }, + ], + }, + { + type: GifNodeType.logicalScreenDescriptor, + bytes: bytes.subarray(6, 6 + 6), + children: [ + { + type: GifNodeType.logicalScreenWidth, + bytes: bytes.subarray(6, 6 + 2), + }, + { + type: GifNodeType.logicalScreenHeight, + bytes: bytes.subarray(8, 8 + 2), + }, + { + type: GifNodeType.logicalScreenDescriptorPackedFields, + bytes: bytes.subarray(10, 10 + 1), + }, + { + type: GifNodeType.logicalScreenBackgroundColorIndex, + bytes: bytes.subarray(11, 11 + 1), + }, + { + type: GifNodeType.logicalScreenPixelAspectRatio, + bytes: bytes.subarray(12, 12 + 1), }, ], },