Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/2d/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@hex-engine/core": "0.5.2",
"@hex-engine/inspector": "0.5.2",
"@types/matter-js": "^0.10.7",
"gifken": "^2.1.1",
"layout-bmfont-text": "^1.3.4",
"matter-js": "^0.14.2",
"mem": "^6.0.1",
Expand Down
155 changes: 155 additions & 0 deletions packages/2d/src/Components/GIF.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { AnimationAPI, AnimationFrame } from './Animation';
import { useType } from '@hex-engine/core';
import gifken, { Gif } from 'gifken';
import Preloader from '../Preloader';

interface GIFInterface extends AnimationAPI<HTMLImageElement> {
getGif(): Gif,
drawCurrentFrame(context: CanvasRenderingContext2D, x?: number, y?: number): void;
}

/**
* A component which enables you to play and manipulate gifs in Hex Engine.
* @example
* import someGifFile from "./your.gif";
*
* export default function MyGif() {
* useType(MyGif);
*
* const gif = useNewComponent(() => GIF({
* url: someGifFile,
* width: 200,
* height: 200,
* fps: 20,
* loop: true
* }));
*
* gif.play()
*
* useDraw((context) => {
* gif.drawCurrentFrame(context);
* });
* }
*/
export default function GIF(options: {
url: string,
width: number,
height: number,
fps?: number,
loop?: boolean
}): GIFInterface {
useType(GIF);
let gif: Gif = new Gif();
let frames: AnimationFrame<HTMLImageElement>[] = [];
let play: boolean = false;
let currentFrameIndex: number = 0;

const loadPromise = load(options.url).then(async arrayBuffer => {
gif = gifken.Gif.parse(arrayBuffer);
frames = await getFrames({
gif,
width: options.width,
height: options.height,
})

setInterval(() => {
if (frames.length - 1 > currentFrameIndex && play) {
currentFrameIndex ++;
}

if(frames.length - 1 <= currentFrameIndex && options.loop && play) {
currentFrameIndex = 0;
}
}, 1000 / (options.fps || 25))
})

Preloader.addTask(() => loadPromise);

return {
getGif() {
return gif;
},
drawCurrentFrame(context: CanvasRenderingContext2D, x: number = 0, y: number = 0) {
if(frames.length !== 0) {
context.drawImage(frames[currentFrameIndex].data, x, y);
}
},
frames: frames,
loop: options.loop || false,
get currentFrameIndex() {
return currentFrameIndex;
},
get currentFrame() {
return frames[currentFrameIndex];
},
get currentFrameCompletion() {
if(frames.length !== 0) {
return currentFrameIndex / frames.length;
}

return 1;
},
pause() {
play = false;
},
resume() {
this.play();
},
play() {
play = true;
},
restart() {
currentFrameIndex = 0;
play = true;
},
goToFrame(frameNumber: number) {
currentFrameIndex = frameNumber;
},
}
}

/**
* Load a gif.
* @param url - gif url
*/
async function load(url: string) {
return await fetch(url)
.then((res) => res.arrayBuffer());
}

/**
* Get all frames of a gif.
* For the moment, it must use a tmp canvas to get each frames due to a bug of gitken.
* See this issues: https://github.com/aaharu/gifken/issues/22
*/
async function getFrames({ gif, width, height }: {
gif: Gif,
width?: number,
height?: number,
}): Promise<AnimationFrame<HTMLImageElement>[]> {
const canvas: HTMLCanvasElement = document.createElement("canvas");
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D ;
canvas.width = width || gif.width;
canvas.height = height || gif.height;
ctx.clearRect(0, 0, gif.width, gif.height);

// get all frames as img
const tmpImgs: HTMLImageElement[] = await Promise.all(gif.split(false).map((splited) => {
return new Promise<HTMLImageElement>((resolve, reject) => {
const img = new Image(width || gif.width, height || gif.height);
img.onload = () => resolve(img);
img.onerror = (e) => reject(e);
img.src = gifken.GifPresenter.writeToDataUrl(splited.writeToArrayBuffer());
})
}));

// draw frame to the tmp canvas and create the final frames.
return tmpImgs.map((img: HTMLImageElement) => {
ctx.drawImage(img, 0, 0);
const newImg = new Image();
newImg.src = canvas.toDataURL("image/gif");
return new AnimationFrame<HTMLImageElement>(newImg, {
duration: 0,
});
})
}
2 changes: 2 additions & 0 deletions packages/2d/src/Components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Font from "./Font";
import FontMetrics from "./FontMetrics";
import Gamepad from "./Gamepad";
import Geometry from "./Geometry";
import GIF from "./GIF";
import Image from "./Image";
import ImageFilter from "./ImageFilter";
import Keyboard from "./Keyboard";
Expand Down Expand Up @@ -42,6 +43,7 @@ export {
FontMetrics,
Gamepad,
Geometry,
GIF,
Image,
ImageFilter,
Keyboard,
Expand Down
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4180,6 +4180,11 @@ brorand@^1.0.1:
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=

browser-or-node@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/browser-or-node/-/browser-or-node-1.2.1.tgz#cd65172da6a7fd689c7a650d326bd2ad145419a7"
integrity sha512-sVIA0cysIED0nbmNOm7sZzKfgN1rpFmrqvLZaFWspaBAftfQcezlC81G6j6U2RJf4Lh66zFxrCeOsvkUXIcPWg==

browser-resolve@^1.11.3:
version "1.11.3"
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
Expand Down Expand Up @@ -6713,6 +6718,13 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"

gifken@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/gifken/-/gifken-2.1.1.tgz#24ce88dbcb43f557bb17ae1e2d91d5633b71041b"
integrity sha512-q8uBypvYwFfoJhNm+4xjKkj5AZiF8PtXW2VsddS3YpCdXtLa6nJRVQ6Aj0R51iWv4dl1Y3qqe1t+wz9viNhfVQ==
dependencies:
browser-or-node "^1.2.1"

github-slugger@^1.0.0, github-slugger@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508"
Expand Down