GIF: UI for LSD packed field

This commit is contained in:
Evan Hahn 2023-08-16 11:45:11 -05:00
parent 025e45f9d2
commit 775746959e
3 changed files with 92 additions and 7 deletions

View File

@ -5,6 +5,7 @@ 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"; import pluralize from "../common/pluralize.js";
import { parseLogicalScreenDescriptorPackedField } from "./parseGif.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();
@ -17,6 +18,11 @@ const VERSION_89 = textEncoder.encode("89a");
*/ */
const p = (...children) => crel("p", {}, ...children); const p = (...children) => crel("p", {}, ...children);
/**
* @param {(string | Node)[]} children
*/
const li = (...children) => crel("li", {}, ...children);
/** /**
* @param {Uint8Array} bytes * @param {Uint8Array} bytes
* @returns {number} * @returns {number}
@ -92,10 +98,50 @@ const NODE_UI_FNS = {
}.`, }.`,
), ),
}), }),
[GifNodeType.logicalScreenDescriptorPackedFields]: () => ({ [GifNodeType.logicalScreenDescriptorPackedFields]: ({ bytes }) => {
const byte = bytes[0];
const packedField = parseLogicalScreenDescriptorPackedField(byte);
return {
title: "Logical Screen Descriptor packed fields", title: "Logical Screen Descriptor packed fields",
description: p("TODO"), description: fragment(
}), p("This byte contains several flags that control how the GIF is displayed. In binary, it looks like this:"),
p(bytes[0].toString(2).padStart(8, "0")),
crel(
"ul",
{},
li(
packedField.globalColorTableFlag
? "The first bit is 1, which means that a global color table follows the logical screen descriptor. See below for more information about this table."
: "The first bit is 0, which means that this GIF has no global color table. This means that each frame has its own local color table.",
),
li(
`The next three bits (${
packedField.colorResolution.toString(2).padStart(3, "0")
}) encode the color resolution. The bigger this number, the more colors this GIF can represent. This value is decoded as binary and then incremented by one, so the color resolution is ${packedField.colorResolution}.`,
),
li(
`The next bit is ${
packedField.sortFlag ? 1 : 0
}, which means that the colors in the global color table are${
packedField.sortFlag ? "" : "n't"
} sorted. Sorting the color table can improve compression.${
packedField.globalColorTableFlag
? ""
: " However, there is no global color table in this GIF, so this bit is ignored."
}`,
),
li(
`The last three bits (${
(byte & 0b111).toString(2).padStart(3, "0")
}) encode the size of the global color table. To decode this field, you add 1 and then raise that to the power of 2, so the size of the global color table is ${
pluralize(packedField.sizeOfGlobalColorTable, "byte")
}. `,
),
),
),
};
},
[GifNodeType.logicalScreenBackgroundColorIndex]: () => ({ [GifNodeType.logicalScreenBackgroundColorIndex]: () => ({
title: "Background Color Index", title: "Background Color Index",
description: p("TODO"), description: p("TODO"),

View File

@ -3,11 +3,35 @@
import { GifNodeType } from "./constants.js"; import { GifNodeType } from "./constants.js";
/** @typedef {import("../../types/gif.d.ts").GifNode} GifNode */ /** @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 * @param {Uint8Array} bytes
* @returns {null | GifNode} The root node of the GIF tree, or null if the GIF is invalid. * @returns {null | GifNode} The root node of the GIF tree, or null if the GIF is invalid.
*/ */
export default (bytes) => { export default (bytes) => {
const widthBytes = bytes.subarray(6, 6 + 2);
const heightBytes = bytes.subarray(8, 8 + 2);
const packedFieldBytes = bytes.subarray(10, 10 + 1);
const packedFieldByte = packedFieldBytes[0];
/** @type {GifNode[]} */ /** @type {GifNode[]} */
const children = [ const children = [
{ {
@ -30,15 +54,15 @@ export default (bytes) => {
children: [ children: [
{ {
type: GifNodeType.logicalScreenWidth, type: GifNodeType.logicalScreenWidth,
bytes: bytes.subarray(6, 6 + 2), bytes: widthBytes,
}, },
{ {
type: GifNodeType.logicalScreenHeight, type: GifNodeType.logicalScreenHeight,
bytes: bytes.subarray(8, 8 + 2), bytes: heightBytes,
}, },
{ {
type: GifNodeType.logicalScreenDescriptorPackedFields, type: GifNodeType.logicalScreenDescriptorPackedFields,
bytes: bytes.subarray(10, 10 + 1), bytes: packedFieldBytes,
}, },
{ {
type: GifNodeType.logicalScreenBackgroundColorIndex, type: GifNodeType.logicalScreenBackgroundColorIndex,

15
test/gif/parseGif.test.ts Normal file
View File

@ -0,0 +1,15 @@
import { assertEquals } from "assert";
import { parseLogicalScreenDescriptorPackedField } from "../../public/gif/parseGif.js";
Deno.test("parsing logical screen descriptor packed field", () => {
const byte = 0b10110011;
const result = parseLogicalScreenDescriptorPackedField(byte);
assertEquals(result, {
globalColorTableFlag: true,
colorResolution: 4,
sortFlag: false,
sizeOfGlobalColorTable: 16,
});
});