Compare commits
2 Commits
5c33e77595
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
| f7a01e6c92 | |||
| 590d76205f |
@@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
## [1.6.0] - 2025-11-18
|
## [1.6.0] - 2025-11-18
|
||||||
- Improved animation preview modal
|
- Improved animation preview modal
|
||||||
|
- Add toggle for negative spacing in cells
|
||||||
|
|
||||||
## [1.5.0] - 2025-11-17
|
## [1.5.0] - 2025-11-17
|
||||||
- Show offset values in sprite cells and in preview modal
|
- Show offset values in sprite cells and in preview modal
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted, toRef } from 'vue';
|
||||||
import FileUploader from './components/FileUploader.vue';
|
import FileUploader from './components/FileUploader.vue';
|
||||||
import SpriteCanvas from './components/SpriteCanvas.vue';
|
import SpriteCanvas from './components/SpriteCanvas.vue';
|
||||||
import Modal from './components/utilities/Modal.vue';
|
import Modal from './components/utilities/Modal.vue';
|
||||||
@@ -147,11 +147,13 @@
|
|||||||
import DarkModeToggle from './components/utilities/DarkModeToggle.vue';
|
import DarkModeToggle from './components/utilities/DarkModeToggle.vue';
|
||||||
import { useSprites } from './composables/useSprites';
|
import { useSprites } from './composables/useSprites';
|
||||||
import { useExport } from './composables/useExport';
|
import { useExport } from './composables/useExport';
|
||||||
|
import { useSettingsStore } from './stores/useSettingsStore';
|
||||||
import type { SpriteFile } from './types/sprites';
|
import type { SpriteFile } from './types/sprites';
|
||||||
|
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
const { sprites, columns, updateSpritePosition, updateSpriteCell, removeSprite, replaceSprite, addSprite, addSpriteWithResize, processImageFiles, alignSprites } = useSprites();
|
const { sprites, columns, updateSpritePosition, updateSpriteCell, removeSprite, replaceSprite, addSprite, addSpriteWithResize, processImageFiles, alignSprites } = useSprites();
|
||||||
|
|
||||||
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExport(sprites, columns);
|
const { downloadSpritesheet, exportSpritesheetJSON, importSpritesheetJSON, downloadAsGif, downloadAsZip } = useExport(sprites, columns, toRef(settingsStore, 'negativeSpacingEnabled'));
|
||||||
const isPreviewModalOpen = ref(false);
|
const isPreviewModalOpen = ref(false);
|
||||||
const isHelpModalOpen = ref(false);
|
const isHelpModalOpen = ref(false);
|
||||||
const isFeedbackModalOpen = ref(false);
|
const isFeedbackModalOpen = ref(false);
|
||||||
|
|||||||
@@ -18,6 +18,11 @@
|
|||||||
<input id="show-all-sprites" type="checkbox" v-model="showAllSprites" class="mr-2" />
|
<input id="show-all-sprites" type="checkbox" v-model="showAllSprites" class="mr-2" />
|
||||||
<label for="show-all-sprites" class="dark:text-gray-200">Compare sprites</label>
|
<label for="show-all-sprites" class="dark:text-gray-200">Compare sprites</label>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Negative spacing control -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<input id="negative-spacing" type="checkbox" v-model="settingsStore.negativeSpacingEnabled" class="mr-2 w-4 h-4" />
|
||||||
|
<label for="negative-spacing" class="dark:text-gray-200 text-sm sm:text-base">Negative spacing</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -157,6 +162,7 @@
|
|||||||
columns: toRef(props, 'columns'),
|
columns: toRef(props, 'columns'),
|
||||||
zoom,
|
zoom,
|
||||||
allowCellSwap,
|
allowCellSwap,
|
||||||
|
negativeSpacingEnabled: toRef(settingsStore, 'negativeSpacingEnabled'),
|
||||||
getMousePosition: (event, z) => canvas2D.getMousePosition(event, z),
|
getMousePosition: (event, z) => canvas2D.getMousePosition(event, z),
|
||||||
onUpdateSprite: (id, x, y) => emit('updateSprite', id, x, y),
|
onUpdateSprite: (id, x, y) => emit('updateSprite', id, x, y),
|
||||||
onUpdateSpriteCell: (id, newIndex) => emit('updateSpriteCell', id, newIndex),
|
onUpdateSpriteCell: (id, newIndex) => emit('updateSpriteCell', id, newIndex),
|
||||||
@@ -271,7 +277,7 @@
|
|||||||
function drawCanvas() {
|
function drawCanvas() {
|
||||||
if (!canvasRef.value || !canvas2D.ctx.value) return;
|
if (!canvasRef.value || !canvas2D.ctx.value) return;
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
const { maxWidth, maxHeight, negativeSpacing } = calculateMaxDimensions();
|
||||||
|
|
||||||
// Set canvas size
|
// Set canvas size
|
||||||
const rows = Math.max(1, Math.ceil(props.sprites.length / props.columns));
|
const rows = Math.max(1, Math.ceil(props.sprites.length / props.columns));
|
||||||
@@ -308,9 +314,10 @@
|
|||||||
const cellY = Math.floor(cellRow * maxHeight);
|
const cellY = Math.floor(cellRow * maxHeight);
|
||||||
|
|
||||||
// Draw all sprites with transparency in this cell
|
// Draw all sprites with transparency in this cell
|
||||||
|
// Position at bottom-right with negative spacing offset
|
||||||
props.sprites.forEach((sprite, spriteIndex) => {
|
props.sprites.forEach((sprite, spriteIndex) => {
|
||||||
if (spriteIndex !== cellIndex) {
|
if (spriteIndex !== cellIndex) {
|
||||||
canvas2D.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y, 0.3);
|
canvas2D.drawImage(sprite.img, cellX + negativeSpacing + sprite.x, cellY + negativeSpacing + sprite.y, 0.3);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -329,8 +336,8 @@
|
|||||||
const cellX = Math.floor(col * maxWidth);
|
const cellX = Math.floor(col * maxWidth);
|
||||||
const cellY = Math.floor(row * maxHeight);
|
const cellY = Math.floor(row * maxHeight);
|
||||||
|
|
||||||
// Draw sprite
|
// Draw sprite with negative spacing offset (bottom-right positioning)
|
||||||
canvas2D.drawImage(sprite.img, cellX + sprite.x, cellY + sprite.y);
|
canvas2D.drawImage(sprite.img, cellX + negativeSpacing + sprite.x, cellY + negativeSpacing + sprite.y);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Draw ghost sprite if we're dragging between cells
|
// Draw ghost sprite if we're dragging between cells
|
||||||
@@ -395,6 +402,7 @@
|
|||||||
watch(() => props.columns, drawCanvas);
|
watch(() => props.columns, drawCanvas);
|
||||||
watch(() => settingsStore.pixelPerfect, drawCanvas);
|
watch(() => settingsStore.pixelPerfect, drawCanvas);
|
||||||
watch(() => settingsStore.darkMode, drawCanvas);
|
watch(() => settingsStore.darkMode, drawCanvas);
|
||||||
|
watch(() => settingsStore.negativeSpacingEnabled, drawCanvas);
|
||||||
watch(showAllSprites, drawCanvas);
|
watch(showAllSprites, drawCanvas);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -209,6 +209,18 @@
|
|||||||
|
|
||||||
// Canvas drawing
|
// Canvas drawing
|
||||||
|
|
||||||
|
// Calculate negative spacing based on sprite dimensions
|
||||||
|
function calculateNegativeSpacing(): number {
|
||||||
|
if (!settingsStore.negativeSpacingEnabled || props.sprites.length === 0) return 0;
|
||||||
|
|
||||||
|
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
||||||
|
const minWidth = Math.min(...props.sprites.map(s => s.width));
|
||||||
|
const minHeight = Math.min(...props.sprites.map(s => s.height));
|
||||||
|
const widthDiff = maxWidth - minWidth;
|
||||||
|
const heightDiff = maxHeight - minHeight;
|
||||||
|
return Math.max(widthDiff, heightDiff);
|
||||||
|
}
|
||||||
|
|
||||||
function drawPreviewCanvas() {
|
function drawPreviewCanvas() {
|
||||||
if (!previewCanvasRef.value || !canvas2D.ctx.value || props.sprites.length === 0) return;
|
if (!previewCanvasRef.value || !canvas2D.ctx.value || props.sprites.length === 0) return;
|
||||||
|
|
||||||
@@ -216,33 +228,36 @@
|
|||||||
if (!currentSprite) return;
|
if (!currentSprite) return;
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
const cellWidth = maxWidth + negativeSpacing;
|
||||||
|
const cellHeight = maxHeight + negativeSpacing;
|
||||||
|
|
||||||
// Apply pixel art optimization
|
// Apply pixel art optimization
|
||||||
canvas2D.applySmoothing();
|
canvas2D.applySmoothing();
|
||||||
|
|
||||||
// Set canvas size to just fit one sprite cell
|
// Set canvas size to fit one sprite cell (expanded with negative spacing)
|
||||||
canvas2D.setCanvasSize(maxWidth, maxHeight);
|
canvas2D.setCanvasSize(cellWidth, cellHeight);
|
||||||
|
|
||||||
// Clear canvas
|
// Clear canvas
|
||||||
canvas2D.clear();
|
canvas2D.clear();
|
||||||
|
|
||||||
// Draw grid background (cell)
|
// Draw grid background (cell)
|
||||||
canvas2D.fillRect(0, 0, maxWidth, maxHeight, '#f9fafb');
|
canvas2D.fillRect(0, 0, cellWidth, cellHeight, '#f9fafb');
|
||||||
|
|
||||||
// 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) {
|
||||||
props.sprites.forEach((sprite, index) => {
|
props.sprites.forEach((sprite, index) => {
|
||||||
if (index !== currentFrameIndex.value && !hiddenFrames.value.includes(index)) {
|
if (index !== currentFrameIndex.value && !hiddenFrames.value.includes(index)) {
|
||||||
canvas2D.drawImage(sprite.img, sprite.x, sprite.y, 0.3);
|
canvas2D.drawImage(sprite.img, negativeSpacing + sprite.x, negativeSpacing + sprite.y, 0.3);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current sprite
|
// Draw current sprite with negative spacing offset
|
||||||
canvas2D.drawImage(currentSprite.img, currentSprite.x, currentSprite.y);
|
canvas2D.drawImage(currentSprite.img, negativeSpacing + currentSprite.x, negativeSpacing + currentSprite.y);
|
||||||
|
|
||||||
// Draw cell border
|
// Draw cell border
|
||||||
canvas2D.strokeRect(0, 0, maxWidth, maxHeight, '#e5e7eb', 1);
|
canvas2D.strokeRect(0, 0, cellWidth, cellHeight, '#e5e7eb', 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag functionality
|
// Drag functionality
|
||||||
@@ -257,9 +272,12 @@
|
|||||||
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
|
const mouseY = ((event.clientY - rect.top) / zoom.value) * scaleY;
|
||||||
|
|
||||||
const sprite = props.sprites[currentFrameIndex.value];
|
const sprite = props.sprites[currentFrameIndex.value];
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
|
||||||
// Check if click is on sprite
|
// Check if click is on sprite (accounting for negative spacing offset)
|
||||||
if (sprite && mouseX >= sprite.x && mouseX <= sprite.x + sprite.width && mouseY >= sprite.y && mouseY <= sprite.y + sprite.height) {
|
const spriteCanvasX = negativeSpacing + sprite.x;
|
||||||
|
const spriteCanvasY = negativeSpacing + sprite.y;
|
||||||
|
if (sprite && mouseX >= spriteCanvasX && mouseX <= spriteCanvasX + sprite.width && mouseY >= spriteCanvasY && mouseY <= spriteCanvasY + sprite.height) {
|
||||||
isDragging.value = true;
|
isDragging.value = true;
|
||||||
activeSpriteId.value = sprite.id;
|
activeSpriteId.value = sprite.id;
|
||||||
dragStartX.value = mouseX;
|
dragStartX.value = mouseX;
|
||||||
@@ -285,14 +303,17 @@
|
|||||||
if (!sprite || sprite.id !== activeSpriteId.value) return;
|
if (!sprite || sprite.id !== activeSpriteId.value) return;
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
const { maxWidth, maxHeight } = getMaxDimensions(props.sprites);
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
const cellWidth = maxWidth + negativeSpacing;
|
||||||
|
const cellHeight = maxHeight + negativeSpacing;
|
||||||
|
|
||||||
// Calculate new position with constraints and round to integers
|
// Calculate new position with constraints and round to integers
|
||||||
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
let newX = Math.round(spritePosBeforeDrag.value.x + deltaX);
|
||||||
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
let newY = Math.round(spritePosBeforeDrag.value.y + deltaY);
|
||||||
|
|
||||||
// Constrain movement within cell
|
// Constrain movement within expanded cell (allow negative values up to -negativeSpacing)
|
||||||
newX = Math.max(0, Math.min(maxWidth - sprite.width, newX));
|
newX = Math.max(-negativeSpacing, Math.min(cellWidth - negativeSpacing - sprite.width, newX));
|
||||||
newY = Math.max(0, Math.min(maxHeight - sprite.height, newY));
|
newY = Math.max(-negativeSpacing, Math.min(cellHeight - negativeSpacing - sprite.height, newY));
|
||||||
|
|
||||||
emit('updateSprite', activeSpriteId.value, newX, newY);
|
emit('updateSprite', activeSpriteId.value, newX, newY);
|
||||||
drawPreviewCanvas();
|
drawPreviewCanvas();
|
||||||
@@ -362,6 +383,7 @@
|
|||||||
watch(showAllSprites, drawPreviewCanvas);
|
watch(showAllSprites, drawPreviewCanvas);
|
||||||
watch(hiddenFrames, drawPreviewCanvas);
|
watch(hiddenFrames, drawPreviewCanvas);
|
||||||
watch(() => settingsStore.pixelPerfect, drawPreviewCanvas);
|
watch(() => settingsStore.pixelPerfect, drawPreviewCanvas);
|
||||||
|
watch(() => settingsStore.negativeSpacingEnabled, drawPreviewCanvas);
|
||||||
|
|
||||||
// Initial draw
|
// Initial draw
|
||||||
if (props.sprites.length > 0) {
|
if (props.sprites.length > 0) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface DragSpriteOptions {
|
|||||||
columns: Ref<number> | number;
|
columns: Ref<number> | number;
|
||||||
zoom?: Ref<number>;
|
zoom?: Ref<number>;
|
||||||
allowCellSwap?: Ref<boolean>;
|
allowCellSwap?: Ref<boolean>;
|
||||||
|
negativeSpacingEnabled?: Ref<boolean>;
|
||||||
getMousePosition: (event: MouseEvent, zoom?: number) => { x: number; y: number } | null;
|
getMousePosition: (event: MouseEvent, zoom?: number) => { x: number; y: number } | null;
|
||||||
onUpdateSprite: (id: string, x: number, y: number) => void;
|
onUpdateSprite: (id: string, x: number, y: number) => void;
|
||||||
onUpdateSpriteCell?: (id: string, newIndex: number) => void;
|
onUpdateSpriteCell?: (id: string, newIndex: number) => void;
|
||||||
@@ -44,6 +45,7 @@ export function useDragSprite(options: DragSpriteOptions) {
|
|||||||
const getColumns = () => (typeof options.columns === 'number' ? options.columns : options.columns.value);
|
const getColumns = () => (typeof options.columns === 'number' ? options.columns : options.columns.value);
|
||||||
const getZoom = () => options.zoom?.value ?? 1;
|
const getZoom = () => options.zoom?.value ?? 1;
|
||||||
const getAllowCellSwap = () => options.allowCellSwap?.value ?? false;
|
const getAllowCellSwap = () => options.allowCellSwap?.value ?? false;
|
||||||
|
const getNegativeSpacingEnabled = () => options.negativeSpacingEnabled?.value ?? false;
|
||||||
|
|
||||||
// Drag state
|
// Drag state
|
||||||
const isDragging = ref(false);
|
const isDragging = ref(false);
|
||||||
@@ -65,28 +67,47 @@ export function useDragSprite(options: DragSpriteOptions) {
|
|||||||
|
|
||||||
const calculateMaxDimensions = () => {
|
const calculateMaxDimensions = () => {
|
||||||
const sprites = getSprites();
|
const sprites = getSprites();
|
||||||
|
const negativeSpacingEnabled = getNegativeSpacingEnabled();
|
||||||
const base = getMaxDimensions(sprites);
|
const base = getMaxDimensions(sprites);
|
||||||
const maxWidth = Math.max(1, base.maxWidth, lastMaxWidth.value);
|
const baseMaxWidth = Math.max(1, base.maxWidth, lastMaxWidth.value);
|
||||||
const maxHeight = Math.max(1, base.maxHeight, lastMaxHeight.value);
|
const baseMaxHeight = Math.max(1, base.maxHeight, lastMaxHeight.value);
|
||||||
lastMaxWidth.value = maxWidth;
|
lastMaxWidth.value = baseMaxWidth;
|
||||||
lastMaxHeight.value = maxHeight;
|
lastMaxHeight.value = baseMaxHeight;
|
||||||
return { maxWidth, maxHeight };
|
|
||||||
|
// Calculate negative spacing based on sprite size differences
|
||||||
|
let negativeSpacing = 0;
|
||||||
|
if (negativeSpacingEnabled && sprites.length > 0) {
|
||||||
|
// Find the smallest sprite dimensions
|
||||||
|
const minWidth = Math.min(...sprites.map(s => s.width));
|
||||||
|
const minHeight = Math.min(...sprites.map(s => s.height));
|
||||||
|
// Negative spacing is the difference between max and min dimensions
|
||||||
|
const widthDiff = baseMaxWidth - minWidth;
|
||||||
|
const heightDiff = baseMaxHeight - minHeight;
|
||||||
|
negativeSpacing = Math.max(widthDiff, heightDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add negative spacing to expand each cell
|
||||||
|
const maxWidth = baseMaxWidth + negativeSpacing;
|
||||||
|
const maxHeight = baseMaxHeight + negativeSpacing;
|
||||||
|
return { maxWidth, maxHeight, negativeSpacing };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Computed sprite positions
|
// Computed sprite positions
|
||||||
const spritePositions = computed<SpritePosition[]>(() => {
|
const spritePositions = computed<SpritePosition[]>(() => {
|
||||||
const sprites = getSprites();
|
const sprites = getSprites();
|
||||||
const columns = getColumns();
|
const columns = getColumns();
|
||||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
const { maxWidth, maxHeight, negativeSpacing } = calculateMaxDimensions();
|
||||||
|
|
||||||
return sprites.map((sprite, index) => {
|
return sprites.map((sprite, index) => {
|
||||||
const col = index % columns;
|
const col = index % columns;
|
||||||
const row = Math.floor(index / columns);
|
const row = Math.floor(index / columns);
|
||||||
|
|
||||||
|
// With negative spacing, sprites are positioned at bottom-right of cell
|
||||||
|
// (spacing added to top and left)
|
||||||
return {
|
return {
|
||||||
id: sprite.id,
|
id: sprite.id,
|
||||||
canvasX: col * maxWidth + sprite.x,
|
canvasX: col * maxWidth + negativeSpacing + sprite.x,
|
||||||
canvasY: row * maxHeight + sprite.y,
|
canvasY: row * maxHeight + negativeSpacing + sprite.y,
|
||||||
cellX: col * maxWidth,
|
cellX: col * maxWidth,
|
||||||
cellY: row * maxHeight,
|
cellY: row * maxHeight,
|
||||||
width: sprite.width,
|
width: sprite.width,
|
||||||
@@ -162,7 +183,7 @@ export function useDragSprite(options: DragSpriteOptions) {
|
|||||||
if (!activeSpriteId.value) return;
|
if (!activeSpriteId.value) return;
|
||||||
const sprites = getSprites();
|
const sprites = getSprites();
|
||||||
const columns = getColumns();
|
const columns = getColumns();
|
||||||
const { maxWidth, maxHeight } = calculateMaxDimensions();
|
const { maxWidth, maxHeight, negativeSpacing } = calculateMaxDimensions();
|
||||||
|
|
||||||
// Use the sprite's current index in the array to calculate cell position
|
// Use the sprite's current index in the array to calculate cell position
|
||||||
const cellCol = spriteIndex % columns;
|
const cellCol = spriteIndex % columns;
|
||||||
@@ -170,11 +191,15 @@ export function useDragSprite(options: DragSpriteOptions) {
|
|||||||
const cellX = cellCol * maxWidth;
|
const cellX = cellCol * maxWidth;
|
||||||
const cellY = cellRow * maxHeight;
|
const cellY = cellRow * maxHeight;
|
||||||
|
|
||||||
const newX = mouseX - cellX - dragOffsetX.value;
|
// Calculate new position relative to cell origin (without the negative spacing offset)
|
||||||
const newY = mouseY - cellY - dragOffsetY.value;
|
// The sprite's x,y is stored relative to where it would be drawn after the negativeSpacing offset
|
||||||
|
const newX = mouseX - cellX - negativeSpacing - dragOffsetX.value;
|
||||||
|
const newY = mouseY - cellY - negativeSpacing - dragOffsetY.value;
|
||||||
|
|
||||||
const constrainedX = Math.floor(Math.max(0, Math.min(maxWidth - sprites[spriteIndex].width, newX)));
|
// The sprite can move within the full expanded cell area
|
||||||
const constrainedY = Math.floor(Math.max(0, Math.min(maxHeight - sprites[spriteIndex].height, newY)));
|
// Allow negative values up to -negativeSpacing so sprite can fill the expanded area
|
||||||
|
const constrainedX = Math.floor(Math.max(-negativeSpacing, Math.min(maxWidth - negativeSpacing - sprites[spriteIndex].width, newX)));
|
||||||
|
const constrainedY = Math.floor(Math.max(-negativeSpacing, Math.min(maxHeight - negativeSpacing - sprites[spriteIndex].height, newY)));
|
||||||
|
|
||||||
onUpdateSprite(activeSpriteId.value, constrainedX, constrainedY);
|
onUpdateSprite(activeSpriteId.value, constrainedX, constrainedY);
|
||||||
onDraw();
|
onDraw();
|
||||||
|
|||||||
@@ -5,7 +5,18 @@ import JSZip from 'jszip';
|
|||||||
import type { Sprite } from '../types/sprites';
|
import type { Sprite } from '../types/sprites';
|
||||||
import { getMaxDimensions } from './useSprites';
|
import { getMaxDimensions } from './useSprites';
|
||||||
|
|
||||||
export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>) => {
|
export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>, negativeSpacingEnabled: Ref<boolean>) => {
|
||||||
|
// Calculate negative spacing based on sprite dimensions
|
||||||
|
const calculateNegativeSpacing = (): number => {
|
||||||
|
if (!negativeSpacingEnabled.value || sprites.value.length === 0) return 0;
|
||||||
|
|
||||||
|
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
||||||
|
const minWidth = Math.min(...sprites.value.map(s => s.width));
|
||||||
|
const minHeight = Math.min(...sprites.value.map(s => s.height));
|
||||||
|
const widthDiff = maxWidth - minWidth;
|
||||||
|
const heightDiff = maxHeight - minHeight;
|
||||||
|
return Math.max(widthDiff, heightDiff);
|
||||||
|
};
|
||||||
const downloadSpritesheet = () => {
|
const downloadSpritesheet = () => {
|
||||||
if (!sprites.value.length) {
|
if (!sprites.value.length) {
|
||||||
alert('Please upload or import sprites before downloading the spritesheet.');
|
alert('Please upload or import sprites before downloading the spritesheet.');
|
||||||
@@ -13,22 +24,25 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
const cellWidth = maxWidth + negativeSpacing;
|
||||||
|
const cellHeight = maxHeight + negativeSpacing;
|
||||||
const rows = Math.ceil(sprites.value.length / columns.value);
|
const rows = Math.ceil(sprites.value.length / columns.value);
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
canvas.width = maxWidth * columns.value;
|
canvas.width = cellWidth * columns.value;
|
||||||
canvas.height = maxHeight * rows;
|
canvas.height = cellHeight * rows;
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
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 = Math.floor(col * maxWidth);
|
const cellX = Math.floor(col * cellWidth);
|
||||||
const cellY = Math.floor(row * maxHeight);
|
const cellY = Math.floor(row * cellHeight);
|
||||||
ctx.drawImage(sprite.img, Math.floor(cellX + sprite.x), Math.floor(cellY + sprite.y));
|
ctx.drawImage(sprite.img, Math.floor(cellX + negativeSpacing + sprite.x), Math.floor(cellY + negativeSpacing + sprite.y));
|
||||||
});
|
});
|
||||||
|
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
@@ -133,20 +147,23 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
const cellWidth = maxWidth + negativeSpacing;
|
||||||
|
const cellHeight = maxHeight + negativeSpacing;
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
canvas.width = maxWidth;
|
canvas.width = cellWidth;
|
||||||
canvas.height = maxHeight;
|
canvas.height = cellHeight;
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
const gif = new GIF({ workers: 2, quality: 10, width: maxWidth, height: maxHeight, workerScript: gifWorkerUrl });
|
const gif = new GIF({ workers: 2, quality: 10, width: cellWidth, height: cellHeight, workerScript: gifWorkerUrl });
|
||||||
|
|
||||||
sprites.value.forEach(sprite => {
|
sprites.value.forEach(sprite => {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = '#f9fafb';
|
ctx.fillStyle = '#f9fafb';
|
||||||
ctx.fillRect(0, 0, maxWidth, maxHeight);
|
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
||||||
ctx.drawImage(sprite.img, Math.floor(sprite.x), Math.floor(sprite.y));
|
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
||||||
gif.addFrame(ctx, { copy: true, delay: 1000 / fps });
|
gif.addFrame(ctx, { copy: true, delay: 1000 / fps });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,18 +187,21 @@ export const useExport = (sprites: Ref<Sprite[]>, columns: Ref<number>) => {
|
|||||||
|
|
||||||
const zip = new JSZip();
|
const zip = new JSZip();
|
||||||
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
|
||||||
|
const negativeSpacing = calculateNegativeSpacing();
|
||||||
|
const cellWidth = maxWidth + negativeSpacing;
|
||||||
|
const cellHeight = maxHeight + negativeSpacing;
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
canvas.width = maxWidth;
|
canvas.width = cellWidth;
|
||||||
canvas.height = maxHeight;
|
canvas.height = cellHeight;
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
sprites.value.forEach((sprite, index) => {
|
sprites.value.forEach((sprite, index) => {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
ctx.fillStyle = '#f9fafb';
|
ctx.fillStyle = '#f9fafb';
|
||||||
ctx.fillRect(0, 0, maxWidth, maxHeight);
|
ctx.fillRect(0, 0, cellWidth, cellHeight);
|
||||||
ctx.drawImage(sprite.img, Math.floor(sprite.x), Math.floor(sprite.y));
|
ctx.drawImage(sprite.img, Math.floor(negativeSpacing + sprite.x), Math.floor(negativeSpacing + sprite.y));
|
||||||
const dataURL = canvas.toDataURL('image/png');
|
const dataURL = canvas.toDataURL('image/png');
|
||||||
const binary = atob(dataURL.split(',')[1]);
|
const binary = atob(dataURL.split(',')[1]);
|
||||||
const buf = new ArrayBuffer(binary.length);
|
const buf = new ArrayBuffer(binary.length);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { ref, watch } from 'vue';
|
|||||||
|
|
||||||
const pixelPerfect = ref(true);
|
const pixelPerfect = ref(true);
|
||||||
const darkMode = ref(false);
|
const darkMode = ref(false);
|
||||||
|
const negativeSpacingEnabled = ref(false);
|
||||||
|
|
||||||
// Initialize dark mode from localStorage or system preference
|
// Initialize dark mode from localStorage or system preference
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
@@ -51,12 +52,18 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
darkMode.value = value;
|
darkMode.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleNegativeSpacing() {
|
||||||
|
negativeSpacingEnabled.value = !negativeSpacingEnabled.value;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pixelPerfect,
|
pixelPerfect,
|
||||||
darkMode,
|
darkMode,
|
||||||
|
negativeSpacingEnabled,
|
||||||
togglePixelPerfect,
|
togglePixelPerfect,
|
||||||
setPixelPerfect,
|
setPixelPerfect,
|
||||||
toggleDarkMode,
|
toggleDarkMode,
|
||||||
setDarkMode,
|
setDarkMode,
|
||||||
|
toggleNegativeSpacing,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user