GIF: parse Logical Screen Descriptor

This commit is contained in:
Evan Hahn 2023-08-16 10:46:31 -05:00
parent c61a57ffbf
commit 025e45f9d2
3 changed files with 84 additions and 2 deletions

View File

@ -7,6 +7,13 @@ export const GifNodeType = [
"header", "header",
"headerSignature", "headerSignature",
"headerVersion", "headerVersion",
"logicalScreenDescriptor",
"logicalScreenWidth",
"logicalScreenHeight",
"logicalScreenDescriptorPackedFields",
"logicalScreenBackgroundColorIndex",
"logicalScreenPixelAspectRatio",
].reduce((result, id) => { ].reduce((result, id) => {
result[id] = id; result[id] = id;
return result; return result;

View File

@ -4,6 +4,7 @@ import { GifNodeType } from "./constants.js";
import crel, { fragment } from "../common/crel.js"; import crel, { fragment } from "../common/crel.js";
import getOwn from "../common/getOwn.js"; import getOwn from "../common/getOwn.js";
import { areBytesEqual } from "../common/bytes.js"; import { areBytesEqual } from "../common/bytes.js";
import pluralize from "../common/pluralize.js";
/** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */ /** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */
const textEncoder = new TextEncoder(); const textEncoder = new TextEncoder();
@ -16,6 +17,15 @@ const VERSION_89 = textEncoder.encode("89a");
*/ */
const p = (...children) => crel("p", {}, ...children); 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 * @typedef {object} NodeUi
* @prop {string} title * @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) => { export default (node) => {
const uiFn = getOwn(NODE_UI_FNS, node.type); 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); return uiFn(node);
}; };

View File

@ -20,7 +20,33 @@ export default (bytes) => {
}, },
{ {
type: GifNodeType.headerVersion, 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),
}, },
], ],
}, },