GIF decoding and rendering with HTML5 canvas
Also, see the specs for GIF89a and What's In A GIF: GIF Explorer by @MrFlick for more technical details.
Example (public domain) GIF from Wikimedia
URL parameters can be in any order, starting with ? after the URL and then parameters in the format NAME=VALUE with & between each parameter.
Note
Values must be encoded URI components.
Parameters with values 0/1 can omit the =VALUE for it to be treated as 1.
For example ?url=https%3A%2F%2Fexample.com%2Fexample.gif&gifInfo=0&frameInfo translates to:
| Name | Decoded value |
|---|---|
url |
https://example.com/example.gif |
gifInfo |
0 (collapsed) |
frameInfo |
1 (expanded) |
Click to toggle table
| Name | Possible values | Description | Default value |
|---|---|---|---|
url |
a GIF file URL | GIF URL to load | none (shows import menu) |
gifInfo |
0 collapsed / 1 expanded |
If the GIF info section should be expanded | 1 (expanded) |
globalColorTable |
0 collapsed / 1 expanded |
If the Global color table section should be expanded | 1 (expanded) |
appExtList |
0 collapsed / 1 expanded |
If the Application-Extensions section should be expanded | 1 (expanded) |
commentsList |
0 collapsed / 1 expanded |
If the Comments section should be expanded | 0 (collapsed) |
unExtList |
0 collapsed / 1 expanded |
If the Unknown extensions section should be expanded | 0 (collapsed) |
frameView |
0 collapsed / 1 expanded |
If the frame canvas section should be expanded | 0 (collapsed) |
frameInfo |
0 collapsed / 1 expanded |
If the Frame info section should be expanded | 0 (collapsed) |
localColorTable |
0 collapsed / 1 expanded |
If the local color table section should be expanded | 1 (expanded) |
frameText |
0 collapsed / 1 expanded |
If the (Frame) Text section should be expanded | 0 (collapsed) |
import |
0 closed / 1 open |
If the import menu should be opened | 0 (closed) |
play |
float between -100 and 100<0 reversed / >0 (or empty) forwards |
If the GIF should be playing, how fast, and in what direction | 0 (paused) |
f |
zero-based frame index (positive integer) if out of bounds uses first frame |
Start at a specific frame | 0 (first frame) |
userLock |
0 OFF / 1 ON |
If the user input lock button should be toggled on | 0 (OFF) |
gifFull |
0 OFF / 1 ON |
If the GIF full window button should be toggled on | 0 (OFF) |
gifReal |
0 OFF / 1 ON |
If the GIF fit window button should be toggled on | 0 (OFF) |
gifSmooth |
0 OFF / 1 ON |
If the GIF img smoothing should be toggled on | 0 (OFF) |
frameFull |
0 OFF / 1 ON |
If the frame full window button should be toggled on | 0 (OFF) |
frameReal |
0 OFF / 1 ON |
If the frame fit window button should be toggled on | 0 (OFF) |
frameSmooth |
0 OFF / 1 ON |
If the frame img smoothing button should be toggled on | 0 (OFF) |
pos |
integer,integer |
The position/offset of the GIF/frame rendering canvas (left,top safe integers) |
0,0 (origin) |
zoom |
float | The starting zoom of the GIF/frame rendering canvas (clamped to +- 500) calculation: canvas_width = GIF_width * (e ↑ (zoom / 100)) |
0 (no zoom) |
- If
urlis not provided, show import menu and ignore all parameters, except for collapsible areas - If, during decoding, an error occurs, it will show the import menu and display the error without further decoding/rendering (ignore all parameters, except for collapsible areas)
- If
importis given only allowurland collapsable areas (ignore all other parameters)- When the import menu is open, the GIF is not yet decoded (only a preview is shown)
- If
gifRealandframeRealare both0(OFF) ignoreposandzoom - If
gifFullis1(ON) ignoreframeFull - If
frameFullis1(ON) ignoreframeView - If
frameViewis0(collapsed) ignoreframeRealandframeSmooth - If
posandzoomare given, applyposbeforezoom
Exported constants in the GIFdecodeModule.js file.
- Export
Interruptclass - Export
DisposalMethodenum - Export
decodeGIFfunction - Export
getGIFLoopAmountfunction
Similar to AbortController, this is used to abort any process.
However, with this, the process can also be paused and resumed later.
It also can provide an AbortSignal for build-in processes like fetch.
Click to show example code
const interrupt=new Interrupt();
abortButton.addEventListener("click", () => {
// ↓ abort on user interaction
interrupt.abort("user");
// ↓ when aborted removes event listener
},{ passive: true, signal: interrupt.signal.signal });
// ↓ promise only knows this so it can't itself use pause/abort, only check (or use AbortSignal)
const interruptSignal=interrupt.signal;
new Promise(async() => {
while(true){
// ↓ wait until unpaused (every second) and throw when aborted, otherwise continue
if(await interruptSignal.check(1000)) throw interruptSignal;
// NOTE: when not paused, immediately continues here
// ...
}
}).then(
result => {
// ↓ remove any event listeners that may use the provided AbortSignal
interrupt.abort();
// ...
},
reason => {
// ↓ check if interrupt was the cause and throw with provided reason
if(reason === interruptSignal) throw interrupt.reason;
// ...
}
);
interrupt.pause();
// interrupt.resume();
// ↓ provided AbortSignal aborts immediately (unpauses automatically)
interrupt.abort("ERROR");Click to show formal definition
declare class Interrupt<T> {
private static class InterruptSignal {
/**
* ## Create an {@linkcode AbortSignal} that will abort when {@linkcode Interrupt.abort} is called
* when aborted, {@linkcode AbortSignal.reason} will be a reference to `this` {@linkcode InterruptSignal} object\
* ! NOT influenced by {@linkcode Interrupt.pause}
*/
get signal: AbortSignal;
/**
* ## Check if signal was aborted
* return delayed until signal is unpaused
* @param {number} [timeout] - time in milliseconds for delay between pause-checks - default `0`
* @returns {Promise<boolean>} when signal is aborted `true` otherwise `false`
* @throws {TypeError} when {@linkcode timeout} is given but not a positive finite number
*/
async check(timeout?: number): Promise<boolean>;
};
/**
* ## Check if {@linkcode obj} is an interrupt signal (instance)
* @param {unknown} obj
* @returns {boolean} gives `true` when it is an interrupt signal and `false` otherwise
*/
static isSignal(obj: unknown): boolean;
/** ## get a signal for (only) checking for an abort */
get signal: InterruptSignal;
/** ## get the reason for abort (`undefined` before abort) */
get reason?: T;
/** ## Pause signal (when not aborted) */
pause(): void;
/** ## Unpause signal */
resume(): void;
/**
* ## Abort signal
* also unpauses signal
* @param {T} [reason] - reason for abort
*/
abort(reason?: T): void;
};
// NOTE: Interrupt and InterruptSignal are immutable
// private fields not shown (except InterruptSignal)See description/type information further below.
Decodes a GIF into its components for rendering on a canvas.
async decodeGIF(gifURL, abortSignal, sizeCheck, progressCallback)
function parameters (in order)
-
gifURL(string) The URL of a GIF file. -
interruptSignal(InterruptSignal) pause/aboard fetching/parsing with this (via Interrupt). -
sizeCheck(optionalfunction) Optional check if the loaded file should be processed if this yieldsfalsethen it will reject withfile to largefunction( byteLength: number // size of file in bytes ): Promise<boolean> | boolean // continues decoding with `true`
-
progressCallback(optionalfunction) Optional callback for showing progress of decoding process (each frame). If asynchronous, it waits for it to resolve.function( percentageRead: number, // decimal from 0 to 1 frameIndex: number, // zero-based frame index frame: ImageData, // current decoded frame (image) framePos: [number, number], // pos from left/top of GIF area gifSize: [number, number] // GIF area width/height ): any
Returns (GIF) a promise of the GIF with each frame decoded separately.
The promise may reject (throw) for the following reasons:
interruptSignalreference, when it triggers- fetch errors when trying to fetch the GIF from
gifURL:fetch error: network errorfetch error (connecting)any unknown error duringfetchfetch error: recieved STATUS_CODEwhen URL yields a status code that's NOT between 200 and 299 (inclusive)fetch error: could not read resourcefetch error: resource to large(not fromsizeCheck)fetch error (reading)any unknown error duringResponse.arrayBuffer
file to largewhensizeCheckyieldsfalsenot a supported GIF filewhen it's not a GIF file or the version is notGIF89aerror while parsing frame [INDEX] "ERROR"while decoding GIF - one of the followingGIF frame size is to largeplain text extension without global color tableundefined block foundreading out of range(unexpected end of file during decoding)unknown error
Throws (TypeError) for one of the following (in order)
gifURLis not astringinterruptSignalis not anInterruptSignalsizeCheckis given (notnullorundefined) but not afunctionprogressCallbackis given (notnullorundefined) but not afunction
Extract the animation loop amount from a GIF.
Note
Generally, for proper looping support, the NETSCAPE2.0 extension must appear immediately after the global color table of the logical screen descriptor (at the beginning of the GIF file).
Still, here, it doesn't matter where it was found.
getGIFLoopAmount(gif)
function parameters (in order)
gif(GIF) A parsed GIF object.
Returns (number) the loop amount of gif as 16bit number (0 to 65'535 or Infinity).
The GIF object constructed has the following attributes.
Click to toggle table
| Attribute | JSDoc annotation | Description |
|---|---|---|
width |
number |
the width of the image in pixels (logical screen size) |
height |
number |
the height of the image in pixels (logical screen size) |
totalTime |
number |
the (maximum) total duration of the gif in milliseconds (all delays added together) will be Infinity if there is a frame with the user input delay flag set and no timeout |
colorRes |
number |
the color depth/resolution in bits per color (in the original) [1-8 bits] |
pixelAspectRatio |
number |
if non zero the pixel aspect ratio will be from 4:1 to 1:4 in 1/64th increments |
sortFlag |
boolean |
if the colors in the global color table are ordered after decreasing importance |
globalColorTable |
[number,number,number][] |
the global color table for the GIF |
backgroundColorIndex |
number|null |
index of the background color into the global color table (if the global color table is not available it's null) can be used as a background before the first frame |
frames |
Frame[] |
each frame of the GIF (decoded into single images) type information further below |
comments |
[string,string][] |
comments in the file and were they where found ([<area found>,<comment>]) |
applicationExtensions |
ApplicationExtension[] |
all application extensions found type information further below |
unknownExtensions |
[number,Uint8Array][] |
all unknown extensions found ([<identifier>,<raw bytes>]) |
Click to toggle table
| Attribute | JSDoc annotation | Description |
|---|---|---|
left |
number |
the position of the left edge of this frame, in pixels, within the gif (from the left edge) |
top |
number |
the position of the top edge of this frame, in pixels, within the gif (from the top edge) |
width |
number |
the width of this frame in pixels |
height |
number |
the height of this frame in pixels |
disposalMethod |
DisposalMethod |
the disposal method for this frame type information further below |
transparentColorIndex |
number|null |
the transparency index into the local or global color table (null if not encountered) |
image |
ImageData |
this frames image data |
plainTextData |
PlainTextData|null |
the text that will be displayed on screen with this frame (null if not encountered) type information further below |
userInputDelayFlag |
boolean |
if set waits for user input before rendering the next frame (timeout after delay if that is non-zero) |
delayTime |
number |
the delay of this frame in milliseconds (0 is undefined (wait for user input or skip frame) - 10ms precision) |
sortFlag |
boolean |
if the colors in the local color table are ordered after decreasing importance |
localColorTable |
[number,number,number][] |
the local color table for this frame |
reserved |
number |
reserved for future use 2bits (from packed field in image descriptor block) |
GCreserved |
number |
reserved for future use 3bits (from packed field in graphics control extension block) |
Click to toggle table
| Name | Internal value ( number) |
Description | Action |
|---|---|---|---|
Unspecified |
0 |
unspecified | do nothing (default to DoNotDispose) |
DoNotDispose |
1 |
do not dispose | keep image / combine with next frame |
RestoreBackgroundColor |
2 |
restore to background color | opaque frame pixels get filled with background color or cleared (when it's the same as transparentColorIndex) |
RestorePrevious |
3 |
restore to previous | dispose frame data after rendering (revealing what was there before) |
UndefinedA |
4 |
undefined | fallback to Unspecified |
UndefinedB |
5 |
undefined | fallback to Unspecified |
UndefinedC |
6 |
undefined | fallback to Unspecified |
UndefinedD |
7 |
undefined | fallback to Unspecified |
Click to toggle table
| Attribute | JSDoc annotation | Description |
|---|---|---|
left |
number |
the position of the left edge of text grid (in pixels) within the GIF (from the left edge) |
top |
number |
the position of the top edge of text grid (in pixels) within the GIF (from the top edge) |
width |
number |
the width of the text grid (in pixels) |
height |
number |
the height of the text grid (in pixels) |
charWidth |
number |
the width (in pixels) of each cell (character) in text grid |
charHeight |
number |
the height (in pixels) of each cell (character) in text grid |
foregroundColor |
number |
the index into the global color table for the foreground color of the text |
backgroundColor |
number |
the index into the global color table for the background color of the text |
text |
string |
the text to render on screen |
Click to toggle table
| Attribute | JSDoc annotation | Description |
|---|---|---|
identifier |
string |
8 character string identifying the application |
authenticationCode |
string |
3 bytes to authenticate the application identifier |
data |
Uint8Array |
the (raw) data of this application extension |
