diff --git a/src/components/MainContent.vue b/src/components/MainContent.vue index 5578e1e..c33a481 100644 --- a/src/components/MainContent.vue +++ b/src/components/MainContent.vue @@ -18,6 +18,7 @@ :style="{ transform: `scale(${store.zoomLevel.value})`, transformOrigin: 'top left', + imageRendering: 'pixelated', // Keep pixel art sharp when zooming }" > diff --git a/src/components/PreviewModal.vue b/src/components/PreviewModal.vue index 92a5dda..e07ea00 100644 --- a/src/components/PreviewModal.vue +++ b/src/components/PreviewModal.vue @@ -56,7 +56,7 @@
- +
diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index 4327c0b..665c2c3 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -83,9 +83,7 @@ -
- Border will only be visible in the preview and won't be included in the downloaded spritesheet. -
+
Border will only be visible in the preview and won't be included in the downloaded spritesheet.
diff --git a/src/composables/useSpritesheetStore.ts b/src/composables/useSpritesheetStore.ts index 1810f7b..40e870b 100644 --- a/src/composables/useSpritesheetStore.ts +++ b/src/composables/useSpritesheetStore.ts @@ -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(); + 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); } }