🚀 gif creator
This commit is contained in:
1
packages/gif-creator/.gitignore
vendored
Normal file
1
packages/gif-creator/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
out.gif
|
||||||
42
packages/gif-creator/__tests__/createGif.spec.ts
Normal file
42
packages/gif-creator/__tests__/createGif.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { createGif } from "..";
|
||||||
|
import { generateGrid } from "@snk/compute/generateGrid";
|
||||||
|
import { computeBestRun } from "@snk/compute";
|
||||||
|
|
||||||
|
const drawOptions = {
|
||||||
|
sizeBorderRadius: 2,
|
||||||
|
sizeCell: 16,
|
||||||
|
sizeDot: 12,
|
||||||
|
colorBorder: "#1b1f230a",
|
||||||
|
colorDots: { 1: "#9be9a8", 2: "#40c463", 3: "#30a14e", 4: "#216e39" },
|
||||||
|
colorEmpty: "#ebedf0",
|
||||||
|
colorSnake: "purple",
|
||||||
|
};
|
||||||
|
|
||||||
|
const gameOptions = { maxSnakeLength: 5 };
|
||||||
|
|
||||||
|
const gifOptions = { delay: 200 };
|
||||||
|
|
||||||
|
it("should generate gif", async () => {
|
||||||
|
const grid = generateGrid(14, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
||||||
|
|
||||||
|
const snake = [
|
||||||
|
{ x: 4, y: -1 },
|
||||||
|
{ x: 3, y: -1 },
|
||||||
|
{ x: 2, y: -1 },
|
||||||
|
{ x: 1, y: -1 },
|
||||||
|
{ x: 0, y: -1 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const commands = computeBestRun(grid, snake, gameOptions).slice(0, 9);
|
||||||
|
|
||||||
|
const gif = await createGif(
|
||||||
|
grid,
|
||||||
|
snake,
|
||||||
|
commands,
|
||||||
|
drawOptions,
|
||||||
|
gameOptions,
|
||||||
|
gifOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(gif).toBeDefined();
|
||||||
|
});
|
||||||
@@ -14,7 +14,9 @@ const drawOptions = {
|
|||||||
|
|
||||||
const gameOptions = { maxSnakeLength: 5 };
|
const gameOptions = { maxSnakeLength: 5 };
|
||||||
|
|
||||||
const grid = generateGrid(14, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
const gifOptions = { delay: 100 };
|
||||||
|
|
||||||
|
const grid = generateGrid(42, 7, { colors: [1, 2, 3, 4], emptyP: 3 });
|
||||||
|
|
||||||
const snake = [
|
const snake = [
|
||||||
{ x: 4, y: -1 },
|
{ x: 4, y: -1 },
|
||||||
@@ -26,4 +28,8 @@ const snake = [
|
|||||||
|
|
||||||
const commands = computeBestRun(grid, snake, gameOptions);
|
const commands = computeBestRun(grid, snake, gameOptions);
|
||||||
|
|
||||||
createGif(grid, snake, commands, drawOptions, gameOptions);
|
createGif(grid, snake, commands, drawOptions, gameOptions, gifOptions).then(
|
||||||
|
(buffer) => {
|
||||||
|
process.stdout.write(buffer);
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -6,16 +6,17 @@ import { Point } from "@snk/compute/point";
|
|||||||
import { copySnake } from "@snk/compute/snake";
|
import { copySnake } from "@snk/compute/snake";
|
||||||
import { drawWorld } from "@snk/draw/drawWorld";
|
import { drawWorld } from "@snk/draw/drawWorld";
|
||||||
import { step } from "@snk/compute/step";
|
import { step } from "@snk/compute/step";
|
||||||
|
import * as tmp from "tmp";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as mkdirp from "mkdirp";
|
import * as execa from "execa";
|
||||||
|
|
||||||
export const createGif = (
|
export const createGif = async (
|
||||||
grid0: Grid,
|
grid0: Grid,
|
||||||
snake0: Point[],
|
snake0: Point[],
|
||||||
commands: Point[],
|
commands: Point[],
|
||||||
drawOptions: Parameters<typeof drawWorld>[4],
|
drawOptions: Parameters<typeof drawWorld>[4],
|
||||||
gameOptions: Parameters<typeof step>[4]
|
gameOptions: Parameters<typeof step>[4],
|
||||||
|
gifOptions: { delay: number }
|
||||||
) => {
|
) => {
|
||||||
const grid = copyGrid(grid0);
|
const grid = copyGrid(grid0);
|
||||||
const snake = copySnake(snake0);
|
const snake = copySnake(snake0);
|
||||||
@@ -24,14 +25,17 @@ export const createGif = (
|
|||||||
const width = drawOptions.sizeCell * (grid.width + 4);
|
const width = drawOptions.sizeCell * (grid.width + 4);
|
||||||
const height = drawOptions.sizeCell * (grid.height + 4) + 100;
|
const height = drawOptions.sizeCell * (grid.height + 4) + 100;
|
||||||
|
|
||||||
const dir = path.join(__dirname, "tmp", Math.random().toString(36).slice(2));
|
const { name: dir, removeCallback: cleanUp } = tmp.dirSync({
|
||||||
mkdirp.sync(dir);
|
unsafeCleanup: true,
|
||||||
|
});
|
||||||
|
|
||||||
const canvas = createCanvas(width, height);
|
const canvas = createCanvas(width, height);
|
||||||
const ctx = canvas.getContext("2d")!;
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
|
||||||
const writeImage = (i: number) => {
|
const writeImage = (i: number) => {
|
||||||
ctx.clearRect(0, 0, 99999, 99999);
|
ctx.clearRect(0, 0, 99999, 99999);
|
||||||
|
ctx.fillStyle = "#fff";
|
||||||
|
ctx.fillRect(0, 0, 99999, 99999);
|
||||||
drawWorld(ctx, grid, snake, stack, drawOptions);
|
drawWorld(ctx, grid, snake, stack, drawOptions);
|
||||||
|
|
||||||
const buffer = canvas.toBuffer("image/png", {
|
const buffer = canvas.toBuffer("image/png", {
|
||||||
@@ -39,17 +43,39 @@ export const createGif = (
|
|||||||
filters: canvas.PNG_FILTER_NONE,
|
filters: canvas.PNG_FILTER_NONE,
|
||||||
});
|
});
|
||||||
|
|
||||||
const filename = path.join(dir, `${i.toString().padStart(4, "0")}.png`);
|
const fileName = path.join(dir, `${i.toString().padStart(4, "0")}.png`);
|
||||||
|
|
||||||
console.log(filename);
|
fs.writeFileSync(fileName, buffer);
|
||||||
|
|
||||||
fs.writeFileSync(filename, buffer);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
writeImage(0);
|
try {
|
||||||
|
writeImage(0);
|
||||||
|
|
||||||
for (let i = 0; i < commands.length; i++) {
|
for (let i = 0; i < commands.length; i++) {
|
||||||
step(grid, snake, stack, commands[i], gameOptions);
|
step(grid, snake, stack, commands[i], gameOptions);
|
||||||
writeImage(i + 1);
|
writeImage(i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const outFileName = path.join(dir, "out.gif");
|
||||||
|
|
||||||
|
await execa(
|
||||||
|
"gm",
|
||||||
|
[
|
||||||
|
"convert",
|
||||||
|
["-loop", "0"],
|
||||||
|
["-delay", gifOptions.delay.toString()],
|
||||||
|
["-dispose", "2"],
|
||||||
|
// ["-layers", "OptimizeFrame"],
|
||||||
|
["-compress", "LZW"],
|
||||||
|
["-strip"],
|
||||||
|
|
||||||
|
path.join(dir, "*.png"),
|
||||||
|
outFileName,
|
||||||
|
].flat()
|
||||||
|
);
|
||||||
|
|
||||||
|
return fs.readFileSync(outFileName);
|
||||||
|
} finally {
|
||||||
|
cleanUp();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -5,13 +5,15 @@
|
|||||||
"@snk/compute": "1.0.0",
|
"@snk/compute": "1.0.0",
|
||||||
"@snk/draw": "1.0.0",
|
"@snk/draw": "1.0.0",
|
||||||
"canvas": "2.6.1",
|
"canvas": "2.6.1",
|
||||||
"mkdirp": "1.0.4"
|
"execa": "4.0.3",
|
||||||
|
"tmp": "0.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mkdirp": "1.0.1",
|
"@types/execa": "2.0.0",
|
||||||
|
"@types/tmp": "0.2.0",
|
||||||
"@zeit/ncc": "0.22.3"
|
"@zeit/ncc": "0.22.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "ncc run __tests__/dev.ts"
|
"dev": "ncc run __tests__/dev.ts --quiet | tail -n +2 > out.gif "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
56
yarn.lock
56
yarn.lock
@@ -617,6 +617,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||||
|
|
||||||
|
"@types/execa@2.0.0":
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/execa/-/execa-2.0.0.tgz#4dd5523520c51834bf8d0c280f460814e5238281"
|
||||||
|
integrity sha512-aBnkJ0r3khaZkHzu9pDZeWXrDg1N/ZtDGRQkK+KIqNVvvTvW+URXMUHQQCQMYdb2GPrcwu9Fq6l9iiT+pirIbg==
|
||||||
|
dependencies:
|
||||||
|
execa "*"
|
||||||
|
|
||||||
"@types/glob@^7.1.1":
|
"@types/glob@^7.1.1":
|
||||||
version "7.1.3"
|
version "7.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
|
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
|
||||||
@@ -682,13 +689,6 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
|
||||||
|
|
||||||
"@types/mkdirp@1.0.1":
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/mkdirp/-/mkdirp-1.0.1.tgz#0930b948914a78587de35458b86c907b6e98bbf6"
|
|
||||||
integrity sha512-HkGSK7CGAXncr8Qn/0VqNtExEE+PHMWb+qlR1faHMao7ng6P3tAaoWWBMdva0gL5h4zprjIO89GJOLXsMcDm1Q==
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/node@*":
|
"@types/node@*":
|
||||||
version "14.0.23"
|
version "14.0.23"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.23.tgz#676fa0883450ed9da0bb24156213636290892806"
|
||||||
@@ -738,6 +738,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
|
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74"
|
||||||
integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==
|
integrity sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==
|
||||||
|
|
||||||
|
"@types/tmp@0.2.0":
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.0.tgz#e3f52b4d7397eaa9193592ef3fdd44dc0af4298c"
|
||||||
|
integrity sha512-flgpHJjntpBAdJD43ShRosQvNC0ME97DCfGvZEDlAThQmnerRXrLbX6YgzRBQCZTthET9eAWFAMaYP0m0Y4HzQ==
|
||||||
|
|
||||||
"@types/uglify-js@*":
|
"@types/uglify-js@*":
|
||||||
version "3.9.3"
|
version "3.9.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.3.tgz#d94ed608e295bc5424c9600e6b8565407b6b4b6b"
|
resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.3.tgz#d94ed608e295bc5424c9600e6b8565407b6b4b6b"
|
||||||
@@ -2717,20 +2722,7 @@ exec-sh@^0.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5"
|
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5"
|
||||||
integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==
|
integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==
|
||||||
|
|
||||||
execa@^1.0.0:
|
execa@*, execa@4.0.3, execa@^4.0.0:
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
|
|
||||||
integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
|
|
||||||
dependencies:
|
|
||||||
cross-spawn "^6.0.0"
|
|
||||||
get-stream "^4.0.0"
|
|
||||||
is-stream "^1.1.0"
|
|
||||||
npm-run-path "^2.0.0"
|
|
||||||
p-finally "^1.0.0"
|
|
||||||
signal-exit "^3.0.0"
|
|
||||||
strip-eof "^1.0.0"
|
|
||||||
|
|
||||||
execa@^4.0.0:
|
|
||||||
version "4.0.3"
|
version "4.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
resolved "https://registry.yarnpkg.com/execa/-/execa-4.0.3.tgz#0a34dabbad6d66100bd6f2c576c8669403f317f2"
|
||||||
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
integrity sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==
|
||||||
@@ -2745,6 +2737,19 @@ execa@^4.0.0:
|
|||||||
signal-exit "^3.0.2"
|
signal-exit "^3.0.2"
|
||||||
strip-final-newline "^2.0.0"
|
strip-final-newline "^2.0.0"
|
||||||
|
|
||||||
|
execa@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
|
||||||
|
integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^6.0.0"
|
||||||
|
get-stream "^4.0.0"
|
||||||
|
is-stream "^1.1.0"
|
||||||
|
npm-run-path "^2.0.0"
|
||||||
|
p-finally "^1.0.0"
|
||||||
|
signal-exit "^3.0.0"
|
||||||
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
exit@^0.1.2:
|
exit@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||||
@@ -4907,7 +4912,7 @@ mixin-deep@^1.2.0:
|
|||||||
for-in "^1.0.2"
|
for-in "^1.0.2"
|
||||||
is-extendable "^1.0.1"
|
is-extendable "^1.0.1"
|
||||||
|
|
||||||
mkdirp@1.0.4, mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
|
mkdirp@1.x, mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
@@ -7014,6 +7019,13 @@ timers-browserify@^2.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
setimmediate "^1.0.4"
|
setimmediate "^1.0.4"
|
||||||
|
|
||||||
|
tmp@0.2.1:
|
||||||
|
version "0.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||||
|
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
|
||||||
|
dependencies:
|
||||||
|
rimraf "^3.0.0"
|
||||||
|
|
||||||
tmpl@1.0.x:
|
tmpl@1.0.x:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
|
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
|
||||||
|
|||||||
Reference in New Issue
Block a user