Added mass position buttons
This commit is contained in:
parent
0a0838832e
commit
0ddb9f4918
92
src/App.vue
92
src/App.vue
@ -21,11 +21,33 @@
|
|||||||
|
|
||||||
<div v-if="sprites.length > 0" class="mt-8">
|
<div v-if="sprites.length > 0" class="mt-8">
|
||||||
<div class="flex flex-wrap items-center gap-6 mb-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>
|
<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" />
|
<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>
|
</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">
|
<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>
|
<i class="fas fa-download"></i>
|
||||||
<span>Download spritesheet</span>
|
<span>Download spritesheet</span>
|
||||||
@ -145,24 +167,24 @@
|
|||||||
canvas.width = maxWidth * columns.value;
|
canvas.width = maxWidth * columns.value;
|
||||||
canvas.height = maxHeight * rows;
|
canvas.height = maxHeight * rows;
|
||||||
|
|
||||||
// Apply pixel art optimization for the export canvas
|
// Disable image smoothing for pixel-perfect rendering
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
// Draw sprites
|
// Draw sprites with integer positions
|
||||||
sprites.value.forEach((sprite, index) => {
|
sprites.value.forEach((sprite, index) => {
|
||||||
const col = index % columns.value;
|
const col = index % columns.value;
|
||||||
const row = Math.floor(index / columns.value);
|
const row = Math.floor(index / columns.value);
|
||||||
|
|
||||||
const cellX = col * maxWidth;
|
const cellX = Math.floor(col * maxWidth);
|
||||||
const cellY = row * maxHeight;
|
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');
|
const link = document.createElement('a');
|
||||||
link.download = 'spritesheet.png';
|
link.download = 'spritesheet.png';
|
||||||
link.href = canvas.toDataURL('image/png');
|
link.href = canvas.toDataURL('image/png', 1.0); // Use maximum quality
|
||||||
link.click();
|
link.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,7 +298,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process each sprite
|
// 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) => {
|
jsonData.sprites.map(async (spriteData: any) => {
|
||||||
return new Promise<Sprite>(resolve => {
|
return new Promise<Sprite>(resolve => {
|
||||||
// Create image from base64
|
// Create image from base64
|
||||||
@ -311,12 +334,57 @@
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// Replace current sprites with imported ones
|
|
||||||
sprites.value = newSprites;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error importing JSON:', error);
|
console.error('Error importing JSON:', error);
|
||||||
alert('Failed to import JSON file. Please check the file format.');
|
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>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
canvas {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -194,18 +194,14 @@
|
|||||||
// Clear canvas
|
// Clear canvas
|
||||||
ctx.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
|
ctx.value.clearRect(0, 0, canvasRef.value.width, canvasRef.value.height);
|
||||||
|
|
||||||
// Set pixel art optimization
|
// Disable image smoothing
|
||||||
if (pixelPerfect.value) {
|
ctx.value.imageSmoothingEnabled = false;
|
||||||
ctx.value.imageSmoothingEnabled = false;
|
|
||||||
} else {
|
|
||||||
ctx.value.imageSmoothingEnabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw grid
|
// Draw grid
|
||||||
ctx.value.strokeStyle = '#e5e7eb';
|
ctx.value.strokeStyle = '#e5e7eb';
|
||||||
for (let col = 0; col < props.columns; col++) {
|
for (let col = 0; col < props.columns; col++) {
|
||||||
for (let row = 0; row < rows; row++) {
|
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 col = index % props.columns;
|
||||||
const row = Math.floor(index / props.columns);
|
const row = Math.floor(index / props.columns);
|
||||||
|
|
||||||
const cellX = col * maxWidth;
|
const cellX = Math.floor(col * maxWidth);
|
||||||
const cellY = row * maxHeight;
|
const cellY = Math.floor(row * maxHeight);
|
||||||
|
|
||||||
// Draw sprite
|
// Draw sprite using integer positions
|
||||||
ctx.value?.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
|
ctx.value?.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
|
|
||||||
// Add this after other refs
|
// Add this after other refs
|
||||||
const hiddenFrames = ref<number[]>([]);
|
const hiddenFrames = ref<number[]>([]);
|
||||||
|
const pixelPerfect = ref(true);
|
||||||
|
|
||||||
// Add these computed properties
|
// Add these computed properties
|
||||||
const visibleFrames = computed(() => props.sprites.filter((_, index) => !hiddenFrames.value.includes(index)));
|
const visibleFrames = computed(() => props.sprites.filter((_, index) => !hiddenFrames.value.includes(index)));
|
||||||
@ -189,6 +190,9 @@
|
|||||||
|
|
||||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||||
|
|
||||||
|
// Apply pixel art optimization consistently
|
||||||
|
ctx.value.imageSmoothingEnabled = !pixelPerfect.value;
|
||||||
|
|
||||||
// Set canvas size to just fit one sprite cell
|
// Set canvas size to just fit one sprite cell
|
||||||
previewCanvasRef.value.width = maxWidth;
|
previewCanvasRef.value.width = maxWidth;
|
||||||
previewCanvasRef.value.height = maxHeight;
|
previewCanvasRef.value.height = maxHeight;
|
||||||
@ -212,8 +216,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply pixel art optimization
|
// Keep pixel art optimization consistent throughout all drawing operations
|
||||||
ctx.value.imageSmoothingEnabled = false;
|
ctx.value.imageSmoothingEnabled = !pixelPerfect.value;
|
||||||
|
|
||||||
// Draw all sprites with transparency if enabled
|
// Draw all sprites with transparency if enabled
|
||||||
if (showAllSprites.value && props.sprites.length > 1) {
|
if (showAllSprites.value && props.sprites.length > 1) {
|
||||||
@ -329,17 +333,17 @@
|
|||||||
const mouseX = ((event.clientX - rect.left) / zoom.value) * scaleX;
|
const mouseX = ((event.clientX - rect.left) / zoom.value) * scaleX;
|
||||||
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
|
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
|
||||||
|
|
||||||
const deltaX = mouseX - dragStartX.value;
|
const deltaX = Math.round(mouseX - dragStartX.value);
|
||||||
const deltaY = mouseY - dragStartY.value;
|
const deltaY = Math.round(mouseY - dragStartY.value);
|
||||||
|
|
||||||
const sprite = props.sprites[currentFrameIndex.value];
|
const sprite = props.sprites[currentFrameIndex.value];
|
||||||
if (!sprite || sprite.id !== activeSpriteId.value) return;
|
if (!sprite || sprite.id !== activeSpriteId.value) return;
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
||||||
|
|
||||||
// Calculate new position with constraints
|
// Calculate new position with constraints and round to integers
|
||||||
let newX = spritePosBeforeDrag.value.x + deltaX;
|
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
||||||
let newY = spritePosBeforeDrag.value.y + deltaY;
|
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
||||||
|
|
||||||
// Constrain movement within cell
|
// Constrain movement within cell
|
||||||
newX = Math.max(0, Math.min(maxWidth - sprite.width, newX));
|
newX = Math.max(0, Math.min(maxWidth - sprite.width, newX));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user