pixi.js
Version:
<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">
1,534 lines (1,375 loc) • 45.4 kB
JavaScript
/*!
* PixiJS - v8.17.1
* Compiled Mon, 16 Mar 2026 09:23:43 UTC
*
* PixiJS is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*/
this.PIXI = this.PIXI || {};
var gif_js = (function (exports) {
'use strict';
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function getDefaultExportFromNamespaceIfPresent (n) {
return n && Object.prototype.hasOwnProperty.call(n, 'default') ? n['default'] : n;
}
function getDefaultExportFromNamespaceIfNotNamed (n) {
return n && Object.prototype.hasOwnProperty.call(n, 'default') && Object.keys(n).length === 1 ? n['default'] : n;
}
function getAugmentedNamespace(n) {
if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a () {
var isInstance = false;
try {
isInstance = this instanceof a;
} catch {}
if (isInstance) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
var lib$1 = {};
var gif = {};
var lib = {};
var hasRequiredLib$1;
function requireLib$1 () {
if (hasRequiredLib$1) return lib;
hasRequiredLib$1 = 1;
"use strict";
Object.defineProperty(lib, "__esModule", {
value: true
});
lib.loop = lib.conditional = lib.parse = void 0;
var parse = function parse(stream, schema) {
var result = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var parent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : result;
if (Array.isArray(schema)) {
schema.forEach(function (partSchema) {
return parse(stream, partSchema, result, parent);
});
} else if (typeof schema === 'function') {
schema(stream, result, parent, parse);
} else {
var key = Object.keys(schema)[0];
if (Array.isArray(schema[key])) {
parent[key] = {};
parse(stream, schema[key], result, parent[key]);
} else {
parent[key] = schema[key](stream, result, parent, parse);
}
}
return result;
};
lib.parse = parse;
var conditional = function conditional(schema, conditionFunc) {
return function (stream, result, parent, parse) {
if (conditionFunc(stream, result, parent)) {
parse(stream, schema, result, parent);
}
};
};
lib.conditional = conditional;
var loop = function loop(schema, continueFunc) {
return function (stream, result, parent, parse) {
var arr = [];
var lastStreamPos = stream.pos;
while (continueFunc(stream, result, parent)) {
var newParent = {};
parse(stream, schema, result, newParent); // cases when whole file is parsed but no termination is there and stream position is not getting updated as well
// it falls into infinite recursion, null check to avoid the same
if (stream.pos === lastStreamPos) {
break;
}
lastStreamPos = stream.pos;
arr.push(newParent);
}
return arr;
};
};
lib.loop = loop;
return lib;
}
var uint8 = {};
var hasRequiredUint8;
function requireUint8 () {
if (hasRequiredUint8) return uint8;
hasRequiredUint8 = 1;
"use strict";
Object.defineProperty(uint8, "__esModule", {
value: true
});
uint8.readBits = uint8.readArray = uint8.readUnsigned = uint8.readString = uint8.peekBytes = uint8.readBytes = uint8.peekByte = uint8.readByte = uint8.buildStream = void 0;
// Default stream and parsers for Uint8TypedArray data type
var buildStream = function buildStream(uint8Data) {
return {
data: uint8Data,
pos: 0
};
};
uint8.buildStream = buildStream;
var readByte = function readByte() {
return function (stream) {
return stream.data[stream.pos++];
};
};
uint8.readByte = readByte;
var peekByte = function peekByte() {
var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
return function (stream) {
return stream.data[stream.pos + offset];
};
};
uint8.peekByte = peekByte;
var readBytes = function readBytes(length) {
return function (stream) {
return stream.data.subarray(stream.pos, stream.pos += length);
};
};
uint8.readBytes = readBytes;
var peekBytes = function peekBytes(length) {
return function (stream) {
return stream.data.subarray(stream.pos, stream.pos + length);
};
};
uint8.peekBytes = peekBytes;
var readString = function readString(length) {
return function (stream) {
return Array.from(readBytes(length)(stream)).map(function (value) {
return String.fromCharCode(value);
}).join('');
};
};
uint8.readString = readString;
var readUnsigned = function readUnsigned(littleEndian) {
return function (stream) {
var bytes = readBytes(2)(stream);
return littleEndian ? (bytes[1] << 8) + bytes[0] : (bytes[0] << 8) + bytes[1];
};
};
uint8.readUnsigned = readUnsigned;
var readArray = function readArray(byteSize, totalOrFunc) {
return function (stream, result, parent) {
var total = typeof totalOrFunc === 'function' ? totalOrFunc(stream, result, parent) : totalOrFunc;
var parser = readBytes(byteSize);
var arr = new Array(total);
for (var i = 0; i < total; i++) {
arr[i] = parser(stream);
}
return arr;
};
};
uint8.readArray = readArray;
var subBitsTotal = function subBitsTotal(bits, startIndex, length) {
var result = 0;
for (var i = 0; i < length; i++) {
result += bits[startIndex + i] && Math.pow(2, length - i - 1);
}
return result;
};
var readBits = function readBits(schema) {
return function (stream) {
var _byte = readByte()(stream); // convert the byte to bit array
var bits = new Array(8);
for (var i = 0; i < 8; i++) {
bits[7 - i] = !!(_byte & 1 << i);
} // convert the bit array to values based on the schema
return Object.keys(schema).reduce(function (res, key) {
var def = schema[key];
if (def.length) {
res[key] = subBitsTotal(bits, def.index, def.length);
} else {
res[key] = bits[def.index];
}
return res;
}, {});
};
};
uint8.readBits = readBits;
return uint8;
}
var hasRequiredGif;
function requireGif () {
if (hasRequiredGif) return gif;
hasRequiredGif = 1;
(function (exports$1) {
"use strict";
Object.defineProperty(exports$1, "__esModule", {
value: true
});
exports$1["default"] = void 0;
var _ = requireLib$1();
var _uint = requireUint8();
// a set of 0x00 terminated subblocks
var subBlocksSchema = {
blocks: function blocks(stream) {
var terminator = 0x00;
var chunks = [];
var streamSize = stream.data.length;
var total = 0;
for (var size = (0, _uint.readByte)()(stream); size !== terminator; size = (0, _uint.readByte)()(stream)) {
// size becomes undefined for some case when file is corrupted and terminator is not proper
// null check to avoid recursion
if (!size) break; // catch corrupted files with no terminator
if (stream.pos + size >= streamSize) {
var availableSize = streamSize - stream.pos;
chunks.push((0, _uint.readBytes)(availableSize)(stream));
total += availableSize;
break;
}
chunks.push((0, _uint.readBytes)(size)(stream));
total += size;
}
var result = new Uint8Array(total);
var offset = 0;
for (var i = 0; i < chunks.length; i++) {
result.set(chunks[i], offset);
offset += chunks[i].length;
}
return result;
}
}; // global control extension
var gceSchema = (0, _.conditional)({
gce: [{
codes: (0, _uint.readBytes)(2)
}, {
byteSize: (0, _uint.readByte)()
}, {
extras: (0, _uint.readBits)({
future: {
index: 0,
length: 3
},
disposal: {
index: 3,
length: 3
},
userInput: {
index: 6
},
transparentColorGiven: {
index: 7
}
})
}, {
delay: (0, _uint.readUnsigned)(true)
}, {
transparentColorIndex: (0, _uint.readByte)()
}, {
terminator: (0, _uint.readByte)()
}]
}, function (stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xf9;
}); // image pipeline block
var imageSchema = (0, _.conditional)({
image: [{
code: (0, _uint.readByte)()
}, {
descriptor: [{
left: (0, _uint.readUnsigned)(true)
}, {
top: (0, _uint.readUnsigned)(true)
}, {
width: (0, _uint.readUnsigned)(true)
}, {
height: (0, _uint.readUnsigned)(true)
}, {
lct: (0, _uint.readBits)({
exists: {
index: 0
},
interlaced: {
index: 1
},
sort: {
index: 2
},
future: {
index: 3,
length: 2
},
size: {
index: 5,
length: 3
}
})
}]
}, (0, _.conditional)({
lct: (0, _uint.readArray)(3, function (stream, result, parent) {
return Math.pow(2, parent.descriptor.lct.size + 1);
})
}, function (stream, result, parent) {
return parent.descriptor.lct.exists;
}), {
data: [{
minCodeSize: (0, _uint.readByte)()
}, subBlocksSchema]
}]
}, function (stream) {
return (0, _uint.peekByte)()(stream) === 0x2c;
}); // plain text block
var textSchema = (0, _.conditional)({
text: [{
codes: (0, _uint.readBytes)(2)
}, {
blockSize: (0, _uint.readByte)()
}, {
preData: function preData(stream, result, parent) {
return (0, _uint.readBytes)(parent.text.blockSize)(stream);
}
}, subBlocksSchema]
}, function (stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0x01;
}); // application block
var applicationSchema = (0, _.conditional)({
application: [{
codes: (0, _uint.readBytes)(2)
}, {
blockSize: (0, _uint.readByte)()
}, {
id: function id(stream, result, parent) {
return (0, _uint.readString)(parent.blockSize)(stream);
}
}, subBlocksSchema]
}, function (stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xff;
}); // comment block
var commentSchema = (0, _.conditional)({
comment: [{
codes: (0, _uint.readBytes)(2)
}, subBlocksSchema]
}, function (stream) {
var codes = (0, _uint.peekBytes)(2)(stream);
return codes[0] === 0x21 && codes[1] === 0xfe;
});
var schema = [{
header: [{
signature: (0, _uint.readString)(3)
}, {
version: (0, _uint.readString)(3)
}]
}, {
lsd: [{
width: (0, _uint.readUnsigned)(true)
}, {
height: (0, _uint.readUnsigned)(true)
}, {
gct: (0, _uint.readBits)({
exists: {
index: 0
},
resolution: {
index: 1,
length: 3
},
sort: {
index: 4
},
size: {
index: 5,
length: 3
}
})
}, {
backgroundColorIndex: (0, _uint.readByte)()
}, {
pixelAspectRatio: (0, _uint.readByte)()
}]
}, (0, _.conditional)({
gct: (0, _uint.readArray)(3, function (stream, result) {
return Math.pow(2, result.lsd.gct.size + 1);
})
}, function (stream, result) {
return result.lsd.gct.exists;
}), // content frames
{
frames: (0, _.loop)([gceSchema, applicationSchema, commentSchema, imageSchema, textSchema], function (stream) {
var nextCode = (0, _uint.peekByte)()(stream); // rather than check for a terminator, we should check for the existence
// of an ext or image block to avoid infinite loops
//var terminator = 0x3B;
//return nextCode !== terminator;
return nextCode === 0x21 || nextCode === 0x2c;
})
}];
var _default = schema;
exports$1["default"] = _default;
} (gif));
return gif;
}
var deinterlace = {};
var hasRequiredDeinterlace;
function requireDeinterlace () {
if (hasRequiredDeinterlace) return deinterlace;
hasRequiredDeinterlace = 1;
"use strict";
Object.defineProperty(deinterlace, "__esModule", {
value: true
});
deinterlace.deinterlace = void 0;
/**
* Deinterlace function from https://github.com/shachaf/jsgif
*/
var deinterlace$1 = function deinterlace(pixels, width) {
var newPixels = new Array(pixels.length);
var rows = pixels.length / width;
var cpRow = function cpRow(toRow, fromRow) {
var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width);
newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels));
}; // See appendix E.
var offsets = [0, 4, 2, 1];
var steps = [8, 8, 4, 2];
var fromRow = 0;
for (var pass = 0; pass < 4; pass++) {
for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) {
cpRow(toRow, fromRow);
fromRow++;
}
}
return newPixels;
};
deinterlace.deinterlace = deinterlace$1;
return deinterlace;
}
var lzw = {};
var hasRequiredLzw;
function requireLzw () {
if (hasRequiredLzw) return lzw;
hasRequiredLzw = 1;
"use strict";
Object.defineProperty(lzw, "__esModule", {
value: true
});
lzw.lzw = void 0;
/**
* javascript port of java LZW decompression
* Original java author url: https://gist.github.com/devunwired/4479231
*/
var lzw$1 = function lzw(minCodeSize, data, pixelCount) {
var MAX_STACK_SIZE = 4096;
var nullCode = -1;
var npix = pixelCount;
var available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, i, datum, data_size, first, top, bi, pi;
var dstPixels = new Array(pixelCount);
var prefix = new Array(MAX_STACK_SIZE);
var suffix = new Array(MAX_STACK_SIZE);
var pixelStack = new Array(MAX_STACK_SIZE + 1); // Initialize GIF data stream decoder.
data_size = minCodeSize;
clear = 1 << data_size;
end_of_information = clear + 1;
available = clear + 2;
old_code = nullCode;
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
for (code = 0; code < clear; code++) {
prefix[code] = 0;
suffix[code] = code;
} // Decode GIF pixel stream.
var datum, bits, count, first, top, pi, bi;
datum = bits = count = first = top = pi = bi = 0;
for (i = 0; i < npix;) {
if (top === 0) {
if (bits < code_size) {
// get the next byte
datum += data[bi] << bits;
bits += 8;
bi++;
continue;
} // Get the next code.
code = datum & code_mask;
datum >>= code_size;
bits -= code_size; // Interpret the code
if (code > available || code == end_of_information) {
break;
}
if (code == clear) {
// Reset decoder.
code_size = data_size + 1;
code_mask = (1 << code_size) - 1;
available = clear + 2;
old_code = nullCode;
continue;
}
if (old_code == nullCode) {
pixelStack[top++] = suffix[code];
old_code = code;
first = code;
continue;
}
in_code = code;
if (code == available) {
pixelStack[top++] = first;
code = old_code;
}
while (code > clear) {
pixelStack[top++] = suffix[code];
code = prefix[code];
}
first = suffix[code] & 0xff;
pixelStack[top++] = first; // add a new string to the table, but only if space is available
// if not, just continue with current table until a clear code is found
// (deferred clear code implementation as per GIF spec)
if (available < MAX_STACK_SIZE) {
prefix[available] = old_code;
suffix[available] = first;
available++;
if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) {
code_size++;
code_mask += available;
}
}
old_code = in_code;
} // Pop a pixel off the pixel stack.
top--;
dstPixels[pi++] = pixelStack[top];
i++;
}
for (i = pi; i < npix; i++) {
dstPixels[i] = 0; // clear missing pixels
}
return dstPixels;
};
lzw.lzw = lzw$1;
return lzw;
}
var hasRequiredLib;
function requireLib () {
if (hasRequiredLib) return lib$1;
hasRequiredLib = 1;
"use strict";
Object.defineProperty(lib$1, "__esModule", {
value: true
});
lib$1.decompressFrames = lib$1.decompressFrame = lib$1.parseGIF = void 0;
var _gif = _interopRequireDefault(requireGif());
var _jsBinarySchemaParser = requireLib$1();
var _uint = requireUint8();
var _deinterlace = requireDeinterlace();
var _lzw = requireLzw();
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
var parseGIF = function parseGIF(arrayBuffer) {
var byteData = new Uint8Array(arrayBuffer);
return (0, _jsBinarySchemaParser.parse)((0, _uint.buildStream)(byteData), _gif["default"]);
};
lib$1.parseGIF = parseGIF;
var generatePatch = function generatePatch(image) {
var totalPixels = image.pixels.length;
var patchData = new Uint8ClampedArray(totalPixels * 4);
for (var i = 0; i < totalPixels; i++) {
var pos = i * 4;
var colorIndex = image.pixels[i];
var color = image.colorTable[colorIndex] || [0, 0, 0];
patchData[pos] = color[0];
patchData[pos + 1] = color[1];
patchData[pos + 2] = color[2];
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;
}
return patchData;
};
var decompressFrame = function decompressFrame(frame, gct, buildImagePatch) {
if (!frame.image) {
console.warn('gif frame does not have associated image.');
return;
}
var image = frame.image; // get the number of pixels
var totalPixels = image.descriptor.width * image.descriptor.height; // do lzw decompression
var pixels = (0, _lzw.lzw)(image.data.minCodeSize, image.data.blocks, totalPixels); // deal with interlacing if necessary
if (image.descriptor.lct.interlaced) {
pixels = (0, _deinterlace.deinterlace)(pixels, image.descriptor.width);
}
var resultImage = {
pixels: pixels,
dims: {
top: frame.image.descriptor.top,
left: frame.image.descriptor.left,
width: frame.image.descriptor.width,
height: frame.image.descriptor.height
}
}; // color table
if (image.descriptor.lct && image.descriptor.lct.exists) {
resultImage.colorTable = image.lct;
} else {
resultImage.colorTable = gct;
} // add per frame relevant gce information
if (frame.gce) {
resultImage.delay = (frame.gce.delay || 10) * 10; // convert to ms
resultImage.disposalType = frame.gce.extras.disposal; // transparency
if (frame.gce.extras.transparentColorGiven) {
resultImage.transparentIndex = frame.gce.transparentColorIndex;
}
} // create canvas usable imagedata if desired
if (buildImagePatch) {
resultImage.patch = generatePatch(resultImage);
}
return resultImage;
};
lib$1.decompressFrame = decompressFrame;
var decompressFrames = function decompressFrames(parsedGif, buildImagePatches) {
return parsedGif.frames.filter(function (f) {
return f.image;
}).map(function (f) {
return decompressFrame(f, parsedGif.gct, buildImagePatches);
});
};
lib$1.decompressFrames = decompressFrames;
return lib$1;
}
var libExports = requireLib();
var index = /*@__PURE__*/getDefaultExportFromCjs(libExports);
"use strict";
var __defProp$1 = Object.defineProperty;
var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues$1 = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
if (__getOwnPropSymbols$1)
for (var prop of __getOwnPropSymbols$1(b)) {
if (__propIsEnum$1.call(b, prop))
__defNormalProp$1(a, prop, b[prop]);
}
return a;
};
var __objRest$1 = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp$1.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols$1)
for (var prop of __getOwnPropSymbols$1(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum$1.call(source, prop))
target[prop] = source[prop];
}
return target;
};
class GifSource {
/**
* @param frames - Array of GifFrame instances.
*/
constructor(frames) {
if (!frames || !frames.length) throw new Error("Invalid frames");
const [{ texture: { width, height } }] = frames;
this.width = width;
this.height = height;
this.frames = frames;
this.textures = this.frames.map((frame) => frame.texture);
this.totalFrames = this.frames.length;
this.duration = this.frames[this.totalFrames - 1].end;
}
/** Destroy animation data and don't use after this */
destroy() {
for (const texture of this.textures) {
texture.destroy(true);
}
for (const frame of this.frames) {
frame.texture = null;
}
this.frames.length = 0;
this.textures.length = 0;
Object.assign(this, {
frames: null,
textures: null,
width: 0,
height: 0,
duration: 0,
totalFrames: 0
});
}
/**
* Create an animated GIF animation from a GIF image's ArrayBuffer. The easiest way to get
* the buffer is to use Assets.
* @example
* import { GifSource, GifSprite } from 'pixi.js/gif';
*
* const buffer = await fetch('./file.gif').then(res => res.arrayBuffer());
* const source = GifSource.from(buffer);
* const sprite = new GifSprite(source);
* @param buffer - GIF image arraybuffer from Assets.
* @param options - Optional options to use when building from buffer.
*/
static from(buffer, options) {
if (!buffer || buffer.byteLength === 0) {
throw new Error("Invalid buffer");
}
const validateAndFix = (gif2) => {
var _a2;
let currentGce = null;
for (const frame of gif2.frames) {
currentGce = (_a2 = frame.gce) != null ? _a2 : currentGce;
if ("image" in frame && !("gce" in frame)) {
frame.gce = currentGce;
}
}
};
const gif = libExports.parseGIF(buffer);
validateAndFix(gif);
const gifFrames = libExports.decompressFrames(gif, true);
const frames = [];
const animWidth = gif.lsd.width;
const animHeight = gif.lsd.height;
const canvas = PIXI.DOMAdapter.get().createCanvas(animWidth, animHeight);
const context = canvas.getContext("2d", { willReadFrequently: true });
const patchCanvas = PIXI.DOMAdapter.get().createCanvas();
const patchContext = patchCanvas.getContext("2d");
let time = 0;
let previousFrame = null;
const _a = options != null ? options : {}, { fps = 30 } = _a, canvasSourceOptions = __objRest$1(_a, ["fps"]);
const defaultDelay = 1e3 / fps;
for (let i = 0; i < gifFrames.length; i++) {
const {
disposalType = 2,
delay = defaultDelay,
patch,
dims: { width, height, left, top }
} = gifFrames[i];
patchCanvas.width = width;
patchCanvas.height = height;
patchContext.clearRect(0, 0, width, height);
const patchData = patchContext.createImageData(width, height);
patchData.data.set(patch);
patchContext.putImageData(patchData, 0, 0);
if (disposalType === 3) {
previousFrame = context.getImageData(0, 0, animWidth, animHeight);
}
context.drawImage(patchCanvas, left, top);
const imageData = context.getImageData(0, 0, animWidth, animHeight);
if (disposalType === 2) {
context.clearRect(0, 0, animWidth, animHeight);
} else if (disposalType === 3) {
context.putImageData(previousFrame, 0, 0);
}
const resource = PIXI.DOMAdapter.get().createCanvas(
imageData.width,
imageData.height
);
const resourceContext = resource.getContext("2d");
resourceContext.putImageData(imageData, 0, 0);
frames.push({
start: time,
end: time + delay,
texture: new PIXI.Texture({
source: new PIXI.CanvasSource(__spreadValues$1({
resource
}, canvasSourceOptions))
})
});
time += delay;
}
canvas.width = canvas.height = 0;
patchCanvas.width = patchCanvas.height = 0;
return new GifSource(frames);
}
}
"use strict";
const GifAsset = {
extension: PIXI.ExtensionType.Asset,
detection: {
test: async () => true,
add: async (formats) => [...formats, "gif"],
remove: async (formats) => formats.filter((format) => format !== "gif")
},
loader: {
/** used for deprecation purposes */
name: "gifLoader",
id: "gif",
test: (url) => PIXI.path.extname(url) === ".gif" || url.startsWith("data:image/gif"),
load: async (url, asset) => {
const response = await PIXI.DOMAdapter.get().fetch(url);
const buffer = await response.arrayBuffer();
return GifSource.from(buffer, asset == null ? void 0 : asset.data);
},
unload: async (asset) => {
asset.destroy();
}
}
};
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __objRest = (source, exclude) => {
var target = {};
for (var prop in source)
if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
target[prop] = source[prop];
if (source != null && __getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(source)) {
if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
target[prop] = source[prop];
}
return target;
};
const _GifSprite = class _GifSprite extends PIXI.Sprite {
constructor(...args) {
const options = args[0] instanceof GifSource ? { source: args[0] } : args[0];
const _a = Object.assign(
{},
_GifSprite.defaultOptions,
options
), {
source,
fps,
loop,
animationSpeed,
autoPlay,
autoUpdate,
onComplete,
onFrameChange,
onLoop
} = _a, rest = __objRest(_a, [
"source",
"fps",
"loop",
"animationSpeed",
"autoPlay",
"autoUpdate",
"onComplete",
"onFrameChange",
"onLoop"
]);
super(__spreadValues({ texture: PIXI.Texture.EMPTY }, rest));
/**
* Animation playback speed multiplier.
* Higher values speed up the animation, lower values slow it down.
* @default 1
* @example
* ```ts
* const animation = new GifSprite({ source });
* animation.animationSpeed = 2; // 2x speed
* ```
* @see {@link GifSprite.play}
* @see {@link GifSprite.stop}
*/
this.animationSpeed = 1;
/**
* Whether to loop the animation.
* If `false`, the animation will stop after the last frame.
* @default true
* @example
* ```ts
* const animation = new GifSprite({ source, loop: false });
* ```
*/
this.loop = true;
/**
* The total duration of animation in milliseconds.
* This represents the length of one complete animation cycle.
* @example
* ```ts
* // Get animation duration
* const animation = new GifSprite({ source });
* console.log('Duration:', animation.duration); // e.g. 1000 for 1 second
* ```
* @readonly
* @default 0
* @remarks
* - Set during initialization from last frame's end time
* - Used for progress calculation and loop timing
* - Value is in milliseconds
* - Cannot be modified after creation
* @see {@link GifSprite.progress} For animation progress
* @see {@link GifSprite.currentFrame} For current frame number
*/
this.duration = 0;
/**
* Whether to start playing right away when created.
* If `false`, you must call {@link GifSprite.play} to start playback.
* @default true
* @example
* ```ts
* const animation = new GifSprite({ source, autoPlay: true });
* ```
* @see {@link GifSprite.play}
*/
this.autoPlay = true;
/**
* Dirty means the image needs to be redrawn. Set to `true` to force redraw.
* @advanced
*/
this.dirty = false;
/** The current frame number (zero-based index). */
this._currentFrame = 0;
/** `true` uses {@link Ticker.shared} to auto update animation time.*/
this._autoUpdate = false;
/** `true` if the instance is currently connected to {@link Ticker.shared} to auto update animation time. */
this._isConnectedToTicker = false;
/** If animation is currently playing. */
this._playing = false;
/** Current playback position in milliseconds. */
this._currentTime = 0;
this.onRender = () => this._updateFrame();
this.texture = source.textures[0];
this.duration = source.frames[source.frames.length - 1].end;
this._source = source;
this._playing = false;
this._currentTime = 0;
this._isConnectedToTicker = false;
Object.assign(this, {
fps,
loop,
animationSpeed,
autoPlay,
autoUpdate,
onComplete,
onFrameChange,
onLoop
});
this.currentFrame = 0;
if (autoPlay) {
this.play();
}
}
/**
* Stops the animation playback.
* Halts at the current frame and disconnects from the ticker if auto-updating.
* @example
* ```ts
* // Basic stop
* const animation = new GifSprite({ source });
* animation.stop();
*
* // Stop at specific frame
* animation.currentFrame = 5;
* animation.stop();
*
* // Stop and reset
* animation.currentFrame = 0;
* animation.stop();
* ```
* @remarks
* - Does nothing if animation is already stopped
* - Maintains current frame position
* - Disconnects from shared ticker if auto-updating
* - Can be resumed with play()
* @see {@link GifSprite.play} For resuming playback
* @see {@link GifSprite.currentFrame} For frame control
*/
stop() {
if (!this._playing) {
return;
}
this._playing = false;
if (this._autoUpdate && this._isConnectedToTicker) {
PIXI.Ticker.shared.remove(this.update, this);
this._isConnectedToTicker = false;
}
}
/**
* Starts or resumes animation playback.
* If animation is at the last frame and not looping, playback will restart from the beginning.
* @example
* ```ts
* // Basic playback
* const animation = new GifSprite({ source, autoPlay: false });
* animation.play();
*
* // Play after stopping
* animation.stop();
* animation.play(); // Resumes from current frame
*
* // Play with auto-updating disabled
* const animation = new GifSprite({
* source,
* autoPlay: false,
* autoUpdate: false
* });
* animation.play();
* app.ticker.add((ticker) => {
* animation.update(ticker);
* });
* ```
* @remarks
* - Does nothing if animation is already playing
* - Connects to shared ticker if autoUpdate is true
* - Restarts from beginning if at last frame of non-looping animation
* - Maintains current frame position otherwise
* @see {@link GifSprite.stop} For stopping playback
* @see {@link GifSprite.playing} For checking playback status
* @see {@link GifSprite.autoUpdate} For controlling automatic updates
*/
play() {
if (this._playing) {
return;
}
this._playing = true;
if (this._autoUpdate && !this._isConnectedToTicker) {
PIXI.Ticker.shared.add(this.update, this, PIXI.UPDATE_PRIORITY.HIGH);
this._isConnectedToTicker = true;
}
if (!this.loop && this.currentFrame === this._source.frames.length - 1) {
this._currentTime = 0;
}
}
/**
* Gets the current progress of the animation as a value between 0 and 1.
* Useful for tracking animation completion and implementing progress bars.
* @example
* ```ts
* // Basic progress tracking
* const animation = new GifSprite({ source });
* console.log('Progress:', Math.round(animation.progress * 100) + '%');
*
* // Update progress bar
* app.ticker.add(() => {
* progressBar.width = animation.progress * 200; // 200px total width
* });
*
* // Check if animation is near end
* if (animation.progress > 0.9) {
* console.log('Animation almost complete!');
* }
* ```
* @remarks
* - Returns 0 at start
* - Returns 1 when complete
* - Updates continuously during playback
* - Based on currentTime and total duration
* @readonly
* @see {@link GifSprite.duration} For total animation length
*/
get progress() {
return this._currentTime / this.duration;
}
/** `true` if the current animation is playing */
get playing() {
return this._playing;
}
/**
* Updates the object transform for rendering.
* This method is called automatically by the ticker if `autoUpdate` is enabled.
* Only updates if the animation is currently playing.
* > [!IMPORTANT] Call this manually when `autoUpdate` is set to `false` to control animation timing.
* @param ticker - Ticker instance used to calculate frame timing
* @example
* ```ts
* // Manual update with app ticker
* const animation = new GifSprite({
* source,
* autoUpdate: false
* });
*
* // Add to custom ticker
* app.ticker.add(() => {
* animation.update(app.ticker);
* });
*
* // Update with custom timing
* const customTicker = new Ticker();
* customTicker.add(() => {
* animation.update(customTicker);
* });
* ```
* @see {@link GifSprite.autoUpdate} For automatic update control
* @see {@link GifSprite.playing} For playback state
* @see {@link Ticker} For timing system details
*/
update(ticker) {
var _a, _b;
if (!this._playing) {
return;
}
const elapsed = this.animationSpeed * ticker.deltaTime / PIXI.Ticker.targetFPMS;
const currentTime = this._currentTime + elapsed;
const localTime = currentTime % this.duration;
const localFrame = this._source.frames.findIndex((frame) => frame.start <= localTime && frame.end > localTime);
if (currentTime >= this.duration) {
if (this.loop) {
this._currentTime = localTime;
this._updateFrameIndex(localFrame);
(_a = this.onLoop) == null ? void 0 : _a.call(this);
} else {
this._currentTime = this.duration;
this._updateFrameIndex(this.totalFrames - 1);
(_b = this.onComplete) == null ? void 0 : _b.call(this);
this.stop();
}
} else {
this._currentTime = localTime;
this._updateFrameIndex(localFrame);
}
}
/** Redraw the current frame, is necessary for the animation to work when */
_updateFrame() {
if (!this.dirty) {
return;
}
this.texture = this._source.frames[this._currentFrame].texture;
this.dirty = false;
}
/**
* Whether to use {@link Ticker.shared} to auto update animation time.
* Controls if the animation updates automatically using the shared ticker.
* @example
* ```ts
* // Using auto-update (default)
* const animation = new GifSprite({
* source,
* autoUpdate: true
* });
*
* // Manual updates
* const animation = new GifSprite({
* source,
* autoUpdate: false
* });
*
* // Custom update loop
* app.ticker.add(() => {
* animation.update(app.ticker);
* });
*
* // Switch update modes at runtime
* animation.autoUpdate = false; // Disconnect from shared ticker
* animation.autoUpdate = true; // Reconnect if playing
* ```
* @default true
* @see {@link GifSprite.update} For manual updating
* @see {@link Ticker.shared} For the shared ticker instance
*/
get autoUpdate() {
return this._autoUpdate;
}
set autoUpdate(value) {
if (value !== this._autoUpdate) {
this._autoUpdate = value;
if (!this._autoUpdate && this._isConnectedToTicker) {
PIXI.Ticker.shared.remove(this.update, this);
this._isConnectedToTicker = false;
} else if (this._autoUpdate && !this._isConnectedToTicker && this._playing) {
PIXI.Ticker.shared.add(this.update, this);
this._isConnectedToTicker = true;
}
}
}
/**
* Gets or sets the current frame number.
* Controls which frame of the GIF animation is currently displayed.
* @example
* ```ts
* // Get current frame
* const animation = new GifSprite({ source });
* console.log('Current frame:', animation.currentFrame);
*
* // Jump to specific frame
* animation.currentFrame = 5;
*
* // Reset to first frame
* animation.currentFrame = 0;
*
* // Get frame at specific progress
* const frameAtProgress = Math.floor(animation.totalFrames * 0.5); // 50%
* animation.currentFrame = frameAtProgress;
* ```
* @throws {Error} If frame index is out of range
* @remarks
* - Zero-based index (0 to totalFrames-1)
* - Updates animation time to frame start
* - Triggers frame change callback
* - Marks sprite as dirty for redraw
* @see {@link GifSprite.totalFrames} For frame count
* @see {@link GifSprite.onFrameChange} For frame change events
*/
get currentFrame() {
return this._currentFrame;
}
set currentFrame(value) {
this._updateFrameIndex(value);
this._currentTime = this._source.frames[value].start;
}
/**
* The source GIF data containing frame textures and timing information.
* This represents the underlying animation data used by the sprite.
* @example
* ```ts
* // Access source data
* const animation = new GifSprite({ source });
* const frameCount = animation.source.totalFrames;
* const frameTexture = animation.source.textures[0];
*
* // Share source between sprites
* const clone = new GifSprite({
* source: animation.source,
* autoPlay: false
* });
*
* // Check source properties
* console.log('Total frames:', animation.source.totalFrames);
* console.log('Frame timing:', animation.source.frames);
* ```
* @remarks
* - Contains all frame textures
* - Manages frame timing data
* - Can be shared between sprites
* - Destroyed with sprite if destroyData=true
* @readonly
* @see {@link GifSource} For source data implementation
* @see {@link GifSprite.clone} For creating independent instances
*/
get source() {
return this._source;
}
/**
* Internally handle updating the frame index
* @param value
*/
_updateFrameIndex(value) {
var _a;
if (value < 0 || value >= this.totalFrames) {
throw new Error(`Frame index out of range, expecting 0 to ${this.totalFrames}, got ${value}`);
}
if (this._currentFrame !== value) {
this._currentFrame = value;
this.dirty = true;
(_a = this.onFrameChange) == null ? void 0 : _a.call(this, value);
}
}
/**
* Gets the total number of frames in the GIF animation.
* @example
* ```ts
* // Get total frames
* const animation = new GifSprite({ source });
* console.log('Total frames:', animation.totalFrames);
* ```
* @readonly
* @see {@link GifSprite.currentFrame} For current frame index
* @see {@link GifSource.totalFrames} For source frame count
*/
get totalFrames() {
return this._source.totalFrames;
}
/**
* Destroy and don't use after this.
* @param destroyData - Destroy the data, cannot be used again.
* @example
* ```ts
* const animation = new GifSprite({ source });
* // Do something with animation...
* animation.destroy(true); // Destroy the animation and its source data
*
* // If you want to keep the source data for reuse, use:
* animation.destroy(false); // Destroy the animation but keep source data
* ```
*/
destroy(destroyData = false) {
this.stop();
super.destroy();
if (destroyData) {
this._source.destroy();
}
const forceClear = null;
this._source = forceClear;
this.onComplete = forceClear;
this.onFrameChange = forceClear;
this.onLoop = forceClear;
}
/**
* Creates an independent copy of this GifSprite instance.
* Useful for creating multiple animations that share the same source data
* but can be controlled independently.
* > [!IMPORTANT]
* > The cloned sprite will have its own playback state, so you can play,
* > pause, or seek it without affecting the original sprite.
* @example
* ```ts
* // Create original animation
* const animation = new GifSprite({ source });
*
* // Create independent clone
* const clone = animation.clone();
* clone.play(); // Plays independently
* animation.stop(); // Original stops, clone continues
*
* // Clone with modified properties
* const halfSpeed = animation.clone();
* halfSpeed.animationSpeed = 0.5;
* ```
* @returns {GifSprite} A new GifSprite instance with the same properties
* @see {@link GifSprite.source} For shared source data
* @see {@link GifSprite.destroy} For cleanup
*/
clone() {
const clone = new _GifSprite({
source: this._source,
autoUpdate: this._autoUpdate,
loop: this.loop,
autoPlay: this.autoPlay,
animationSpeed: this.animationSpeed,
onComplete: this.onComplete,
onFrameChange: this.onFrameChange,
onLoop: this.onLoop
});
clone.dirty = true;
return clone;
}
};
/**
* Default configuration options for GifSprite instances.
*
* These values are used when specific options are not provided to the constructor.
* Each property can be overridden by passing it in the options object.
* @example
* ```ts
* GifSprite.defaultOptions.fps = 24; // Change default FPS to 24
* GifSprite.defaultOptions.loop = false; // Disable looping by default
*
* const animation = new GifSprite(); // Will use these defaults
* ```
*/
_GifSprite.defaultOptions = {
fps: 30,
loop: true,
animationSpeed: 1,
autoPlay: true,
autoUpdate: true,
onComplete: null,
onFrameChange: null,
onLoop: null
};
let GifSprite = _GifSprite;
"use strict";
"use strict";
PIXI.extensions.add(GifAsset);
"use strict";
exports.GifAsset = GifAsset;
exports.GifSource = GifSource;
exports.GifSprite = GifSprite;
return exports;
})({});
Object.assign(this.PIXI, gif_js);
//# sourceMappingURL=gif.js.map