Added mass position buttons

This commit is contained in:
Dennis Postma 2025-04-06 01:21:19 +02:00
parent 0a0838832e
commit 0ddb9f4918
3 changed files with 98 additions and 30 deletions

View File

@ -21,11 +21,33 @@
<div v-if="sprites.length > 0" class="mt-8">
<div class="flex flex-wrap items-center gap-6 mb-8">
<div class="flex items-center space-x-3">
<div class="flex items-center space-x-1">
<label for="columns" class="text-gray-700 font-medium">Columns:</label>
<input id="columns" type="number" v-model="columns" min="1" max="10" class="w-20 px-3 py-2 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all" />
</div>
<!-- Add mass position buttons -->
<div class="flex items-center space-x-2">
<button @click="alignSprites('left')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Left">
<i class="fas fa-arrow-left"></i>
</button>
<button @click="alignSprites('center')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Center">
<i class="fas fa-arrows-left-right"></i>
</button>
<button @click="alignSprites('right')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Right">
<i class="fas fa-arrow-right"></i>
</button>
<button @click="alignSprites('top')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Top">
<i class="fas fa-arrow-up"></i>
</button>
<button @click="alignSprites('middle')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Middle">
<i class="fas fa-arrows-up-down"></i>
</button>
<button @click="alignSprites('bottom')" class="p-2 bg-gray-100 hover:bg-gray-200 text-gray-700 rounded-lg transition-colors" title="Align Bottom">
<i class="fas fa-arrow-down"></i>
</button>
</div>
<button @click="downloadSpritesheet" class="px-6 py-2.5 bg-blue-500 hover:bg-blue-600 text-white font-medium rounded-lg transition-colors flex items-center space-x-2">
<i class="fas fa-download"></i>
<span>Download spritesheet</span>
@ -145,24 +167,24 @@
canvas.width = maxWidth * columns.value;
canvas.height = maxHeight * rows;
// Apply pixel art optimization for the export canvas
// Disable image smoothing for pixel-perfect rendering
ctx.imageSmoothingEnabled = false;
// Draw sprites
// Draw sprites with integer positions
sprites.value.forEach((sprite, index) => {
const col = index % columns.value;
const row = Math.floor(index / columns.value);
const cellX = col * maxWidth;
const cellY = row * maxHeight;
const cellX = Math.floor(col * maxWidth);
const cellY = Math.floor(row * maxHeight);
ctx.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
ctx.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
});
// Create download link
// Create download link with PNG format
const link = document.createElement('a');
link.download = 'spritesheet.png';
link.href = canvas.toDataURL('image/png');
link.href = canvas.toDataURL('image/png', 1.0); // Use maximum quality
link.click();
};
@ -276,7 +298,8 @@
}
// Process each sprite
const newSprites = await Promise.all(
// Replace current sprites with imported ones
sprites.value = await Promise.all(
jsonData.sprites.map(async (spriteData: any) => {
return new Promise<Sprite>(resolve => {
// Create image from base64
@ -311,12 +334,57 @@
});
})
);
// Replace current sprites with imported ones
sprites.value = newSprites;
} catch (error) {
console.error('Error importing JSON:', error);
alert('Failed to import JSON file. Please check the file format.');
}
};
// Add new alignment function
const alignSprites = (position: 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom') => {
if (sprites.value.length === 0) return;
// Find max dimensions for the current column layout
let maxWidth = 0;
let maxHeight = 0;
sprites.value.forEach(sprite => {
maxWidth = Math.max(maxWidth, sprite.width);
maxHeight = Math.max(maxHeight, sprite.height);
});
sprites.value = sprites.value.map((sprite, index) => {
let x = sprite.x;
let y = sprite.y;
switch (position) {
case 'left':
x = 0;
break;
case 'center':
x = (maxWidth - sprite.width) / 2;
break;
case 'right':
x = maxWidth - sprite.width;
break;
case 'top':
y = 0;
break;
case 'middle':
y = (maxHeight - sprite.height) / 2;
break;
case 'bottom':
y = maxHeight - sprite.height;
break;
}
return { ...sprite, x, y };
});
};
</script>
<style>
canvas {
image-rendering: pixelated;
image-rendering: crisp-edges;
}
</style>

View File

@ -194,18 +194,14 @@
// Clear canvas
ctx.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
// Set pixel art optimization
if (pixelPerfect.value) {
ctx.value.imageSmoothingEnabled = false;
} else {
ctx.value.imageSmoothingEnabled = true;
}
// Disable image smoothing
ctx.value.imageSmoothingEnabled = false;
// Draw grid
ctx.value.strokeStyle = '#e5e7eb';
for (let col = 0; col < props.columns; col++) {
for (let row = 0; row < rows; row++) {
ctx.value.strokeRect(col * maxWidth, row * maxHeight, maxWidth, maxHeight);
ctx.value.strokeRect(Math.floor(col * maxWidth), Math.floor(row * maxHeight), maxWidth, maxHeight);
}
}
@ -214,11 +210,11 @@
const col = index % props.columns;
const row = Math.floor(index / props.columns);
const cellX = col * maxWidth;
const cellY = row * maxHeight;
const cellX = Math.floor(col * maxWidth);
const cellY = Math.floor(row * maxHeight);
// Draw sprite
ctx.value?.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
// Draw sprite using integer positions
ctx.value?.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
});
};

View File

@ -159,6 +159,7 @@
// Add this after other refs
const hiddenFrames = ref<number[]>([]);
const pixelPerfect = ref(true);
// Add these computed properties
const visibleFrames = computed(() => props.sprites.filter((_, index) => !hiddenFrames.value.includes(index)));
@ -189,6 +190,9 @@
const { maxWidth, maxHeight } = calculateMaxDimensions();
// Apply pixel art optimization consistently
ctx.value.imageSmoothingEnabled = !pixelPerfect.value;
// Set canvas size to just fit one sprite cell
previewCanvasRef.value.width = maxWidth;
previewCanvasRef.value.height = maxHeight;
@ -212,8 +216,8 @@
}
}
// Apply pixel art optimization
ctx.value.imageSmoothingEnabled = false;
// Keep pixel art optimization consistent throughout all drawing operations
ctx.value.imageSmoothingEnabled = !pixelPerfect.value;
// Draw all sprites with transparency if enabled
if (showAllSprites.value && props.sprites.length > 1) {
@ -329,17 +333,17 @@
const mouseX = ((event.clientX - rect.left) / zoom.value) * scaleX;
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
const deltaX = mouseX - dragStartX.value;
const deltaY = mouseY - dragStartY.value;
const deltaX = Math.round(mouseX - dragStartX.value);
const deltaY = Math.round(mouseY - dragStartY.value);
const sprite = props.sprites[currentFrameIndex.value];
if (!sprite || sprite.id !== activeSpriteId.value) return;
const { maxWidth, maxHeight } = calculateMaxDimensions();
// Calculate new position with constraints
let newX = spritePosBeforeDrag.value.x + deltaX;
let newY = spritePosBeforeDrag.value.y + deltaY;
// Calculate new position with constraints and round to integers
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
// Constrain movement within cell
newX = Math.max(0, Math.min(maxWidth - sprite.width, newX));