From 0ccdfcdc52446dd04ddcfe7bd700a3d4f43c0997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Tue, 1 Nov 2022 15:26:21 +0000 Subject: [PATCH] feat: initial canvas support --- examples/web/index.ts | 4 ++ examples/web/react/app.tsx | 7 ++ .../web/react/components/canvas/canvas.css | 0 .../web/react/components/canvas/canvas.tsx | 70 +++++++++++++++++++ examples/web/react/components/index.ts | 1 + examples/web/react/components/tiles/tiles.tsx | 35 ++++++---- 6 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 examples/web/react/components/canvas/canvas.css create mode 100644 examples/web/react/components/canvas/canvas.tsx diff --git a/examples/web/index.ts b/examples/web/index.ts index 4bce7851..3261d747 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -524,6 +524,10 @@ class GameboyEmulator extends Observable implements Emulator { return this.fps; } + getTile(index: number): Uint8Array { + return this.gameBoy!.get_tile_buffer(index); + } + toggleRunning() { if (this.paused) { this.resume(); diff --git a/examples/web/react/app.tsx b/examples/web/react/app.tsx index 25c8ab4f..dbdafd88 100644 --- a/examples/web/react/app.tsx +++ b/examples/web/react/app.tsx @@ -21,6 +21,7 @@ import { PanelSplit, Paragraph, Section, + Tiles, Title, Toast } from "./components"; @@ -159,6 +160,8 @@ export interface Emulator extends ObservableI { */ getFramerate(): number; + getTile(index: number): Uint8Array; + /** * Boot (or reboots) the emulator according to the provided * set of options. @@ -443,6 +446,10 @@ export const App: FC<AppProps> = ({ emulator, backgrounds = ["264653"] }) => { onClearHandler={onClearHandler} onMinimize={onMinimize} /> + <Tiles + getTile={(index) => emulator.getTile(index)} + tileCount={384} + /> <KeyboardGB /> </div> } diff --git a/examples/web/react/components/canvas/canvas.css b/examples/web/react/components/canvas/canvas.css new file mode 100644 index 00000000..e69de29b diff --git a/examples/web/react/components/canvas/canvas.tsx b/examples/web/react/components/canvas/canvas.tsx new file mode 100644 index 00000000..b3c4df8f --- /dev/null +++ b/examples/web/react/components/canvas/canvas.tsx @@ -0,0 +1,70 @@ +import React, { FC, useEffect, useRef } from "react"; + +import "./canvas.css"; + +export type CanvasStructure = { + canvas: HTMLCanvasElement; + canvasContext: CanvasRenderingContext2D; + canvasImage: ImageData; + canvasBuffer: DataView; +}; + +type CanvasProps = { + width: number; + height: number; + scale?: number; + style?: string[]; + onCanvas?: (structure: CanvasStructure) => void; +}; + +export const Canvas: FC<CanvasProps> = ({ + width, + height, + scale = 1, + style = [], + onCanvas +}) => { + const classes = () => ["canvas", ...style].join(" "); + const canvasRef = useRef<HTMLCanvasElement>(null); + useEffect(() => { + if (canvasRef.current) { + const structure = initCanvas( + width, + width, + scale, + canvasRef.current + ); + onCanvas && onCanvas(structure); + } + }, [canvasRef]); + return ( + <canvas + ref={canvasRef} + className={classes()} + width={width} + height={height} + /> + ); +}; + +const initCanvas = ( + width: number, + height: number, + scale: number, + canvas: HTMLCanvasElement +): CanvasStructure => { + const canvasContext = canvas.getContext("2d")!; + canvasContext.imageSmoothingEnabled = false; + + const canvasImage = canvasContext.createImageData(width, height); + const canvasBuffer = new DataView(canvasImage.data.buffer); + + return { + canvas: canvas, + canvasContext: canvasContext, + canvasImage: canvasImage, + canvasBuffer: canvasBuffer + }; +}; + +export default Canvas; diff --git a/examples/web/react/components/index.ts b/examples/web/react/components/index.ts index 1de0da2b..838636b9 100644 --- a/examples/web/react/components/index.ts +++ b/examples/web/react/components/index.ts @@ -2,6 +2,7 @@ export * from "./button/button"; export * from "./button-container/button-container"; export * from "./button-increment/button-increment"; export * from "./button-switch/button-switch"; +export * from "./canvas/canvas"; export * from "./display/display"; export * from "./footer/footer"; export * from "./info/info"; diff --git a/examples/web/react/components/tiles/tiles.tsx b/examples/web/react/components/tiles/tiles.tsx index a6f522a4..e9e3cc4b 100644 --- a/examples/web/react/components/tiles/tiles.tsx +++ b/examples/web/react/components/tiles/tiles.tsx @@ -1,17 +1,30 @@ import React, { FC } from "react"; import { PixelFormat } from "../../app"; +import Canvas, { CanvasStructure } from "../canvas/canvas"; import "./tiles.css"; type TilesProps = { + getTile: (index: number) => Uint8Array; + tileCount: number; style?: string[]; }; -export const Tiles: FC<TilesProps> = ({ style = [] }) => { +export const Tiles: FC<TilesProps> = ({ getTile, tileCount, style = [] }) => { const classes = () => ["title", ...style].join(" "); + const onCanvas = (structure: CanvasStructure) => { + console.info("On canvas"); + setTimeout(() => { + for (let index = 0; index < 384; index++) { + const pixels = getTile(index); + console.info("VAI desenhar"); + drawTile(index, pixels, structure); + } + }, 1000); + }; return ( <div className={classes()}> - <canvas className="canvas-tiles" width="128" height="192"></canvas> + <Canvas width={128} height={192} scale={2} onCanvas={onCanvas} /> </div> ); }; @@ -23,24 +36,20 @@ export const Tiles: FC<TilesProps> = ({ style = [] }) => { * @param index The index of the sprite to be drawn. * @param pixels Buffer of pixels that contains the RGB data * that is going to be drawn. - * @param context The canvas context to which the tile is + * @param structure The canvas context to which the tile is * growing to be drawn. - * @param buffer The data buffer to be used in the drawing - * process, re-usage of it improves performance. * @param format The pixel format of the sprite. */ const drawTile = ( index: number, pixels: Uint8Array, - canvas: HTMLCanvasElement, - context: CanvasRenderingContext2D, - canvasImage: ImageData, - buffer: DataView, + structure: CanvasStructure, format: PixelFormat = PixelFormat.RGB ) => { const line = Math.floor(index / 16); const column = index % 16; - let offset = (line * canvas.width * 8 + column * 8) * PixelFormat.RGBA; + let offset = + (line * structure.canvas.width * 8 + column * 8) * PixelFormat.RGBA; let counter = 0; for (let i = 0; i < pixels.length; i += format) { const color = @@ -48,17 +57,17 @@ const drawTile = ( (pixels[i + 1] << 16) | (pixels[i + 2] << 8) | (format === PixelFormat.RGBA ? pixels[i + 3] : 0xff); - buffer.setUint32(offset, color); + structure.canvasBuffer.setUint32(offset, color); counter++; if (counter === 8) { counter = 0; - offset += (canvas.width - 7) * PixelFormat.RGBA; + offset += (structure.canvas.width - 7) * PixelFormat.RGBA; } else { offset += PixelFormat.RGBA; } } - context.putImageData(canvasImage, 0, 0); + structure.canvasContext.putImageData(structure.canvasImage, 0, 0); }; export default Tiles; -- GitLab