#javascript #math #canvas
Вопрос:
Контекст
Я создаю игровой клон с цветными пикселями, используя canvas
я сохраняю состояние холста внутри массива, который выглядит следующим образом:
[{"x":0,"y":0,"pickedColor":"white","colorCode":null},{"x":0,"y":1,"pickedColor":"white","colorCode":null},{"x":0,"y":2,"pickedColor":"white","colorCode":null},{"x":0,"y":3,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":4,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":5,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":6,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":7,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":8,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":9,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":10,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":11,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":12,"pickedColor":"#8bc34a","colorCode":null},{"x":0,"y":13,"pickedColor":"white","colorCode":null},{"x":0,"y":14,"pickedColor":"white","colorCode":null},{"x":0,"y":15,"pickedColor":"white","colorCode":null},{"x":0,"y":16,"pickedColor":"white","colorCode":null},{"x":0,"y":17,"pickedColor":"white","colorCode":null},{"x":0,"y":18,"pickedColor":"white","colorCode":null},{"x":0,"y":19,"pickedColor":"white","colorCode":null},{"x":1,"y":0,"pickedColor":"white","colorCode":null},{"x":1,"y":1,"pickedColor":"white","colorCode":null},{"x":1,"y":2,"pickedColor":"white","colorCode":null},{"x":1,"y":3,"pickedColor":"white","colorCode":null},{"x":1,"y":4,"pickedColor":"white","colorCode":null},{"x":1,"y":5,"pickedColor":"white","colorCode":null},{"x":1,"y":6,"pickedColor":"white","colorCode":null},{"x":1,"y":7,"pickedColor":"white","colorCode":null},{"x":1,"y":8,"pickedColor":"white","colorCode":null},{"x":1,"y":9,"pickedColor":"white","colorCode":null},{"x":1,"y":10,"pickedColor":"white","colorCode":null},{"x":1,"y":11,"pickedColor":"white","colorCode":null},{"x":1,"y":12,"pickedColor":"#8bc34a","colorCode":null},{"x":1,"y":13,"pickedColor":"#8bc34a","colorCode":null},{"x":1,"y":14,"pickedColor":"white","colorCode":null},{"x":1,"y":15,"pickedColor":"white","colorCode":null},{"x":1,"y":16,"pickedColor":"white","colorCode":null},{"x":1,"y":17,"pickedColor":"white","colorCode":null},{"x":1,"y":18,"pickedColor":"white","colorCode":null},{"x":1,"y":19,"pickedColor":"white","colorCode":null},{"x":2,"y":0,"pickedColor":"white","colorCode":null},{"x":2,"y":1,"pickedColor":"white","colorCode":null},{"x":2,"y":2,"pickedColor":"white","colorCode":null},{"x":2,"y":3,"pickedColor":"white","colorCode":null}]
Таким образом, каждая прямая кишка имеет x
свои координаты и y
координаты на ней.
Чтобы нарисовать прямую линию на экране, я использую эту функцию, чтобы рассчитать, насколько большой должна быть каждая «прямая линия», чтобы поместиться внутри границ холста:
// width / height comes from props and rectSize comes from props
const [rectCountX, setRectCountX] = useState(Math.floor(width / rectSize));
const [rectCountY, setRectCountY] = useState(Math.floor(height / rectSize));
Например width
, и height
может быть 800
, и 600
и то rectSize
может быть 30
.
Это вычисляет, сколько rects
я могу нарисовать в каждом направлении.
Вот как я рисую начальную доску:
const generateDrawingBoard = (ctx) => {
// Generate an Array of pixels that have all the things we need to redraw
for (var i = 0; i < rectCountX; i ) {
for (var j = 0; j < rectCountY; j ) {
// this is the quint essence whats saved in a huge array. 1000's of these pixels.
// With the help of this, we can redraw the whole canvas although canvas has not state or save functionality :)
const pixel = {
x: i,
y: j,
pickedColor: "white",
// we don't know the color code yet, we generate that afterwards
colorCode: null,
};
updateBoardData(pixel);
ctx.fillStyle = "white";
ctx.strokeRect(i * rectSize, j * rectSize, rectSize, rectSize);
}
}
};
Это прекрасно работает. Пользователь рисует холст и сохраняет его в базе данных.
Проблема
У меня есть pixelArtPreview
компоненты. Это получает данные из базы данных, и для каждого пиксельного рисунка он будет рисовать прямую линию, но меньшего размера, поэтому я могу разместить много прямых линий на странице, чтобы представить пользователю список пиксельных рисунков.
Поэтому мне нужно пересчитать rectSize
каждую прямую в массиве, чтобы вписаться в новую width
и height
. Это именно то, о чем я сейчас бьюсь головой.
Итак, вот компонент, о котором я упоминал:
import { useEffect, useRef, useState } from "react";
import { drawPixelArtFromState } from "../utils/drawPixelArtFromState";
const PixelArtPreview = ({ pixelArt }) => {
const canvasRef = useRef(null);
const [ctx, setCtx] = useState(null);
const [canvas, setCanvas] = useState(null);
useEffect(() => {
const canvas = canvasRef.current;
// This is where I scale the original size
// the whole pixelArt comes from the database and looks like this (example data):
// { pixelArtTitle: "some title", pixelArtWidth: 1234, pixelArtHeight: 1234, pixels: [... (the array I shows above with pixels)]}
canvas.width = pixelArt.pixelArtWidth * 0.5;
canvas.height = pixelArt.pixelArtHeight * 0.5;
setCanvas(canvas);
const context = canvas.getContext("2d");
setCtx(context);
}, []);
useEffect(() => {
if (!ctx) return;
drawPixelArtFromState(pixelArt, ctx);
}, [pixelArt, ctx]);
return <canvas className="m-4 border-4" ref={canvasRef} />;
};
export default PixelArtPreview;
Но волшебство происходит внутри импортированной функции drawPixelFromState(pixelArt, ctx)
Это указанная функция (с комментариями, каким был мой процесс):
export const drawPixelArtFromState = (pixelArt, ctx) => {
// how much pixels have been saved from the original scale when the art has been created
const canvasCount= JSON.parse(pixelArt.pixels).length;
// how many pixels we have on X
const xCount = JSON.parse(pixelArt.pixels)[canvasCount- 1].x;
// how many pixels we have on Y
const yCount = JSON.parse(pixelArt.pixels)[canvasCount- 1].y;
// total pixles (canvas height * canvas.width with the scale of 0.5 so it matches the canvas from the component before)
// this should give me all the pixels inside the canvas
const canvasPixelsCount =
pixelArt.pixelArtWidth * 0.5 * (pixelArt.pixelArtHeight * 0.5);
// now i try to find out how big each pixel has to be
const newRectSize = canvasPixelsCount / canvasCount;
// this is for example 230 rects which can't be I see only 2 rects on the canvas with that much of a rectSize
console.log(newRectSize);
// TODO: Parse it instantly where we fetch it
JSON.parse(pixelArt.pixels).forEach((pixel) => {
ctx.fillStyle = "white";
ctx.strokeRect(
pixel.x * newRectSize,
pixel.y * newRectSize,
newRectSize,
newRectSize
);
ctx.fillStyle = pixel.pickedColor;
ctx.fillRect(
pixel.x * newRectSize,
pixel.y * newRectSize,
newRectSize,
newRectSize
);
});
};
Вот как этот пример выглядит на экране (это 4 отдельных холста, которые видны на серой границе — я ожидаю увидеть всю пиксельную графику внутри маленького холста):
вопрос:
Мне нужно найти правильную формулу для вычисления нового rectSize
, чтобы все прямые в массиве могли поместиться внутри нового холста width
и height
. Возможно ли это вообще или мне нужно старое rectSize
, чтобы расчет работал?
Итак, TL;DR: насколько велика должна быть каждая прямая x, чтобы вместить все x прямых в y холст.
Большое спасибо!
Ответ №1:
Извините, ребята, после долгих часов борьбы, наконец, что-то щелкнуло. Расчет, который я сделал:
// now i try to find out how big each pixel has to be
const newRectSize = canvasPixelsCount / rectCount;
Дает мне площадь пикселя. Но мне нужна только одна его сторона (так canvas.fillRect
как она заботится только о значении x и заботится об этой области). Поэтому мне нужен квадратный корень из этого.
// now i try to find out how big each pixel has to be
const newRectSize = Math.sqrt(canvasPixelsCount / rectCount);
Теперь это работает идеально.
Скриншот: