@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
173 lines (172 loc) • 6.16 kB
JavaScript
"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();
}
}