From 487ab075c677cd42f1c563c071892d293a3cad0d Mon Sep 17 00:00:00 2001 From: netpro2k <netpro2k@gmail.com> Date: Tue, 26 Jun 2018 19:23:20 -0700 Subject: [PATCH] WIP, got gif loading in a worker mostly working --- package.json | 4 +- src/components/image-plus.js | 73 +++++++++++++++++++++++++++++++- src/workers/gifparsing.worker.js | 73 ++++++++++++++++++++++++++++++++ webpack.config.js | 4 ++ 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 src/workers/gifparsing.worker.js diff --git a/package.json b/package.json index cae88c475..b1069ad16 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "device-detect": "^1.0.7", "event-target-shim": "^3.0.1", "form-urlencoded": "^2.0.4", + "gif-engine-js": "^1.0.1", "jsonschema": "^1.2.2", "mobile-detect": "^1.4.1", "moment": "^2.22.0", @@ -89,6 +90,7 @@ "style-loader": "^0.20.2", "webpack": "^4.0.1", "webpack-cli": "^2.0.9", - "webpack-dev-server": "^3.0.0" + "webpack-dev-server": "^3.0.0", + "worker-loader": "^2.0.0" } } diff --git a/src/components/image-plus.js b/src/components/image-plus.js index 942f02281..78189bd53 100644 --- a/src/components/image-plus.js +++ b/src/components/image-plus.js @@ -1,3 +1,34 @@ +import GIFWorker from "../workers/gifparsing.worker.js"; + +class GIFTexture extends THREE.Texture { + constructor(frames, delays) { + super(frames[0][0]); + this.generateMipmaps = false; + this.isVideoTexture = true; + this.minFilter = THREE.NearestFilter; + + this.frames = frames; + this.delays = delays; + + this.frame = 0; + this.frameStartTime = Date.now(); + } + + update() { + if (!this.frames || !this.delays) return; + + const now = Date.now(); + + if (now - this.frameStartTime > this.delays[this.frame]) { + this.frame = (this.frame + 1) % this.frames.length; + this.frameStartTime = now; + // console.log(this.gifData.frame, this.gifData.frames[this.gifData.frame][0]); + this.image = this.frames[this.frame][0]; + this.needsUpdate = true; + } + } +} + AFRAME.registerComponent("image-plus", { dependencies: ["geometry", "material"], @@ -68,7 +99,45 @@ AFRAME.registerComponent("image-plus", { this.billboardTarget.getWorldQuaternion(this.el.object3D.quaternion); }, - update() { - this.el.setAttribute("material", "src", this.data.src); + async update() { + // textureLoader.load( + // getProxyUrl(this.data.src), + // texture => { + // this.el.setAttribute("material", { + // transparent: true, + // src: texture + // }); + // }, + // function() { + // /* no-op */ + // }, + // function(xhr) { + // console.error("`$s` could not be fetched (Error code: %s; Response: %s)", xhr.status, xhr.statusText); + // } + // ); + + const json = await fetch("https://smoke-dev.reticulum.io/api/v1/media", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + media: { + url: this.data.src + } + }) + }).then(r => r.json()); + + const rawImageData = await fetch(json.images.raw, { mode: "cors" }).then(r => r.arrayBuffer()); + const worker = new GIFWorker(); + worker.onmessage = e => { + const [frames, delays, width, height] = e.data; + const material = this.el.components.material.material; + material.map = new GIFTexture(frames, delays); + material.transparent = true; + material.needsUpdate = true; + this._fit(width, height); + }; + worker.postMessage(rawImageData, [rawImageData]); } }); diff --git a/src/workers/gifparsing.worker.js b/src/workers/gifparsing.worker.js new file mode 100644 index 000000000..4648f1274 --- /dev/null +++ b/src/workers/gifparsing.worker.js @@ -0,0 +1,73 @@ +import { GIF } from "gif-engine-js"; + +const getDisposals = frameObj => (frameObj.graphicExtension && frameObj.graphicExtension.disposalMethod) || 0; +const getDelays = frameObj => (frameObj.graphicExtension && frameObj.graphicExtension.delay - 1) || 0; +const copyColorsTransparent = async (source, target, fWidth, fHeight, oLeft, oTop, cWidth, flag) => { + for (let row = 0, pointer = -1; fHeight > row; ++row) + for (let column = 0; fWidth > column; ++column) { + let offset = (column + oLeft + (row + oTop) * cWidth) * 4; + if (flag && source[pointer + 4] === 0) { + pointer += 4; + continue; + } + target[offset] = source[++pointer]; + target[++offset] = source[++pointer]; + target[++offset] = source[++pointer]; + ++pointer; + target[++offset] = flag ? source[pointer] : 255; + } +}; +const messageHandler = async e => { + try { + const o = await GIF(e.data); + const frameCount = o.frames.length; + const compiledFrames = new Array(frameCount); + const delays = o.frames.map(getDelays); + const canvasWidth = o.descriptor.width; + const canvasHeight = o.descriptor.height; + const disposals = o.frames.map(getDisposals); + const canvas = new Uint8ClampedArray(canvasWidth * canvasHeight * 4); + let index = 0; + do { + const frame = o.frames[index]; + const transparentColorFlag = frame.graphicExtension && frame.graphicExtension.transparentColorFlag; + const [ + { data: frameImageData, width: frameWidth, height: frameHeight }, + offsetLeft, + offsetTop + ] = await o.toImageData(index); + await copyColorsTransparent( + frameImageData, + canvas, + frameWidth, + frameHeight, + offsetLeft, + offsetTop, + canvasWidth, + transparentColorFlag + ); + const a = new Uint8ClampedArray(canvas); + compiledFrames[index] = [new ImageData(a, canvasWidth, canvasHeight)]; + if (disposals[index] === 2) { + for (let row = 0; frameHeight > row; ++row) { + for (let column = 0; frameWidth > column; ++column) { + let offset = (column + offsetLeft + (row + offsetTop) * canvasWidth) * 4; + canvas[offset] = 0; + canvas[++offset] = 0; + canvas[++offset] = 0; + canvas[++offset] = transparentColorFlag ? 0 : 255; + } + } + } + } while (++index < frameCount); + postMessage([compiledFrames, delays, canvasWidth, canvasHeight]); + } catch (er) { + console.error(er); + } +}; +(global => { + global.onmessage = messageHandler; + global.onerror = e => { + postMessage(["log", e]); + }; +})((() => self)()); diff --git a/webpack.config.js b/webpack.config.js index 6f66a85c9..cd9c6fc47 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -129,6 +129,10 @@ const config = { interpolate: "require" } }, + { + test: /\.worker\.js$/, + use: { loader: "worker-loader" } + }, { test: /\.js$/, include: [path.resolve(__dirname, "src")], -- GitLab