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
url
is 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
import
is given only allowurl
and collapsable areas (ignore all other parameters)- When the import menu is open, the GIF is not yet decoded (only a preview is shown)
- If
gifReal
andframeReal
are both0
(OFF) ignorepos
andzoom
- If
gifFull
is1
(ON) ignoreframeFull
- If
frameFull
is1
(ON) ignoreframeView
- If
frameView
is0
(collapsed) ignoreframeReal
andframeSmooth
- If
pos
andzoom
are given, applypos
beforezoom
Exported constants in the GIFdecodeModule.js
file.
- Export
Interrupt
class - Export
DisposalMethod
enum - Export
decodeGIF
function - Export
getGIFLoopAmount
function
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 yieldsfalse
then it will reject withfile to large
function( 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:
interruptSignal
reference, when it triggers- fetch errors when trying to fetch the GIF from
gifURL
:fetch error: network error
fetch error (connecting)
any unknown error duringfetch
fetch error: recieved STATUS_CODE
when URL yields a status code that's NOT between 200 and 299 (inclusive)fetch error: could not read resource
fetch error: resource to large
(not fromsizeCheck
)fetch error (reading)
any unknown error duringResponse.arrayBuffer
file to large
whensizeCheck
yieldsfalse
not a supported GIF file
when it's not a GIF file or the version is notGIF89a
error while parsing frame [INDEX] "ERROR"
while decoding GIF - one of the followingGIF frame size is to large
plain text extension without global color table
undefined block found
reading out of range
(unexpected end of file during decoding)unknown error
Throws (TypeError
) for one of the following (in order)
gifURL
is not astring
interruptSignal
is not anInterruptSignal
sizeCheck
is given (notnull
orundefined
) but not afunction
progressCallback
is given (notnull
orundefined
) 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) - 10 ms 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 |