make the sprites only resize to what they need to be

This commit is contained in:
2026-01-20 21:51:45 -06:00
parent 6f59e34761
commit 95d821c376
2 changed files with 139 additions and 36 deletions

View File

@@ -323,7 +323,8 @@ const outlineColorPicker = document.getElementById('outline-color-picker');
canvas.on('path:created', saveHistory);
canvas.on('object:added', (e) => {
if (e.target && e.target.type !== 'path') {
// Only save history for valid fabric objects
if (e.target && typeof e.target.getBounds === 'function' && e.target.type !== 'path') {
saveHistory();
}
});
@@ -375,7 +376,19 @@ const outlineColorPicker = document.getElementById('outline-color-picker');
document.querySelector('[data-tool="draw"]').click();
// Load existing costume if provided
if (existingCostume && existingCostume.texture) {
if (existingCostume) {
// If we have editor data, restore the full canvas state
if (existingCostume.editorData) {
canvas.loadFromJSON(existingCostume.editorData).then(() => {
canvas.renderAll();
saveHistory();
}).catch(err => {
console.error('Failed to load editor data:', err);
saveHistory();
});
}
// Otherwise, load from texture image (legacy/first edit)
else if (existingCostume.texture) {
const url = existingCostume.texture.baseTexture?.resource?.url || existingCostume.texture.baseTexture?.cacheId;
if (url) {
@@ -395,20 +408,30 @@ const outlineColorPicker = document.getElementById('outline-color-picker');
saveHistory();
}).catch(err => {
console.error('Failed to load costume:', err);
saveHistory();
});
} else {
saveHistory();
}
} else {
saveHistory();
}
} else {
// Save initial empty state
saveHistory();
}
document.querySelector('.save-btn').addEventListener('click', () => {
const dataURL = canvas.toDataURL({
format: 'png',
quality: 1,
multiplier: 1
document.querySelector('.save-btn').addEventListener('click', async () => {
// Save both cropped version (for display) and full canvas data (for re-editing)
const croppedDataURL = await autoCropCanvas(canvas);
const fullCanvasJSON = canvas.toJSON();
if (onSave) {
onSave({
dataURL: croppedDataURL,
editorData: fullCanvasJSON
});
if (onSave) onSave(dataURL);
}
closeCostumeEditor();
});
@@ -416,6 +439,80 @@ const outlineColorPicker = document.getElementById('outline-color-picker');
document.querySelector('.close-editor-btn').addEventListener('click', closeCostumeEditor);
}
function autoCropCanvas(canvas) {
return new Promise((resolve) => {
// First, get the full canvas as a data URL
const fullDataURL = canvas.toDataURL({ format: 'png', quality: 1 });
// Create an image from it
const img = new Image();
img.onload = () => {
// Create a temporary canvas to analyze pixels
const tempCanvas = document.createElement('canvas');
tempCanvas.width = canvas.width;
tempCanvas.height = canvas.height;
const ctx = tempCanvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data;
let minX = canvas.width, minY = canvas.height, maxX = 0, maxY = 0;
let hasContent = false;
// Scan for non-transparent pixels
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
const alpha = pixels[(y * canvas.width + x) * 4 + 3];
if (alpha > 0) {
hasContent = true;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
}
// If no content, return small empty canvas
if (!hasContent) {
const emptyCanvas = document.createElement('canvas');
emptyCanvas.width = 100;
emptyCanvas.height = 100;
resolve(emptyCanvas.toDataURL('image/png'));
return;
}
// Add padding
const padding = 10;
minX = Math.max(0, minX - padding);
minY = Math.max(0, minY - padding);
maxX = Math.min(canvas.width - 1, maxX + padding);
maxY = Math.min(canvas.height - 1, maxY + padding);
const cropWidth = maxX - minX + 1;
const cropHeight = maxY - minY + 1;
// Create final cropped canvas
const croppedCanvas = document.createElement('canvas');
croppedCanvas.width = cropWidth;
croppedCanvas.height = cropHeight;
const croppedCtx = croppedCanvas.getContext('2d');
// Draw the cropped portion
croppedCtx.drawImage(
img,
minX, minY, cropWidth, cropHeight,
0, 0, cropWidth, cropHeight
);
resolve(croppedCanvas.toDataURL('image/png'));
};
img.src = fullDataURL;
});
}
export function closeCostumeEditor() {
if (editorContainer) {
editorContainer.remove();

View File

@@ -595,12 +595,14 @@ function renderCostumesList() {
editBtn.draggable = false;
editBtn.title = "Edit costume";
editBtn.onclick = () => {
openCostumeEditor(costume, async (dataURL) => {
if (!dataURL) return;
openCostumeEditor(costume, async (costumeData) => {
if (!costumeData) return;
// Update the existing costume
const newTexture = PIXI.Texture.from(dataURL);
const newTexture = PIXI.Texture.from(costumeData.dataURL);
newTexture.editorData = costumeData.editorData;
costume.texture = newTexture;
costume.editorData = costumeData.editorData;
// Update sprite if this is the current costume
if (activeSprite.pixiSprite.texture === costume.texture) {
@@ -613,11 +615,11 @@ function renderCostumesList() {
if (currentSocket && currentRoom) {
currentSocket.emit("projectUpdate", {
roomId: currentRoom,
type: "updateCostume",
type: "addCostume",
data: {
spriteId: activeSprite.id,
name: costume.name,
texture: dataURL,
name: uniqueName,
texture: costumeData.dataURL,
},
});
}
@@ -1825,6 +1827,7 @@ loadButton.addEventListener("click", () => {
});
loadInput.addEventListener("change", loadProject);
// Create new costume with editor
// Create new costume with editor
document.getElementById('create-costume-button')?.addEventListener('click', () => {
if (!activeSprite) {
@@ -1832,10 +1835,13 @@ document.getElementById('create-costume-button')?.addEventListener('click', () =
return;
}
openCostumeEditor(null, async (dataURL) => {
if (!dataURL || !activeSprite) return;
openCostumeEditor(null, async (costumeData) => {
if (!costumeData || !activeSprite) return;
const texture = PIXI.Texture.from(dataURL);
const texture = PIXI.Texture.from(costumeData.dataURL);
// Store the editorData along with the texture for future editing
texture.editorData = costumeData.editorData;
let uniqueName = 'costume';
let counter = 1;