diff --git a/package-lock.json b/package-lock.json
index 07d10d9..689bccd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2084,39 +2084,39 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.24.tgz",
- "integrity": "sha512-eDl5H57AOpNakGNAkFDH+y7kTqrQpJkZFXhWZQGyx/5Wh7B1uQYvcWkvZi11BDhscPgj8N7XV3oRwiPnx1Vrig==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
+ "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
- "@vue/shared": "3.5.24",
+ "@vue/shared": "3.5.25",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.24.tgz",
- "integrity": "sha512-1QHGAvs53gXkWdd3ZMGYuvQFXHW4ksKWPG8HP8/2BscrbZ0brw183q2oNWjMrSWImYLHxHrx1ItBQr50I/q2zw==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
+ "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-core": "3.5.24",
- "@vue/shared": "3.5.24"
+ "@vue/compiler-core": "3.5.25",
+ "@vue/shared": "3.5.25"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.24.tgz",
- "integrity": "sha512-8EG5YPRgmTB+YxYBM3VXy8zHD9SWHUJLIGPhDovo3Z8VOgvP+O7UP5vl0J4BBPWYD9vxtBabzW1EuEZ+Cqs14g==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz",
+ "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
- "@vue/compiler-core": "3.5.24",
- "@vue/compiler-dom": "3.5.24",
- "@vue/compiler-ssr": "3.5.24",
- "@vue/shared": "3.5.24",
+ "@vue/compiler-core": "3.5.25",
+ "@vue/compiler-dom": "3.5.25",
+ "@vue/compiler-ssr": "3.5.25",
+ "@vue/shared": "3.5.25",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.6",
@@ -2124,13 +2124,13 @@
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.24.tgz",
- "integrity": "sha512-trOvMWNBMQ/odMRHW7Ae1CdfYx+7MuiQu62Jtu36gMLXcaoqKvAyh+P73sYG9ll+6jLB6QPovqoKGGZROzkFFg==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
+ "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.24",
- "@vue/shared": "3.5.24"
+ "@vue/compiler-dom": "3.5.25",
+ "@vue/shared": "3.5.25"
}
},
"node_modules/@vue/compiler-vue2": {
@@ -2240,53 +2240,53 @@
}
},
"node_modules/@vue/reactivity": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz",
- "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.25.tgz",
+ "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
"license": "MIT",
"dependencies": {
- "@vue/shared": "3.5.24"
+ "@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz",
- "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.25.tgz",
+ "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.24",
- "@vue/shared": "3.5.24"
+ "@vue/reactivity": "3.5.25",
+ "@vue/shared": "3.5.25"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz",
- "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz",
+ "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
"license": "MIT",
"dependencies": {
- "@vue/reactivity": "3.5.24",
- "@vue/runtime-core": "3.5.24",
- "@vue/shared": "3.5.24",
+ "@vue/reactivity": "3.5.25",
+ "@vue/runtime-core": "3.5.25",
+ "@vue/shared": "3.5.25",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz",
- "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.25.tgz",
+ "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-ssr": "3.5.24",
- "@vue/shared": "3.5.24"
+ "@vue/compiler-ssr": "3.5.25",
+ "@vue/shared": "3.5.25"
},
"peerDependencies": {
- "vue": "3.5.24"
+ "vue": "3.5.25"
}
},
"node_modules/@vue/shared": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz",
- "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz",
+ "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
"license": "MIT"
},
"node_modules/@vue/tsconfig": {
@@ -2374,9 +2374,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
- "version": "2.8.30",
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz",
- "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==",
+ "version": "2.8.31",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
+ "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2473,9 +2473,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001756",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
- "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
+ "version": "1.0.30001757",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
+ "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"dev": true,
"funding": [
{
@@ -2666,9 +2666,9 @@
}
},
"node_modules/electron-to-chromium": {
- "version": "1.5.259",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz",
- "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==",
+ "version": "1.5.260",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.260.tgz",
+ "integrity": "sha512-ov8rBoOBhVawpzdre+Cmz4FB+y66Eqrk6Gwqd8NGxuhv99GQ8XqMAr351KEkOt7gukXWDg6gJWEMKgL2RLMPtA==",
"dev": true,
"license": "ISC"
},
@@ -4885,16 +4885,16 @@
"license": "MIT"
},
"node_modules/vue": {
- "version": "3.5.24",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.24.tgz",
- "integrity": "sha512-uTHDOpVQTMjcGgrqFPSb8iO2m1DUvo+WbGqoXQz8Y1CeBYQ0FXf2z1gLRaBtHjlRz7zZUBHxjVB5VTLzYkvftg==",
+ "version": "3.5.25",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.25.tgz",
+ "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
"license": "MIT",
"dependencies": {
- "@vue/compiler-dom": "3.5.24",
- "@vue/compiler-sfc": "3.5.24",
- "@vue/runtime-dom": "3.5.24",
- "@vue/server-renderer": "3.5.24",
- "@vue/shared": "3.5.24"
+ "@vue/compiler-dom": "3.5.25",
+ "@vue/compiler-sfc": "3.5.25",
+ "@vue/runtime-dom": "3.5.25",
+ "@vue/server-renderer": "3.5.25",
+ "@vue/shared": "3.5.25"
},
"peerDependencies": {
"typescript": "*"
diff --git a/src/App.vue b/src/App.vue
index ee03b0f..5d73622 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -53,7 +53,7 @@
-
Upload sprites
+
Upload sprites or single image
Drag and drop images or import from JSON
@@ -65,7 +65,7 @@
Create spritesheets for your game development and animation projects with our completely free, open-source Spritesheet generator.
This powerful online tool lets you upload individual sprite images and automatically arrange them into optimized sprite sheets with customizable layouts - perfect for indie developers, animators, and studios of any size.
Key features of this sprite editor
-
+
- Free sprite editor: Edit, organize, and optimize your game sprites directly in your browser
- Automatic spritesheet generation: Convert multiple PNG, JPG, or GIF images into efficient sprite atlases
- Customizable grid layouts: Adjust spacing, padding, and arrangement for pixel-perfect results
@@ -74,7 +74,7 @@
- Zero installation required: No downloads - use our web-based sprite sheet maker instantly
- Batch processing: Upload and process multiple sprites simultaneously
- Export options: Download spritesheet as PNG, JPG, GIF, ZIP or JSON.
-
+
@@ -392,20 +392,29 @@
if (files.length === 1 && files[0].type.startsWith('image/')) {
const file = files[0];
- const url = URL.createObjectURL(file);
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) {
+ spritesheetImageUrl.value = url;
+ spritesheetImageFile.value = file;
+ isSpritesheetSplitterOpen.value = true;
+ return;
+ }
- const img = new Image();
- img.onload = () => {
- if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) {
- spritesheetImageUrl.value = url;
- spritesheetImageFile.value = file;
- isSpritesheetSplitterOpen.value = true;
- return;
- }
-
- processImageFiles([file]);
+ processImageFiles([file]);
+ };
+ img.onerror = () => {
+ console.error('Failed to load image:', file.name);
+ };
+ img.src = url;
};
- img.src = url;
+ reader.onerror = () => {
+ console.error('Failed to read image file:', file.name);
+ };
+ reader.readAsDataURL(file);
return;
}
@@ -439,10 +448,12 @@
const closeSpritesheetSplitter = () => {
isSpritesheetSplitterOpen.value = false;
- if (spritesheetImageUrl.value) {
- URL.revokeObjectURL(spritesheetImageUrl.value);
- spritesheetImageUrl.value = '';
+ if (spritesheetImageUrl.value && spritesheetImageUrl.value.startsWith('blob:')) {
+ try {
+ URL.revokeObjectURL(spritesheetImageUrl.value);
+ } catch {}
}
+ spritesheetImageUrl.value = '';
spritesheetImageFile.value = null;
};
diff --git a/src/composables/useLayers.ts b/src/composables/useLayers.ts
index c9bc14d..5e083a5 100644
--- a/src/composables/useLayers.ts
+++ b/src/composables/useLayers.ts
@@ -137,37 +137,53 @@ export const useLayers = () => {
} catch {}
}
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- l.sprites[i] = { id: old.id, file, img, url, width: img.width, height: img.height, x: old.x, y: old.y };
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ l.sprites[i] = { id: old.id, file, img, url, width: img.width, height: img.height, x: old.x, y: old.y };
+ };
+ img.onerror = () => {
+ console.error('Failed to load replacement image:', file.name);
+ };
+ img.src = url;
};
- img.onerror = () => {
- URL.revokeObjectURL(url);
+ reader.onerror = () => {
+ console.error('Failed to read replacement image file:', file.name);
};
- img.src = url;
+ reader.readAsDataURL(file);
};
const addSprite = (file: File) => {
const l = activeLayer.value;
if (!l) return;
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- const next: Sprite = {
- id: crypto.randomUUID(),
- file,
- img,
- url,
- width: img.width,
- height: img.height,
- x: 0,
- y: 0,
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ const next: Sprite = {
+ id: crypto.randomUUID(),
+ file,
+ img,
+ url,
+ width: img.width,
+ height: img.height,
+ x: 0,
+ y: 0,
+ };
+ l.sprites = [...l.sprites, next];
};
- l.sprites = [...l.sprites, next];
+ img.onerror = () => {
+ console.error('Failed to load sprite image:', file.name);
+ };
+ img.src = url;
};
- img.onerror = () => URL.revokeObjectURL(url);
- img.src = url;
+ reader.onerror = () => {
+ console.error('Failed to read sprite image file:', file.name);
+ };
+ reader.readAsDataURL(file);
};
const processImageFiles = async (files: File[]) => {
diff --git a/src/composables/useSprites.ts b/src/composables/useSprites.ts
index edd912c..e1c8f40 100644
--- a/src/composables/useSprites.ts
+++ b/src/composables/useSprites.ts
@@ -87,129 +87,157 @@ export const useSprites = () => {
const old = sprites.value[i];
revokeIfBlob(old.url);
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- const next: Sprite = {
- id: old.id,
- file,
- img,
- url,
- width: img.width,
- height: img.height,
- x: old.x,
- y: old.y,
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ const next: Sprite = {
+ id: old.id,
+ file,
+ img,
+ url,
+ width: img.width,
+ height: img.height,
+ x: old.x,
+ y: old.y,
+ };
+ const arr = [...sprites.value];
+ arr[i] = next;
+ sprites.value = arr;
};
- const arr = [...sprites.value];
- arr[i] = next;
- sprites.value = arr;
+ img.onerror = () => {
+ console.error('Failed to load replacement image:', file.name);
+ };
+ img.src = url;
};
- img.onerror = () => {
- console.error('Failed to load replacement image:', file.name);
- URL.revokeObjectURL(url);
+ reader.onerror = () => {
+ console.error('Failed to read replacement image file:', file.name);
};
- img.src = url;
+ reader.readAsDataURL(file);
};
const addSprite = (file: File) => {
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- const s: Sprite = {
- id: crypto.randomUUID(),
- file,
- img,
- url,
- width: img.width,
- height: img.height,
- x: 0,
- y: 0,
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ const s: Sprite = {
+ id: crypto.randomUUID(),
+ file,
+ img,
+ url,
+ width: img.width,
+ height: img.height,
+ x: 0,
+ y: 0,
+ };
+ sprites.value = [...sprites.value, s];
};
- sprites.value = [...sprites.value, s];
+ img.onerror = () => {
+ console.error('Failed to load new sprite image:', file.name);
+ };
+ img.src = url;
};
- img.onerror = () => {
- console.error('Failed to load new sprite image:', file.name);
- URL.revokeObjectURL(url);
+ reader.onerror = () => {
+ console.error('Failed to read sprite image file:', file.name);
};
- img.src = url;
+ reader.readAsDataURL(file);
};
const addSpriteWithResize = (file: File) => {
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ const { maxWidth, maxHeight } = getMaxDimensions(sprites.value);
- const newSprite: Sprite = {
- id: crypto.randomUUID(),
- file,
- img,
- url,
- width: img.width,
- height: img.height,
- x: 0,
- y: 0,
+ const newSprite: Sprite = {
+ id: crypto.randomUUID(),
+ file,
+ img,
+ url,
+ width: img.width,
+ height: img.height,
+ x: 0,
+ y: 0,
+ };
+
+ const newMaxWidth = Math.max(maxWidth, img.width);
+ const newMaxHeight = Math.max(maxHeight, img.height);
+
+ if (img.width > maxWidth || img.height > maxHeight) {
+ sprites.value = sprites.value.map(sprite => {
+ let newX = sprite.x;
+ let newY = sprite.y;
+
+ if (img.width > maxWidth) {
+ const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0;
+ newX = Math.floor(relativeX * newMaxWidth);
+ newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width));
+ }
+
+ if (img.height > maxHeight) {
+ const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0;
+ newY = Math.floor(relativeY * newMaxHeight);
+ newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height));
+ }
+
+ return { ...sprite, x: newX, y: newY };
+ });
+ }
+
+ sprites.value = [...sprites.value, newSprite];
+ triggerForceRedraw();
};
-
- const newMaxWidth = Math.max(maxWidth, img.width);
- const newMaxHeight = Math.max(maxHeight, img.height);
-
- if (img.width > maxWidth || img.height > maxHeight) {
- sprites.value = sprites.value.map(sprite => {
- let newX = sprite.x;
- let newY = sprite.y;
-
- if (img.width > maxWidth) {
- const relativeX = maxWidth > 0 ? sprite.x / maxWidth : 0;
- newX = Math.floor(relativeX * newMaxWidth);
- newX = Math.max(0, Math.min(newX, newMaxWidth - sprite.width));
- }
-
- if (img.height > maxHeight) {
- const relativeY = maxHeight > 0 ? sprite.y / maxHeight : 0;
- newY = Math.floor(relativeY * newMaxHeight);
- newY = Math.max(0, Math.min(newY, newMaxHeight - sprite.height));
- }
-
- return { ...sprite, x: newX, y: newY };
- });
- }
-
- sprites.value = [...sprites.value, newSprite];
- triggerForceRedraw();
+ img.onerror = () => {
+ console.error('Failed to load new sprite image:', file.name);
+ };
+ img.src = url;
};
- img.onerror = () => {
- console.error('Failed to load new sprite image:', file.name);
- URL.revokeObjectURL(url);
+ reader.onerror = () => {
+ console.error('Failed to read sprite image file:', file.name);
};
- img.src = url;
+ reader.readAsDataURL(file);
};
const processImageFiles = (files: File[]) => {
Promise.all(
files.map(
file =>
- new Promise(resolve => {
- const url = URL.createObjectURL(file);
- const img = new Image();
- img.onload = () => {
- resolve({
- id: crypto.randomUUID(),
- file,
- img,
- url,
- width: img.width,
- height: img.height,
- x: 0,
- y: 0,
- });
+ new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = e => {
+ const url = e.target?.result as string;
+ const img = new Image();
+ img.onload = () => {
+ resolve({
+ id: crypto.randomUUID(),
+ file,
+ img,
+ url,
+ width: img.width,
+ height: img.height,
+ x: 0,
+ y: 0,
+ });
+ };
+ img.onerror = () => reject(new Error(`Failed to load image: ${file.name}`));
+ img.src = url;
};
- img.src = url;
+ reader.onerror = () => reject(new Error(`Failed to read file: ${file.name}`));
+ reader.readAsDataURL(file);
})
)
- ).then(newSprites => {
- sprites.value = [...sprites.value, ...newSprites];
- });
+ )
+ .then(newSprites => {
+ sprites.value = [...sprites.value, ...newSprites];
+ })
+ .catch(err => {
+ console.error('Error processing image files:', err);
+ });
};
onUnmounted(() => {