UNPKG

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
/*! * 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