From ec7a11c9b6727fe1e71ddf163da050510f1cb8e3 Mon Sep 17 00:00:00 2001 From: Maciej Caderek Date: Sat, 22 Jan 2022 06:00:40 +0100 Subject: [PATCH] WIP --- package-lock.json | 24 ++++++++++++++- package.json | 4 ++- src/board/Board.ts | 30 +++++++++--------- src/board/fill/createGradient.ts | 28 ++++++++++++----- src/board/fill/createPattern.ts | 12 -------- src/board/layers/drawRectangle.ts | 12 +++++--- src/board/styles-board/avocado.ts | 2 +- src/board/styles-board/calm.ts | 2 +- src/board/styles-board/glass.ts | 40 ------------------------ src/board/styles-board/index.ts | 4 +-- src/board/styles-board/lichess.ts | 8 ++--- src/board/styles-board/temp.ts | 42 +++++++++++++++++++++++++ src/{gif => encoders}/GIF.ts | 0 src/encoders/MP4.ts | 37 ++++++++++++++++++++++ src/encoders/WebM.ts | 35 +++++++++++++++++++++ src/encoders/createAnimation.ts | 51 +++++++++++++++++++++++++++++++ src/gif/createSimpleGIF.ts | 41 ------------------------- src/main.ts | 18 +++++------ src/style.css | 8 ++++- src/types.ts | 3 +- 20 files changed, 262 insertions(+), 139 deletions(-) delete mode 100644 src/board/fill/createPattern.ts delete mode 100644 src/board/styles-board/glass.ts create mode 100644 src/board/styles-board/temp.ts rename src/{gif => encoders}/GIF.ts (100%) create mode 100644 src/encoders/MP4.ts create mode 100644 src/encoders/WebM.ts create mode 100644 src/encoders/createAnimation.ts delete mode 100644 src/gif/createSimpleGIF.ts diff --git a/package-lock.json b/package-lock.json index a8869bd..13b7fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,9 @@ "@types/gif.js": "^0.2.2", "chess.js": "^0.12.0", "gif.js": "^0.2.0", - "webfontloader": "^1.6.28" + "h264-mp4-encoder": "^1.0.12", + "webfontloader": "^1.6.28", + "webm-writer": "^1.0.0" }, "devDependencies": { "@types/node": "^17.0.8", @@ -323,6 +325,11 @@ "resolved": "https://registry.npmjs.org/gif.js/-/gif.js-0.2.0.tgz", "integrity": "sha1-YV5uN4iFDNOiDIX+nwlTnnhJA+g=" }, + "node_modules/h264-mp4-encoder": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz", + "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==" + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -480,6 +487,11 @@ "version": "1.6.28", "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", "integrity": "sha1-23hhKSU8tujq5UwvsF+HCvZnW64=" + }, + "node_modules/webm-writer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/webm-writer/-/webm-writer-1.0.0.tgz", + "integrity": "sha512-xafP4mzUqht03HBXP0Ov2YGsxfD08uncad9fQeshYwQXrcP6Z/4uxd1IUaGKqKigFPAgaD9xb6JEKA8SXLQMLA==" } }, "dependencies": { @@ -672,6 +684,11 @@ "resolved": "https://registry.npmjs.org/gif.js/-/gif.js-0.2.0.tgz", "integrity": "sha1-YV5uN4iFDNOiDIX+nwlTnnhJA+g=" }, + "h264-mp4-encoder": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/h264-mp4-encoder/-/h264-mp4-encoder-1.0.12.tgz", + "integrity": "sha512-xih3J+Go0o1RqGjhOt6TwXLWWGqLONRPyS8yoMu/RoS/S8WyEv4HuHp1KBsDDl8srZQ3gw9f95JYkCSjCuZbHQ==" + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -767,6 +784,11 @@ "version": "1.6.28", "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", "integrity": "sha1-23hhKSU8tujq5UwvsF+HCvZnW64=" + }, + "webm-writer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/webm-writer/-/webm-writer-1.0.0.tgz", + "integrity": "sha512-xafP4mzUqht03HBXP0Ov2YGsxfD08uncad9fQeshYwQXrcP6Z/4uxd1IUaGKqKigFPAgaD9xb6JEKA8SXLQMLA==" } } } diff --git a/package.json b/package.json index 85551e5..797858c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "@types/gif.js": "^0.2.2", "chess.js": "^0.12.0", "gif.js": "^0.2.0", - "webfontloader": "^1.6.28" + "h264-mp4-encoder": "^1.0.12", + "webfontloader": "^1.6.28", + "webm-writer": "^1.0.0" } } diff --git a/src/board/Board.ts b/src/board/Board.ts index 5557f30..cb55ae9 100644 --- a/src/board/Board.ts +++ b/src/board/Board.ts @@ -23,7 +23,7 @@ class Board { private squareSize: number = 84; private innerSize: number = 672; private borderWidth: number = 24; - private background: Promise | null = null; + private background: HTMLCanvasElement | null = null; private extraInfo: boolean = true; private scale: number = 1; private margin: number = 0; @@ -125,12 +125,18 @@ class Board { } async renderBackground() { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; + + canvas.width = this.size; + canvas.height = this.size + this.margin * 2; + const { background, dark, light, border, coords } = this.style; - await drawRectangle(this.tempCtx, this.width, this.height, 0, 0, border); + await drawRectangle(ctx, this.width, this.height, 0, 0, border); await drawRectangle( - this.tempCtx, + ctx, this.innerSize, this.innerSize, this.borderVisible ? this.borderWidth : 0, @@ -149,19 +155,12 @@ class Board { const x = file * this.squareSize + this.borderWidth; const y = rank * this.squareSize + this.borderWidth + this.margin; - await drawRectangle( - this.tempCtx, - this.squareSize, - this.squareSize, - x, - y, - style - ); + await drawRectangle(ctx, this.squareSize, this.squareSize, x, y, style); } } drawCoords( - this.tempCtx, + ctx, coords, this.squareSize, this.tiles, @@ -172,7 +171,7 @@ class Board { this.margin ); - this.background = createImageBitmap(this.tempCanvas); + this.background = canvas; } async frame( @@ -180,6 +179,7 @@ class Board { header: { [key: string]: string | undefined }, move: Move | null = null ) { + console.log("Preparing frame"); this.lastMove = move; this.boardData = boardData; @@ -193,10 +193,12 @@ class Board { this.tempCtx.clearRect(0, 0, this.size, this.size); if (this.background === null) { + console.log("Background rendering..."); await this.renderBackground(); + console.log("Background rendered"); } - this.tempCtx.drawImage((await this.background) as ImageBitmap, 0, 0); + this.tempCtx.drawImage((await this.background) as HTMLCanvasElement, 0, 0); if (boardData !== null) { if (this.lastMove) { diff --git a/src/board/fill/createGradient.ts b/src/board/fill/createGradient.ts index a972bb4..d507e0b 100644 --- a/src/board/fill/createGradient.ts +++ b/src/board/fill/createGradient.ts @@ -15,13 +15,27 @@ const createGradient = ( x: number, y: number ) => { - const [dirXStart, dirYStart, dirXStop, dirYStop] = gradientDirs[data.dir]; - const gradient = ctx.createLinearGradient( - x + dirXStart * width, - y + dirYStart * height, - x + dirXStop * width, - y + dirYStop * height - ); + let gradient: CanvasGradient; + + if (data.dir === "radial") { + const radius = Math.sqrt((width / 2) ** 2 + (height / 2) ** 2); + gradient = ctx.createRadialGradient( + x + width / 2, + y + height / 2, + radius, + x + width / 2, + y + height / 2, + 0 + ); + } else { + const [dirXStart, dirYStart, dirXStop, dirYStop] = gradientDirs[data.dir]; + gradient = ctx.createLinearGradient( + x + dirXStart * width, + y + dirYStart * height, + x + dirXStop * width, + y + dirYStop * height + ); + } const maxIndex = data.colors.length - 1; diff --git a/src/board/fill/createPattern.ts b/src/board/fill/createPattern.ts deleted file mode 100644 index 04fe6cb..0000000 --- a/src/board/fill/createPattern.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ImageData } from "../../types"; -import loadImage from "../loaders/loadImage"; - -const createPattern = async ( - ctx: CanvasRenderingContext2D, - data: ImageData -) => { - const img = await loadImage(data.src); - return ctx.createPattern(img, "repeat"); -}; - -export default createPattern; diff --git a/src/board/layers/drawRectangle.ts b/src/board/layers/drawRectangle.ts index 59bbef6..ab8af00 100644 --- a/src/board/layers/drawRectangle.ts +++ b/src/board/layers/drawRectangle.ts @@ -1,6 +1,6 @@ import { SquareStyle } from "../../types"; import createGradient from "../fill/createGradient"; -import createPattern from "../fill/createPattern"; +import loadImage from "../loaders/loadImage"; const drawRectangle = async ( ctx: CanvasRenderingContext2D, @@ -10,11 +10,15 @@ const drawRectangle = async ( y: number, squareStyle: SquareStyle ) => { + if (squareStyle.type === "image") { + const img = await loadImage(squareStyle.data.src); + ctx.drawImage(img, x, y, width, height); + return; + } + const fill = await (squareStyle.type === "solid" ? squareStyle.data.color - : squareStyle.type === "gradient" - ? createGradient(ctx, squareStyle.data, width, height, x, y) - : createPattern(ctx, squareStyle.data)); + : createGradient(ctx, squareStyle.data, width, height, x, y)); if (fill === null) { throw new Error("Cannot create canvas fill style"); diff --git a/src/board/styles-board/avocado.ts b/src/board/styles-board/avocado.ts index 1e3d589..6177d25 100644 --- a/src/board/styles-board/avocado.ts +++ b/src/board/styles-board/avocado.ts @@ -27,7 +27,7 @@ const style: Style = { border: { type: "solid", data: { - color: "#1f2b15", + color: "#2d3923", }, }, coords: { diff --git a/src/board/styles-board/calm.ts b/src/board/styles-board/calm.ts index 30b6d69..a40d62d 100644 --- a/src/board/styles-board/calm.ts +++ b/src/board/styles-board/calm.ts @@ -35,7 +35,7 @@ const style: Style = { type: "gradient", data: { dir: "diagonal-top", - colors: ["#70982b", "#008b7a"], + colors: ["#65a32e", "#007a80"], }, }, coords: { diff --git a/src/board/styles-board/glass.ts b/src/board/styles-board/glass.ts deleted file mode 100644 index f7985d0..0000000 --- a/src/board/styles-board/glass.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Style } from "../../types"; - -const style: Style = { - name: "Glass", - background: { - type: "solid", - data: { - color: "transparent", - }, - }, - dark: { - type: "solid", - data: { - color: "#ffffff33", - }, - }, - light: { - type: "solid", - data: { - color: "#ffffff99", - }, - }, - moveIndicator: { - type: "color", - data: "#00ffff55", - }, - border: { - type: "solid", - data: { - color: "#ffffff66", - }, - }, - coords: { - onLight: "#222", - onDark: "#ddd", - onBorder: "#ddd", - }, -}; - -export default style; diff --git a/src/board/styles-board/index.ts b/src/board/styles-board/index.ts index 9a21a4c..2497df2 100644 --- a/src/board/styles-board/index.ts +++ b/src/board/styles-board/index.ts @@ -1,21 +1,21 @@ import avocado from "./avocado"; import calm from "./calm"; import standard from "./standard"; -import glass from "./glass"; import kittens from "./kittens"; import lichess from "./lichess"; import lila from "./lila"; import mono from "./mono"; import peach from "./peach"; +import temp from "./temp"; export default { avocado, calm, - glass, kittens, lichess, lila, mono, peach, standard, + temp, }; diff --git a/src/board/styles-board/lichess.ts b/src/board/styles-board/lichess.ts index e3116d2..f37962f 100644 --- a/src/board/styles-board/lichess.ts +++ b/src/board/styles-board/lichess.ts @@ -27,13 +27,13 @@ const style: Style = { border: { type: "solid", data: { - color: "#896d56", + color: "#916655", }, }, coords: { - onLight: "#b58863", - onDark: "#f0d9b5", - onBorder: "#f0d9b5", + onLight: "#9c6f49", + onDark: "#f9f0e1", + onBorder: "#f9f0e1", }, }; diff --git a/src/board/styles-board/temp.ts b/src/board/styles-board/temp.ts new file mode 100644 index 0000000..67f936b --- /dev/null +++ b/src/board/styles-board/temp.ts @@ -0,0 +1,42 @@ +import { Style } from "../../types"; + +const style: Style = { + name: "Temp", + background: { + type: "gradient", + data: { + dir: "radial", + colors: ["#ff00ff", "#00ffff"], + }, + }, + dark: { + type: "image", + data: { + src: "https://placekitten.com/1024/1024", + }, + }, + light: { + type: "gradient", + data: { + dir: "radial", + colors: ["#ffffff11", "#ffff0099", "#ffff0099"], + }, + }, + moveIndicator: { + type: "hueShift", + data: 70, + }, + border: { + type: "solid", + data: { + color: "#2d3923", + }, + }, + coords: { + onLight: "#4d7a26", + onDark: "#ffffc4", + onBorder: "#ececa4", + }, +}; + +export default style; diff --git a/src/gif/GIF.ts b/src/encoders/GIF.ts similarity index 100% rename from src/gif/GIF.ts rename to src/encoders/GIF.ts diff --git a/src/encoders/MP4.ts b/src/encoders/MP4.ts new file mode 100644 index 0000000..8318b06 --- /dev/null +++ b/src/encoders/MP4.ts @@ -0,0 +1,37 @@ +import * as HME from "h264-mp4-encoder"; + +class MP4 { + private hme: Promise; + private encoder: HME.H264MP4Encoder | null = null; + + constructor(width: number, height: number) { + this.hme = HME.createH264MP4Encoder(); + this.setup(width, height); + } + + async setup(width: number, height: number) { + this.encoder = await this.hme; + this.encoder.width = width; + this.encoder.height = height; + this.encoder.initialize(); + } + + // add(frame: CanvasImageSource | string, delay: number) { + // // this.encoder?.addFrameRgba() + // } + + // async render(): Promise { + // const blob = await this.video.complete(); + + // const timestamp = Date.now(); + + // const file = new File([blob], `board_${timestamp}.webm`, { + // type: "video/webm", + // lastModified: timestamp, + // }); + + // return file; + // } +} + +export default MP4; diff --git a/src/encoders/WebM.ts b/src/encoders/WebM.ts new file mode 100644 index 0000000..b8a9d34 --- /dev/null +++ b/src/encoders/WebM.ts @@ -0,0 +1,35 @@ +// @ts-ignore +import WebMWriter from "webm-writer"; + +class WebM { + private video: WebMWriter; + + constructor() { + this.video = new WebMWriter({ + quality: 0.8, + fileWriter: null, + fd: null, + frameDuration: 1000, + transparent: false, + }); + } + + add(frame: CanvasImageSource | string, delay: number) { + this.video.addFrame(frame, delay); + } + + async render(): Promise { + const blob = await this.video.complete(); + + const timestamp = Date.now(); + + const file = new File([blob], `board_${timestamp}.webm`, { + type: "video/webm", + lastModified: timestamp, + }); + + return file; + } +} + +export default WebM; diff --git a/src/encoders/createAnimation.ts b/src/encoders/createAnimation.ts new file mode 100644 index 0000000..6848ea1 --- /dev/null +++ b/src/encoders/createAnimation.ts @@ -0,0 +1,51 @@ +import { Style } from "../types"; +import Board from "../board/Board"; +import Game from "../game/Game"; +import GIF from "./GIF"; +import WebM from "./WebM"; +// import MP4 from "./MP4"; + +const MOVE_TIME = 1000; + +const createAnimation = async ( + pgn: string, + style: Style, + size: number = 720, + format: "GIF" | "WebM" +) => { + const game = new Game().loadPGN(pgn); + const board = new Board(8).setStyle(style).setSize(size).hideBorder(); + const animation = + format === "GIF" ? new GIF(board.width, board.height, true) : new WebM(); + const header = game.getHeader(); + + await board.titleFrame(header); + board.render(); + animation.add(format === "GIF" ? board.toImgElement() : board.canvas, 5000); + + await board.frame(game.getBoardData(), header); + board.render(); + animation.add( + format === "GIF" ? board.toImgElement() : board.canvas, + MOVE_TIME + ); + + while (true) { + const move = game.next(); + + if (!move) { + break; + } + + await board.frame(game.getBoardData(), header, move); + board.render(); + animation.add( + format === "GIF" ? board.toImgElement() : board.canvas, + MOVE_TIME + ); + } + + return await animation.render(); +}; + +export default createAnimation; diff --git a/src/gif/createSimpleGIF.ts b/src/gif/createSimpleGIF.ts deleted file mode 100644 index 38141c2..0000000 --- a/src/gif/createSimpleGIF.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Style } from "./../types"; -import Board from "../board/Board"; -import Game from "../game/Game"; -import GIF from "./GIF"; - -const MOVE_TIME = 1000; - -const createSimpleGIF = async ( - pgn: string, - style: Style, - size: number = 720 -) => { - const game = new Game().loadPGN(pgn); - const board = new Board(8).setStyle(style).setSize(size).hideBorder(); - const gif = new GIF(board.width, board.height, true); - const header = game.getHeader(); - - await board.titleFrame(header); - board.render(); - gif.add(board.toImgElement(), 5000); - - await board.frame(game.getBoardData(), header); - board.render(); - gif.add(board.toImgElement(), MOVE_TIME); - - while (true) { - const move = game.next(); - - if (!move) { - break; - } - - await board.frame(game.getBoardData(), header, move); - board.render(); - gif.add(board.toImgElement(), MOVE_TIME); - } - - return await gif.render(); -}; - -export default createSimpleGIF; diff --git a/src/main.ts b/src/main.ts index 0299dbd..fd29630 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import Board from "./board/Board"; import styles from "./board/styles-board"; import Game from "./game/Game"; import pgns from "./test-data/pgns"; -import createSimpleGIF from "./gif/createSimpleGIF"; +import createAnimation from "./encoders/createAnimation"; // import { decompressPGN } from "./game/PGNHelpers"; import WebFont from "webfontloader"; @@ -21,10 +21,10 @@ const play = async (board: Board, pgn: string | null, interval: number) => { const header = game.getHeader(); - await board.titleFrame(header); - board.render(); + // await board.titleFrame(header); + // board.render(); await board.frame(game.getBoardData(), header); - await delay(interval * 3); + // await delay(interval * 3); board.render(); while (true) { @@ -44,7 +44,7 @@ const play = async (board: Board, pgn: string | null, interval: number) => { }; const createDownloadLink = async (pgn: string, style: Style) => { - const file = await createSimpleGIF(pgn, style, 720); + const file = await createAnimation(pgn, style, 720, "GIF"); const link = document.createElement("a"); link.innerText = "DOWNLOAD"; link.setAttribute("href", URL.createObjectURL(file)); @@ -62,7 +62,7 @@ const main = async () => { // const hash = window.location.hash; // const pgn = hash === "" ? null : decompressPGN(hash.slice(1)); - const pgn = pgns[pgns.length - 1]; + const pgn = pgns[2]; const board = new Board(8).setStyle(style).setSize(720).showBorder(); $app?.appendChild(board.canvas); @@ -72,9 +72,9 @@ const main = async () => { const interval = 1000; play(board, pgn, interval); - createDownloadLink(pgns[2], style).then((link) => { - document.body.appendChild(link); - }); + // createDownloadLink(pgns[2], style).then((link) => { + // document.body.appendChild(link); + // }); }; WebFont.load({ diff --git a/src/style.css b/src/style.css index 5c6cd09..dfccdd3 100644 --- a/src/style.css +++ b/src/style.css @@ -1,3 +1,9 @@ +* { + border: 0; + margin: 0; + padding: 0; +} + body { background-color: #111; background-image: url(background.png); @@ -15,7 +21,7 @@ body { height: 800px; */ /* width: 1024px; height: 1024px; */ - margin: 10px; box-shadow: 0 0 30px rgba(0, 0, 0, 0.5); border-radius: 5px; + max-width: 100%; } diff --git a/src/types.ts b/src/types.ts index c1b0b56..70452a5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,8 @@ export type GradientDir = | "horizontal" | "vertical" | "diagonal-top" - | "diagonal-bottom"; + | "diagonal-bottom" + | "radial"; export type GradientData = { dir: GradientDir;