Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
808d8a7
feat(color): implement robust Color class
Crypt212 Apr 12, 2025
e5fa999
docs(color): add tweaks to color documentation
Crypt212 Apr 12, 2025
86322d3
fix(color): use substring method for setting hex instead of deprecated
Crypt212 Apr 13, 2025
5dbe092
test(color): refactored color tests
Crypt212 Apr 15, 2025
bbbbadc
docs(color): add documentation to the private member variables
Crypt212 Apr 15, 2025
a56d93d
feat: add DirtyRectangle class with tests and docs
Crypt212 Apr 15, 2025
a76f093
test: add full test suite for Validation
Crypt212 Apr 15, 2025
a1522ca
docs(validation): add markdown API documentation file
Crypt212 Apr 15, 2025
7daea99
refactor(dirty-rectangle): remove state type restriction, and update
Crypt212 Apr 15, 2025
8305003
refactor(dirty-rectangle): remove console logging
Crypt212 Apr 15, 2025
cd0dea7
feat: make Color class immutable and add caching for performance
Crypt212 Apr 19, 2025
c80661c
feat: integrate ColorClass and DirtyRectangle into CanvasGrid class
Crypt212 Apr 19, 2025
56c693c
refactor(history): finalize core implementation with fixes and docs
Crypt212 Apr 19, 2025
b35190d
Removed side docs to avoid confusion
Crypt212 May 3, 2025
66f5cd2
refactor(color): fix alpha handling in mix method
Crypt212 May 3, 2025
7f85583
refactor: renamed dirty rectangle to change region
Crypt212 May 3, 2025
8240e51
refactor: rename history-system to action-history, refactor code and …
Crypt212 May 3, 2025
cbcf168
refactor: rename layer-system to layer-manager, refine code, do fixes
Crypt212 May 3, 2025
bd90c5c
refactor: rename canvas-grid to pixel-layer, do fixes and tests
Crypt212 May 3, 2025
466b53f
refactor(action-history): modify addActionData method to add reference
Crypt212 May 3, 2025
43af8ca
docs(validation): refine the jsdocs documentation of validateNumber f…
Crypt212 May 3, 2025
5a4189f
tests(color): augment and enhance color test suite increased coverage…
Crypt212 May 3, 2025
63721be
fix(pixel-layer): fix history controling methods (undo and redo)
Crypt212 May 3, 2025
ff51352
refactor(services): rename and move ActionHistory to History service
Crypt212 May 18, 2025
06889a1
refactor(core): move and enhance PixelLayer with improved action hand…
Crypt212 May 18, 2025
4f9017d
refactor(services): enhance ChangeRegion with in-place merging and mo…
Crypt212 May 18, 2025
314b64c
minor change
Crypt212 May 18, 2025
e1cb022
refactor(pixel-layer): fix history manipulation and remove time prote…
Crypt212 May 18, 2025
d3c5176
HUUUUUUUGE
Crypt212 Jun 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Changelog

## [v0.5.0] - 2023-08-20
### Added
- History system with undo/redo
- Pencil, eraser, line, and bucket tools
- Renewed structure of the project andd add `#` imports

### Changed
- Optimized action cancellation (5x faster) and reduced memory usage in pixel layer by 30%

## [Unreleased]
### Added

- Tool management (WIP)
- Layer support (WIP)
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ A simple pixel editor web application that allows users to create and edit pixel
- [Overview](#overview)
- [Current Status](#current-status)
- [Planned Features](#planned-features)
- [Recent Improvements](#recent-improvements)
- [Installation](#installation)
- [Running Tests](#running-tests)
- [License](#license)
Expand Down Expand Up @@ -35,6 +36,16 @@ The `HistorySystem` module is implemented to manage the undo and redo actions.
- Editiable color palletes
- Responsive design

## Recent Improvements

### Performance Optimizations (v0.5)
- 🚀 Undo/redo system now handles 1000-step actions 5x faster (384ms → 65ms)
- 🧹 Reduced memory usage by 30% in history operations

### Core Enhancements
- Refactored to modular architecture (CanvasManager, ToolManager, etc.)
- Implemented robust history system with merge optimization

## Installation

To run the pixel editor locally, follow these steps:
Expand Down
110 changes: 110 additions & 0 deletions dist/core/algorithms/graphic-algorithms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
export function drawPixel({ x, y, diameter = 5, isSquare = false, setPixel }) {
diameter = Math.floor(diameter);
const radius = Math.floor(0.5 * diameter); // Pre-calculate radius
const radiusSquared = radius * radius; // Pre-calculate radius squared for performance
const startX = x - radius;
const startY = y - radius;
const endX = Math.max(x + 1, x + radius);
const endY = Math.max(y + 1, y + radius);
if (isSquare)
// For squared area
for (let currentY = startY; currentY < endY; currentY++)
for (let currentX = startX; currentX < endX; currentX++) {
setPixel(currentX, currentY);
}
else
// For circular area
for (let currentY = startY; currentY < endY; currentY++)
for (let currentX = startX; currentX < endX; currentX++) {
const dx = x - currentX - 0.5;
const dy = y - currentY - 0.5;
if (dx * dx + dy * dy <= radiusSquared) {
setPixel(currentX, currentY);
}
}
}
export function drawLine({ x0, y0, x1, y1, setPixel }) {
// Standard Bresenham's algorithm
const dx = Math.abs(x1 - x0);
const dy = Math.abs(y1 - y0);
const sx = x0 < x1 ? 1 : -1;
const sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
while (true) {
setPixel(x0, y0);
if (x0 === x1 && y0 === y1)
break;
const e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
export function drawVaryingThicknessLine({ x0, y0, x1, y1, thicknessFunction, setPixel }) {
const drawPrepLine = (x0, y0, dx, dy, width, initError, initWidth, direction) => {
const stepX = dx > 0 ? 1 : -1;
const stepY = dy > 0 ? 1 : -1;
dx *= stepX;
dy *= stepY;
const threshold = dx - 2 * dy;
const diagonalError = -2 * dx;
const stepError = 2 * dy;
const widthThreshold = 2 * width * Math.sqrt(dx * dx + dy * dy);
let error = direction * initError;
let y = y0;
let x = x0;
let thickness = dx + dy - direction * initWidth;
while (thickness <= widthThreshold) {
setPixel(x, y);
if (error > threshold) {
x -= stepX * direction;
error += diagonalError;
thickness += stepError;
}
error += stepError;
thickness -= diagonalError;
y += stepY * direction;
}
};
const drawLineRightLeftOctents = (x0, y0, x1, y1, thicknessFunction) => {
const stepX = x1 - x0 > 0 ? 1 : -1;
const stepY = y1 - y0 > 0 ? 1 : -1;
const dx = (x1 - x0) * stepX;
const dy = (y1 - y0) * stepY;
const threshold = dx - 2 * dy;
const diagonalError = -2 * dx;
const stepError = 2 * dy;
let error = 0;
let prepError = 0;
let y = y0;
let x = x0;
for (let i = 0; i < dx; i++) {
[1, -1].forEach((dir) => {
drawPrepLine(x, y, dx * stepX, dy * stepY, thicknessFunction(i) / 2, prepError, error, dir);
});
if (error > threshold) {
y += stepY;
error += diagonalError;
if (prepError > threshold) {
[1, -1].forEach((dir) => {
drawPrepLine(x, y, dx * stepX, dy * stepY, thicknessFunction(i) / 2, prepError + diagonalError + stepError, error, dir);
});
prepError += diagonalError;
}
prepError += stepError;
}
error += stepError;
x += stepX;
}
};
if (Math.abs(x1 - x0) < Math.abs(y1 - y0))
// if line is steep, flip along x = y axis, then do the function then flip the pixels again then draw
drawLineRightLeftOctents(y0, x0, y1, x1, thicknessFunction);
else
drawLineRightLeftOctents(x0, y0, x1, y1, thicknessFunction);
}
110 changes: 110 additions & 0 deletions dist/core/drawing-algorithms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
export function drawPixel({ x, y, diameter = 5, isSquare = false, setPixel }) {
diameter = Math.floor(diameter);
const radius = Math.floor(0.5 * diameter); // Pre-calculate radius
const radiusSquared = radius * radius; // Pre-calculate radius squared for performance
const startX = x - radius;
const startY = y - radius;
const endX = Math.max(x + 1, x + radius);
const endY = Math.max(y + 1, y + radius);
if (isSquare)
// For squared area
for (let currentY = startY; currentY < endY; currentY++)
for (let currentX = startX; currentX < endX; currentX++) {
setPixel(currentX, currentY);
}
else
// For circular area
for (let currentY = startY; currentY < endY; currentY++)
for (let currentX = startX; currentX < endX; currentX++) {
const dx = x - currentX - 0.5;
const dy = y - currentY - 0.5;
if (dx * dx + dy * dy <= radiusSquared) {
setPixel(currentX, currentY);
}
}
}
export function drawLine({ x0, y0, x1, y1, setPixel }) {
// Standard Bresenham's algorithm
const dx = Math.abs(x1 - x0);
const dy = Math.abs(y1 - y0);
const sx = x0 < x1 ? 1 : -1;
const sy = y0 < y1 ? 1 : -1;
let err = dx - dy;
while (true) {
setPixel(x0, y0);
if (x0 === x1 && y0 === y1)
break;
const e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
export function drawVaryingThicknessLine({ x0, y0, x1, y1, thicknessFunction, setPixel }) {
const drawPrepLine = (x0, y0, dx, dy, width, initError, initWidth, direction) => {
const stepX = dx > 0 ? 1 : -1;
const stepY = dy > 0 ? 1 : -1;
dx *= stepX;
dy *= stepY;
const threshold = dx - 2 * dy;
const diagonalError = -2 * dx;
const stepError = 2 * dy;
const widthThreshold = 2 * width * Math.sqrt(dx * dx + dy * dy);
let error = direction * initError;
let y = y0;
let x = x0;
let thickness = dx + dy - direction * initWidth;
while (thickness <= widthThreshold) {
setPixel(x, y);
if (error > threshold) {
x -= stepX * direction;
error += diagonalError;
thickness += stepError;
}
error += stepError;
thickness -= diagonalError;
y += stepY * direction;
}
};
const drawLineRightLeftOctents = (x0, y0, x1, y1, thicknessFunction) => {
const stepX = x1 - x0 > 0 ? 1 : -1;
const stepY = y1 - y0 > 0 ? 1 : -1;
const dx = (x1 - x0) * stepX;
const dy = (y1 - y0) * stepY;
const threshold = dx - 2 * dy;
const diagonalError = -2 * dx;
const stepError = 2 * dy;
let error = 0;
let prepError = 0;
let y = y0;
let x = x0;
for (let i = 0; i < dx; i++) {
[1, -1].forEach((dir) => {
drawPrepLine(x, y, dx * stepX, dy * stepY, thicknessFunction(i) / 2, prepError, error, dir);
});
if (error > threshold) {
y += stepY;
error += diagonalError;
if (prepError > threshold) {
[1, -1].forEach((dir) => {
drawPrepLine(x, y, dx * stepX, dy * stepY, thicknessFunction(i) / 2, prepError + diagonalError + stepError, error, dir);
});
prepError += diagonalError;
}
prepError += stepError;
}
error += stepError;
x += stepX;
}
};
if (Math.abs(x1 - x0) < Math.abs(y1 - y0))
// if line is steep, flip along x = y axis, then do the function then flip the pixels again then draw
drawLineRightLeftOctents(y0, x0, y1, x1, thicknessFunction);
else
drawLineRightLeftOctents(x0, y0, x1, y1, thicknessFunction);
}
1 change: 1 addition & 0 deletions dist/core/drawing-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
8 changes: 8 additions & 0 deletions dist/core/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export {};
//
// // Extension pattern
// declare module "./events" {
// interface EventTypes {
// "CUSTOM_TOOL_EVENT": { customData: any };
// }
// }
1 change: 1 addition & 0 deletions dist/core/interfaces/drawing-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {};
Loading