UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

173 lines (172 loc) 6.16 kB
"use strict"; import { ParamEvent } from "./../../poly/ParamEvent"; import { TypedCopNode } from "./_Base"; import { CoreLoaderTexture } from "../../../core/loader/Texture"; import { NodeParamsConfig, ParamConfig } from "../utils/params/ParamsConfig"; import { TextureParamsController, TextureParamConfig } from "./utils/TextureParamsController"; import { InputCloneMode } from "../../poly/InputCloneMode"; import { CanvasTexture } from "three"; import { parseGIF, decompressFrames } from "gifuct-js"; import { isBooleanTrue } from "../../../core/BooleanValue"; import { isUrlGif } from "../../../core/FileTypeController"; import { CopType } from "../../poly/registers/nodes/types/Cop"; import { EXTENSIONS_BY_NODE_TYPE_BY_CONTEXT } from "../../../core/loader/FileExtensionRegister"; import { NodeContext } from "../../poly/NodeContext"; export function GifCopParamConfig(Base) { return class Mixin extends Base { constructor() { super(...arguments); /** @param url to fetch the gif from */ this.url = ParamConfig.STRING("", { fileBrowse: { extensions: EXTENSIONS_BY_NODE_TYPE_BY_CONTEXT[NodeContext.COP][CopType.GIF] } }); /** @param reload the image */ this.reload = ParamConfig.BUTTON(null, { callback: (node, param) => { GifCopNode.PARAM_CALLBACK_reload(node); } }); /** @param play the gif */ this.play = ParamConfig.BOOLEAN(1, { cook: false, callback: (node) => { GifCopNode.PARAM_CALLBACK_gifUpdatePlay(node); } }); /** @param set the gif frame */ this.gifFrame = ParamConfig.INTEGER(0, { cook: false, range: [0, 100], rangeLocked: [true, false], callback: (node) => { GifCopNode.PARAM_CALLBACK_gifUpdateFrameIndex(node); } }); } }; } class GifCopParamsConfig extends TextureParamConfig(GifCopParamConfig(NodeParamsConfig)) { } const ParamsConfig = new GifCopParamsConfig(); export class GifCopNode extends TypedCopNode { constructor() { super(...arguments); this.paramsConfig = ParamsConfig; this.textureParamsController = new TextureParamsController(this); this._gifCanvasContext = null; this._tmpCanvasContext = null; this._parsedFrames = []; this._frameDelay = 100; this._frameIndex = 0; } static type() { return CopType.GIF; } initializeNode() { this.io.inputs.setCount(0, 1); this.io.inputs.initInputsClonedState(InputCloneMode.NEVER); } async cook(input_contents) { const url = this.pv.url; if (!isUrlGif(url)) { this.states.error.set("url is not an image"); } else { CoreLoaderTexture.incrementInProgressLoadsCount(); await CoreLoaderTexture.waitForMaxConcurrentLoadsQueueFreed(); const response = await fetch(url); const buffer = await response.arrayBuffer(); const gif = await parseGIF(buffer); const buildImagePatches = true; this._parsedFrames = await decompressFrames(gif, buildImagePatches); const firstFrame = this._parsedFrames[0]; this._frameDelay = firstFrame.delay; this._frameIndex = this.pv.gifFrame - 1; this._createCanvas(); const texture = this._gifCanvasElement ? new CanvasTexture(this._gifCanvasElement) : void 0; CoreLoaderTexture.decrementInProgressLoadsCount(url, texture); if (texture) { await this.textureParamsController.update(texture); this.setTexture(texture); } else { this.states.error.set("failed to create canvas"); } } } _createCanvas() { const gifFrame = this._parsedFrames[0]; this._gifCanvasElement = document.createElement("canvas"); this._tmpCanvasElement = document.createElement("canvas"); this._gifCanvasElement.width = gifFrame.dims.width; this._gifCanvasElement.height = gifFrame.dims.height; this._tmpCanvasElement.width = gifFrame.dims.width; this._tmpCanvasElement.height = gifFrame.dims.height; this._gifCanvasContext = this._gifCanvasElement.getContext("2d"); this._tmpCanvasContext = this._tmpCanvasElement.getContext("2d"); this._drawNextFrame(); } _drawOnCanvas() { if (!(this._gifCanvasContext && this._tmpCanvasElement && this._tmpCanvasContext)) { return; } let gifFrame = this._parsedFrames[this._frameIndex]; if (!gifFrame) { console.warn(`no frame at index ${this._frameIndex}, using last frame`); gifFrame = this._parsedFrames[this._parsedFrames.length - 1]; } if (gifFrame) { const dims = gifFrame.dims; if (!this._frameImageData || dims.width != this._frameImageData.width || dims.height != this._frameImageData.height) { this._tmpCanvasElement.width = dims.width; this._tmpCanvasElement.height = dims.height; this._frameImageData = this._tmpCanvasContext.createImageData(dims.width, dims.height); } this._frameImageData.data.set(gifFrame.patch); this._tmpCanvasContext.putImageData(this._frameImageData, 0, 0); this._gifCanvasContext.drawImage(this._tmpCanvasElement, dims.left, dims.top); const texture = this.containerController.container().texture(); if (!texture) { return; } texture.needsUpdate = true; } } _drawNextFrame() { this._frameIndex++; if (this._frameIndex >= this._parsedFrames.length) { this._frameIndex = 0; } this._drawOnCanvas(); if (isBooleanTrue(this.pv.play)) { setTimeout(() => { this._drawNextFrame(); }, this._frameDelay); } } gifUpdateFrameIndex() { this._frameIndex = this.pv.gifFrame; this._drawOnCanvas(); } // // // UTILS // // static PARAM_CALLBACK_reload(node) { node.paramCallbackReload(); } paramCallbackReload() { this.p.url.setDirty(); this.p.url.emit(ParamEvent.ASSET_RELOAD_REQUEST); } static PARAM_CALLBACK_gifUpdatePlay(node) { node.gifUpdatePlay(); } gifUpdatePlay() { if (isBooleanTrue(this.pv.play)) { this._drawNextFrame(); } } static PARAM_CALLBACK_gifUpdateFrameIndex(node) { node.gifUpdateFrameIndex(); } }