Pixel art fixes

This commit is contained in:
Dennis Postma 2025-04-03 04:23:02 +02:00
parent 1c7a7db299
commit 8fdb0dbae3
4 changed files with 74 additions and 47 deletions

View File

@ -18,6 +18,7 @@
:style="{
transform: `scale(${store.zoomLevel.value})`,
transformOrigin: 'top left',
imageRendering: 'pixelated', // Keep pixel art sharp when zooming
}"
></canvas>
</div>

View File

@ -56,7 +56,7 @@
</div>
<div class="flex justify-center bg-gray-700 p-6 rounded mb-6">
<canvas ref="animCanvas" class="block"></canvas>
<canvas ref="animCanvas" class="block" style="image-rendering: pixelated"></canvas>
</div>
</div>
</div>

View File

@ -83,9 +83,7 @@
</div>
</div>
<div class="text-xs text-gray-400 mt-2">
<i class="fas fa-info-circle mr-1"></i> Border will only be visible in the preview and won't be included in the downloaded spritesheet.
</div>
<div class="text-xs text-gray-400 mt-2"><i class="fas fa-info-circle mr-1"></i> Border will only be visible in the preview and won't be included in the downloaded spritesheet.</div>
</div>
</div>

View File

@ -45,7 +45,7 @@ const zoomLevel = ref(1); // Default zoom level (1 = 100%)
const previewBorder = reactive({
enabled: false,
color: '#ff0000', // Default red color
width: 2 // Default width in pixels
width: 2, // Default width in pixels
});
export function useSpritesheetStore() {
@ -144,8 +144,6 @@ export function useSpritesheetStore() {
const cols = columns.value;
const rows = Math.ceil(totalSprites / cols);
console.log(`Store: Updating canvas size for ${totalSprites} sprites, ${cols} columns, ${rows} rows`);
if (cellSize.width <= 0 || cellSize.height <= 0) {
console.error('Store: Invalid cell size for canvas update', cellSize);
return;
@ -156,7 +154,6 @@ export function useSpritesheetStore() {
// Ensure the canvas is large enough to display all sprites
if (canvas.value.width !== newWidth || canvas.value.height !== newHeight) {
console.log(`Store: Resizing canvas from ${canvas.value.width}x${canvas.value.height} to ${newWidth}x${newHeight}`);
canvas.value.width = newWidth;
canvas.value.height = newHeight;
@ -183,8 +180,6 @@ export function useSpritesheetStore() {
return;
}
console.log(`Store: Auto-arranging ${sprites.value.length} sprites with ${columns.value} columns`);
// First update the canvas size to ensure it's large enough
updateCanvasSize();
@ -195,9 +190,6 @@ export function useSpritesheetStore() {
sprite.x = column * cellSize.width;
sprite.y = row * cellSize.height;
// Log the position of each sprite for debugging
console.log(`Store: Sprite ${index} (${sprite.name}) positioned at (${sprite.x}, ${sprite.y})`);
});
// Check if the canvas is ready before attempting to render
@ -233,8 +225,6 @@ export function useSpritesheetStore() {
// Make sure the canvas size is correct before rendering
updateCanvasSize();
console.log(`Store: Rendering ${sprites.value.length} sprites on canvas ${canvas.value.width}x${canvas.value.height}`);
// Clear the canvas
ctx.value.clearRect(0, 0, canvas.value.width, canvas.value.height);
@ -242,6 +232,14 @@ export function useSpritesheetStore() {
drawGrid();
}
// First, collect all occupied cells
const occupiedCells = new Set<string>();
sprites.value.forEach(sprite => {
const cellX = Math.floor(sprite.x / cellSize.width);
const cellY = Math.floor(sprite.y / cellSize.height);
occupiedCells.add(`${cellX},${cellY}`);
});
// Draw each sprite - remove the zoom scaling from context
sprites.value.forEach((sprite, index) => {
try {
@ -253,33 +251,23 @@ export function useSpritesheetStore() {
// Check if sprite is within canvas bounds
if (sprite.x >= 0 && sprite.y >= 0 && sprite.x + sprite.width <= canvas.value!.width && sprite.y + sprite.height <= canvas.value!.height) {
if (sprite.img.complete && sprite.img.naturalWidth !== 0) {
// Draw the image at its original size
ctx.value!.drawImage(sprite.img, sprite.x, sprite.y, sprite.width, sprite.height);
// For pixel art, ensure we're drawing at exact pixel boundaries
const x = Math.round(sprite.x);
const y = Math.round(sprite.y);
// Draw border around the cell if enabled (preview only)
if (previewBorder.enabled) {
const cellX = Math.floor(sprite.x / cellSize.width) * cellSize.width;
const cellY = Math.floor(sprite.y / cellSize.height) * cellSize.height;
ctx.value!.strokeStyle = previewBorder.color;
ctx.value!.lineWidth = previewBorder.width / zoomLevel.value; // Adjust for zoom
ctx.value!.strokeRect(cellX, cellY, cellSize.width, cellSize.height);
}
// Draw the image at its original size with pixel-perfect rendering
ctx.value!.imageSmoothingEnabled = false; // Keep pixel art sharp
ctx.value!.drawImage(sprite.img, x, y, sprite.width, sprite.height);
} else {
console.warn(`Store: Sprite image ${index} not fully loaded, setting onload handler`);
sprite.img.onload = () => {
if (ctx.value && canvas.value) {
ctx.value.drawImage(sprite.img, sprite.x, sprite.y, sprite.width, sprite.height);
// For pixel art, ensure we're drawing at exact pixel boundaries
const x = Math.round(sprite.x);
const y = Math.round(sprite.y);
// Draw border around the cell if enabled (preview only)
if (previewBorder.enabled) {
const cellX = Math.floor(sprite.x / cellSize.width) * cellSize.width;
const cellY = Math.floor(sprite.y / cellSize.height) * cellSize.height;
ctx.value.strokeStyle = previewBorder.color;
ctx.value.lineWidth = previewBorder.width / zoomLevel.value; // Adjust for zoom
ctx.value.strokeRect(cellX, cellY, cellSize.width, cellSize.height);
}
ctx.value.imageSmoothingEnabled = false; // Keep pixel art sharp
ctx.value.drawImage(sprite.img, x, y, sprite.width, sprite.height);
}
};
}
@ -290,6 +278,28 @@ export function useSpritesheetStore() {
console.error(`Store: Error rendering sprite at index ${index}:`, spriteError);
}
});
// Draw borders around occupied cells if enabled (preview only)
if (previewBorder.enabled && occupiedCells.size > 0) {
ctx.value!.strokeStyle = previewBorder.color;
ctx.value!.lineWidth = previewBorder.width / zoomLevel.value; // Adjust for zoom
// Draw borders around each occupied cell
occupiedCells.forEach(cellKey => {
const [cellX, cellY] = cellKey.split(',').map(Number);
// Calculate pixel-perfect coordinates for the cell
// Add 0.5 to align with pixel boundaries for crisp lines
const x = Math.floor(cellX * cellSize.width) + 0.5;
const y = Math.floor(cellY * cellSize.height) + 0.5;
// Adjust width and height to ensure the border is inside the cell
const width = cellSize.width - 1;
const height = cellSize.height - 1;
ctx.value!.strokeRect(x, y, width, height);
});
}
} catch (error) {
console.error('Store: Error in renderSpritesheetPreview:', error);
}
@ -305,19 +315,21 @@ export function useSpritesheetStore() {
const visibleWidth = canvas.value.width / zoomLevel.value;
const visibleHeight = canvas.value.height / zoomLevel.value;
// Draw vertical lines
// Draw vertical lines - ensure pixel-perfect grid lines
for (let x = 0; x <= visibleWidth; x += cellSize.width) {
const pixelX = Math.floor(x) + 0.5; // Align to pixel boundary for crisp lines
ctx.value.beginPath();
ctx.value.moveTo(x, 0);
ctx.value.lineTo(x, visibleHeight);
ctx.value.moveTo(pixelX, 0);
ctx.value.lineTo(pixelX, visibleHeight);
ctx.value.stroke();
}
// Draw horizontal lines
// Draw horizontal lines - ensure pixel-perfect grid lines
for (let y = 0; y <= visibleHeight; y += cellSize.height) {
const pixelY = Math.floor(y) + 0.5; // Align to pixel boundary for crisp lines
ctx.value.beginPath();
ctx.value.moveTo(0, y);
ctx.value.lineTo(visibleWidth, y);
ctx.value.moveTo(0, pixelY);
ctx.value.lineTo(visibleWidth, pixelY);
ctx.value.stroke();
}
}
@ -380,8 +392,14 @@ export function useSpritesheetStore() {
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
// Ensure pixel art remains sharp in the downloaded file
tempCtx.imageSmoothingEnabled = false;
sprites.value.forEach(sprite => {
tempCtx.drawImage(sprite.img, sprite.x, sprite.y);
// Use rounded coordinates for pixel-perfect rendering
const x = Math.round(sprite.x);
const y = Math.round(sprite.y);
tempCtx.drawImage(sprite.img, x, y);
});
const link = document.createElement('a');
@ -435,16 +453,26 @@ export function useSpritesheetStore() {
const cellX = Math.floor(currentSprite.x / cellSize.width);
const cellY = Math.floor(currentSprite.y / cellSize.height);
const offsetX = currentSprite.x - cellX * cellSize.width;
const offsetY = currentSprite.y - cellY * cellSize.height;
// Calculate precise offset for pixel-perfect rendering
const offsetX = Math.round(currentSprite.x - cellX * cellSize.width);
const offsetY = Math.round(currentSprite.y - cellY * cellSize.height);
// Keep pixel art sharp
animation.ctx.imageSmoothingEnabled = false;
animation.ctx.drawImage(currentSprite.img, offsetX, offsetY);
// Draw border if enabled (only for preview, not included in download)
// Draw border around the cell if enabled (only for preview, not included in download)
if (previewBorder.enabled) {
animation.ctx.strokeStyle = previewBorder.color;
animation.ctx.lineWidth = previewBorder.width;
animation.ctx.strokeRect(0, 0, animation.canvas.width, animation.canvas.height);
// Use pixel-perfect coordinates for the border (0.5 offset for crisp lines)
const x = 0.5;
const y = 0.5;
const width = animation.canvas.width - 1;
const height = animation.canvas.height - 1;
animation.ctx.strokeRect(x, y, width, height);
}
}