Пиксельный редактор
Следующий проект для изучения возможностей React+Redux - пиксельный редактор.
В пиксельном редакторе есть следующие инструменты для рисования:
- карадаш;
- ластик;
- прямоугольник;
- круг;
- прямая линия;
- заливка;
- выбор цвета.
А также дополнительные возможности:
- предпросмотр;
- сохранение изображения на компьютере пользователя.
Размер области для рисования - 25 х 28 пикселей.
Область рисования редактора первоначально была реализована путем добавления сетки из div-компонентов, где каждый div - представлял собой один пиксель будущего изображения. От этого способа пришлось отказаться, т.к. при рисовании прямоугольника стала заметна задержка рисования. Чтобы найти проблему, я измерила время обработки каждого события с помощью React Developer Tools.
Оказалось, что одно событиие mousemove и связанная с ним пачка событий выполняются примерно 311ms, что довольно много. При этом
290ms осуществляется обновление и перерисовка компонента Canvas (который на самом деле div) и его дочерних компонентов Cell (тех самых div-пикселей).
После перехода к обрасти рисования на канве (canvas), скорость отрисовки графических примитивов возросла в несколько раз. Это отражено и при замере скорости в React Developer Tools.
Сетка канвы для визуализации пикселей выполнена в CSS. Размер в backgroundSize - размер
ячейки.
const canvasStyle = {
backgroundSize: `${CELL_WIDTH}px ${CELL_WIDTH}px`
}
background: linear-gradient(to right, #464646 1px, transparent 1px),
linear-gradient(to bottom, #464646 1px, transparent 1px);
Для рисования графических примитивов реализованы следующие алгоритмы:
- алгоритм Брезенхема для рисования прямой линии;
- Midpoint алгоритм для рисования окружностей и эллипсов.
Кусок кода для расчета координат прямой линии по алгоритму Брезенхема:
...
if (Math.abs(startX - endX) > Math.abs(startY - endY)) {
const angularRatio = Math.abs((endY - startY) / (endX - startX));
const xCoords = getCoordsOffset(startX, endX);
const yCoords = bresenhemLine(startY, endY, angularRatio, xCoords.length);
return filterCanvasBorder(
yCoords.map((item, inx) => ([item, xCoords[inx]])))
...
const bresenhemLine = (start, end, angularRatio, len) => {
let err = 0;
let coord = start;
return [start].concat(
Array(len - 1)
.fill()
.map((_) => {
err += angularRatio;
if (err >= 0.5) {
coord += (end - start) > 0 && (end - start) !== 0 ? 1 : -1;
err -= 1;
}
return coord
})
)
}
Нарисованное изображение сохраняется на компьютере пользователя в формате PNG.