From 89c2a7933778aa2a24e139645f9d6a390d71abb4 Mon Sep 17 00:00:00 2001 From: Gator Date: Mon, 26 Dec 2022 15:50:02 +0100 Subject: [PATCH] render picture : ratio and resizing to board --- src/components/CanvasBoard.vue | 18 ++- src/modules/automata.js | 215 --------------------------------- src/modules/picture.js | 10 ++ src/modules/renderer.js | 59 +++++---- 4 files changed, 50 insertions(+), 252 deletions(-) delete mode 100644 src/modules/automata.js diff --git a/src/components/CanvasBoard.vue b/src/components/CanvasBoard.vue index e132a18..cff9adb 100644 --- a/src/components/CanvasBoard.vue +++ b/src/components/CanvasBoard.vue @@ -15,9 +15,9 @@ create1dInitialState, create2dRandomGrid, } from "../modules/board.js"; + import { picToBoard } from "../modules/picture.js"; const store = globalStore(); - const available2dRules = { conway: conwayRules, overpopulation: overpopulationRules, @@ -149,12 +149,20 @@ // draw 2d automaton from an uploaded picture. // use the picture representation as an initial state - const draw2dPicture = () => { - store.renderer.renderImage(store.picture); - const newBoard = store.renderer.getResizedImageData( + const draw2dPicture = async () => { + store.renderer.renderImage( store.picture, - store.board + store.renderer.ctx, + store.renderer.width, + store.renderer.height ); + const resized = store.renderer.renderImage( + store.picture, + store.renderer.workCtx, + store.board.width, + store.board.height + ); + const newBoard = picToBoard(resized.data, store.board); store.board.grid = Object.freeze(newBoard); }; diff --git a/src/modules/automata.js b/src/modules/automata.js deleted file mode 100644 index b4f1ce3..0000000 --- a/src/modules/automata.js +++ /dev/null @@ -1,215 +0,0 @@ -// handles negative index and index bigger than its array length -function guard(index, arrayLength) { - if (index > arrayLength - 1) return 0; - if (index < 0) return arrayLength - 1; - return index; -} - -// get the next evolution of a 1D CA initial state -function evolve1d(state, rules) { - function getCell(index) { - const safeIndex = guard(index, state.length); - return state[safeIndex]; - } - const newState = state.map((_, x) => { - const cells = [getCell(x - 1), getCell(x), getCell(x + 1)]; - return rules[cells.join("")]; - }); - - return newState.map(Number); -} - -// create a 2D board from a 1D CA initial state -// function createBoard(state, rules, height) { -// function createBoardAcc(s, h, acc) { -// if (h === 0) return acc; -// const newState = evolve1d(s, rules); -// const newAcc = acc.concat([s]); -// return createBoardAcc(newState, h - 1, newAcc); -// } -// return createBoardAcc(state, height, []); -// } - -// performance "choke point" in full imperative -function createBoard(state, rules, max) { - var board = []; - let prevState = []; - for (let i = 0; i < max; i++) { - let nextState = []; - // use the passed initial step during first iteration - if (i == 0) { - nextState = evolve1d(state, rules); - } else { - nextState = evolve1d(prevState, rules); - } - board = board.concat([nextState]); - prevState = nextState; - } - return board; -} - -// Find the neighbor of a given cell in a 2D CA board -function getCellNeighbors(board, cellCoordinates) { - const [x, y] = cellCoordinates; - const rowLength = board[0].length; // caca? - - // handles board edges where the cell is missing neighbors - function getCell(xx, yy) { - const safeX = guard(xx, board.length); - const safeY = guard(yy, rowLength.length); - return board[safeX][safeY]; - } - - // the current cell is not included in the result - return [ - getCell(x - 1, y - 1), - getCell(x, y - 1), - getCell(x + 1, y - 1), - getCell(x - 1, y), - getCell(x + 1, y), - getCell(x - 1, y + 1), - getCell(x, y + 1), - getCell(x + 1, y - 1), - ]; -} - -// Sums the value of a cell's neighbors -function getNeighborsSum(cells) { - return cells.reduce((cell, acc) => cell + acc, 0); -} - -// Get the next evolution of a cell according to -// Conway's game of life rules -function conwayRules(cell, neighbors) { - // loneliness rule - if (cell === 1 && neighbors < 2) return 0; - // overpopulation rule - if (cell === 1 && neighbors > 3) return 0; - // born when three live neighbors rule - if (cell === 0 && neighbors === 3) return 1; - // the cell remains the same if none apply - return cell; -} - -// Get the next evolution of a cell according to -// Conway's game of life rules -function servietteRules(cell, neighbors) { - // loneliness rule - if (cell === 0 && [2, 3, 4].find((x) => x == neighbors)) return 1; - // the cell remains the same if none apply - return 0; -} - -// variation of the game of life where a -// cell comes to live if 6 neigbor cells are alive -function highLifeRules(cell, neighbors) { - // loneliness rule - if (cell === 1 && neighbors < 2) return 0; - // overpopulation rule - if (cell === 1 && neighbors > 3) return 0; - // born when three live neighbors rule - if (cell === 0 && neighbors === 2) return 1; - // highlife rules - if ((cell === 0 && neighbors === 3) || neighbors === 6) return 1; - // the cell remains the same if none apply - return cell; -} - -// variation on the game of life's rules, -// where the "three live neighbors" rule is ignored -function threebornRules(cell, neighbors) { - // loneliness rule - if (cell === 1 && neighbors < 2) return 0; - // overpopulation rule - if (cell === 1 && neighbors > 3) return 0; - // born when three live neighbors rule - return cell; -} - -// variation on the game of life's rules, -// where the loneliness rule is ignored -function lonelinessRules(cell, neighbors) { - // overpopulation rule - if (cell === 1 && neighbors > 3) return 0; - // born when three live neighbors rule - if (cell === 0 && neighbors === 3) return 1; - // the cell remains the same if none apply - return cell; -} - -// variation on the game of life's rules, -// where the overpopulation rule is ignored -function overpopulationRules(cell, neighbors) { - // loneliness rule - if (cell === 1 && neighbors < 2) return 0; - // born when three live neighbors rule - if (cell === 0 && neighbors === 3) return 1; - // the cell remains the same if none apply - return cell; -} - -// get the next evolution of a 2D CA initial state -// Rules : Moore neighborhood -function evolve2d(board, rulesFn) { - return board.map((row, x) => - row.map((cell, y) => { - const neighbors = getCellNeighbors(board, [x, y]); - const sum = getNeighborsSum(neighbors); - return rulesFn(cell, sum); - }) - ); -} - -function getDrawingValues(state, acc, cell) { - const d = cell.dimension; - return Object.keys(state).map((key) => { - const fillStyle = (() => { - if (state[key] === "1") return cell.liveColor; - return cell.deadColor; - })(); - - return { - move: [key * d, acc * d], - fill: [key * d, acc * d, d, d], - fillStyle, - }; - }); -} - -// Populates the first state with a single living cell in the center -function create1dStateOneCell(width) { - return [...Array(width)].map((cell, index) => { - if (index === Math.floor(width / 2)) return 1; - return 0; - }); -} - -// Populates the first state of a 1D CA with cells returned -// by initFn -function create1dState(width, initFn, args) { - return [...Array(width)].map(() => initFn(...args)); -} - -// Populates the first state of a 2D CA with cells returned -// by initFn -function create2dState(width, height, initFn, args) { - return [...Array(height)].map(() => - [...Array(width)].map(() => initFn(...args)) - ); -} - -export { - getDrawingValues, - create1dState, - create2dState, - createBoard, - create1dStateOneCell, - conwayRules, - overpopulationRules, - lonelinessRules, - threebornRules, - highLifeRules, - servietteRules, - evolve1d, - evolve2d, -}; diff --git a/src/modules/picture.js b/src/modules/picture.js index 18c86f5..a65e566 100644 --- a/src/modules/picture.js +++ b/src/modules/picture.js @@ -69,3 +69,13 @@ export function boardToPic(board) { }, img.data); return img; } + +// https://stackoverflow.com/questions/3332237/image-resizing-algorithm +export function scaleToTargetSize(w1, h1, w2, h2) { + const sourceRatio = w1 / h1; + const targetRatio = w2 / h2; + if (sourceRatio > targetRatio) { + return [w2, w2 / sourceRatio]; + } + return [h2 * sourceRatio, h2]; +} diff --git a/src/modules/renderer.js b/src/modules/renderer.js index 62517e0..864fdc2 100644 --- a/src/modules/renderer.js +++ b/src/modules/renderer.js @@ -1,44 +1,40 @@ -import { boardToPic, picToBoard } from "./picture.js"; +import { boardToPic, scaleToTargetSize } from "./picture.js"; + +function scaleAndApply(context, ratio, callback) { + context.save(); + // rescale + context.imageSmoothingEnabled = false; + context.scale(ratio, ratio); + // apply + callback(); + context.restore(); +} // draws the board representation on the canvas -async function render(board) { +function render(board) { const d = board.cellProperties.size; // bool to RGBA colors - const img = await boardToPic(board); - this.ctx.save(); - // rescale + const img = boardToPic(board); this.ctx.clearRect(0, 0, this.width, this.height); - this.workCtx.putImageData(img, 0, 0); - this.ctx.imageSmoothingEnabled = false; - this.ctx.scale(d, d); - // draw from work canvas - this.ctx.drawImage(this.workCanvas, 0, 0, this.width, this.height); - this.ctx.restore(); + scaleAndApply(this.ctx, d, () => { + this.workCtx.putImageData(img, 0, 0); + this.ctx.drawImage(this.workCanvas, 0, 0, this.width, this.height); + }); } // draw image on canvas -function renderImage(image) { - this.ctx.fillStyle = "black"; - this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); - this.ctx.drawImage( +function renderImage(image, ctx, tw, th) { + ctx.fillStyle = "black"; + ctx.fillRect(0, 0, tw, th); + const dimensions = scaleToTargetSize(image.width, image.height, tw, th); + ctx.drawImage( image, - Math.floor((this.canvas.width - image.width) / 2), - Math.floor((this.canvas.height - image.height) / 2), - image.width, - image.height + Math.floor((tw - dimensions[0]) / 2), + Math.floor((th - dimensions[1]) / 2), + dimensions[0], + dimensions[1] ); -} - -// resize image to board dimensions -function getResizedImageData(image, board) { - // draw the image on the work canvas with the dimensions of the board - this.workCtx.drawImage(image, 0, 0, board.width, board.height); - - // get the resized image data from work canvas - const resized = this.workCtx.getImageData(0, 0, board.width, board.height); - - // convert the image into a 2D board of boolean based on pixel value - return picToBoard(resized.data, board); + return ctx.getImageData(0, 0, tw, th); } function resize(width, height) { @@ -63,7 +59,6 @@ function Renderer() { this.ctx = null; this.refreshRate = 300; this.render = render; - this.getResizedImageData = getResizedImageData; this.renderImage = renderImage; this.reset = reset; this.resize = resize;