Skip to content

Commit 3ba41a7

Browse files
authored
feat: Add methods to retrieve parsed sprite and image (#3614)
Introduce two new methods for obtaining parsed sprites and images from the sprite sheet, enhancing the functionality for rendering and image manipulation.
1 parent 851fc99 commit 3ba41a7

File tree

3 files changed

+176
-1
lines changed

3 files changed

+176
-1
lines changed

site/docs/04-graphics/04.1-spritesheets.mdx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,22 @@ const ss = ex.SpriteSheet.fromImageSourceWithSourceViews({
5757
],
5858
})
5959
```
60+
61+
## Parsing Out Discrete Images
62+
63+
If you pass a large sheet image into a SpriteSheet then use a Sprite via `getSprite()`, Excalibur draws a window into that larger sheet.
64+
But what if you want a completely isolated Sprite parsed out of the host image?
65+
What if you wanted to use that Sprite in an HTML image for UI?
66+
We got convenience methods to help with that. They work exactly like `getSprite()` but they return NEW and unique Sprites and ImageElements.
67+
68+
```ts
69+
const myNewSprite = ss.getSpriteAsStandalone(x,y); // new Sprite
70+
```
71+
72+
or
73+
74+
```ts
75+
const myNewImage = getSpriteAsImage(x,y); // new HTMLImageElement
76+
```
77+
78+

src/engine/Graphics/SpriteSheet.ts

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ImageSource } from './ImageSource';
1+
import { ImageSource } from './ImageSource';
22
import type { SourceView } from './Sprite';
33
import { Sprite } from './Sprite';
44
import type { GraphicOptions } from './Graphic';
@@ -178,6 +178,114 @@ export class SpriteSheet {
178178
throw Error(`Invalid sprite coordinates (${x}, ${y})`);
179179
}
180180

181+
/**
182+
* Returns a sprite that has a new backing image the exact size of the sprite that tha is a copy of the original sprite slice.
183+
*
184+
* Useful if you need to apply effects, manipulate, or mutate the image and you don't want to disturb the original sprite sheet.
185+
*
186+
*/
187+
public async getSpriteAsStandalone(x: number, y: number): Promise<Sprite> {
188+
if (x >= this.columns || x < 0) {
189+
throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), x: ${x} should be between 0 and ${this.columns - 1} columns`);
190+
}
191+
if (y >= this.rows || y < 0) {
192+
throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), y: ${y} should be between 0 and ${this.rows - 1} rows`);
193+
}
194+
const spriteIndex = x + y * this.columns;
195+
const sprite = this.sprites[spriteIndex];
196+
const cnv = document.createElement('canvas');
197+
const ctx = cnv.getContext('2d');
198+
cnv.width = sprite.width;
199+
cnv.height = sprite.height;
200+
201+
if (!sprite) {
202+
throw Error(`Invalid sprite coordinates (${x}, ${y})`);
203+
}
204+
if (!ctx) {
205+
throw Error('Unable to create canvas context');
206+
}
207+
208+
ctx.drawImage(
209+
sprite.image.image,
210+
sprite.sourceView.x,
211+
sprite.sourceView.y,
212+
sprite.sourceView.width,
213+
sprite.sourceView.height,
214+
0,
215+
0,
216+
sprite.sourceView.width,
217+
sprite.sourceView.height
218+
);
219+
220+
const imgSrc = new ImageSource(cnv.toDataURL());
221+
await imgSrc.load();
222+
223+
return new Sprite({
224+
image: imgSrc,
225+
sourceView: {
226+
x: 0,
227+
y: 0,
228+
width: sprite.width,
229+
height: sprite.height
230+
},
231+
destSize: {
232+
width: sprite.width,
233+
height: sprite.height
234+
}
235+
});
236+
}
237+
238+
/**
239+
* Returns a new image exact size and copy of the original sprite slice.
240+
*
241+
* Useful if you need to apply effects, manipulate, or mutate the image and you don't want to disturb the original sprite sheet.
242+
*/
243+
public async getSpriteAsImage(x: number, y: number): Promise<HTMLImageElement> {
244+
if (x >= this.columns || x < 0) {
245+
throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), x: ${x} should be between 0 and ${this.columns - 1} columns`);
246+
}
247+
if (y >= this.rows || y < 0) {
248+
throw Error(`No sprite exists in the SpriteSheet at (${x}, ${y}), y: ${y} should be between 0 and ${this.rows - 1} rows`);
249+
}
250+
const spriteIndex = x + y * this.columns;
251+
const sprite = this.sprites[spriteIndex];
252+
const cnv = document.createElement('canvas');
253+
const ctx = cnv.getContext('2d');
254+
cnv.width = sprite.width;
255+
cnv.height = sprite.height;
256+
257+
if (!sprite) {
258+
throw Error(`Invalid sprite coordinates (${x}, ${y})`);
259+
}
260+
if (!ctx) {
261+
throw Error('Unable to create canvas context');
262+
}
263+
264+
ctx.drawImage(
265+
sprite.image.image,
266+
sprite.sourceView.x,
267+
sprite.sourceView.y,
268+
sprite.sourceView.width,
269+
sprite.sourceView.height,
270+
0,
271+
0,
272+
sprite.sourceView.width,
273+
sprite.sourceView.height
274+
);
275+
276+
const imgSrc = new Image(sprite.width, sprite.height);
277+
imgSrc.src = cnv.toDataURL();
278+
279+
return await new Promise((resolve, reject) => {
280+
imgSrc.onload = () => {
281+
resolve(imgSrc);
282+
};
283+
imgSrc.onerror = (e) => {
284+
reject(e);
285+
};
286+
});
287+
}
288+
181289
/**
182290
* Create a sprite sheet from a sparse set of {@apilink SourceView} rectangles
183291
* @param options

src/spec/vitest/SpriteSheetSpec.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,52 @@ describe('A SpriteSheet for Graphics', () => {
229229
expect(ss.rows).toBe(clone.rows);
230230
expect(ss.columns).toBe(clone.columns);
231231
});
232+
233+
it('can produce a unique image element by x,y', async () => {
234+
const image = new ex.ImageSource('/src/spec/assets/images/SpriteSheetSpec/kenny-cards.png');
235+
236+
await image.load();
237+
const ss = ex.SpriteSheet.fromImageSource({
238+
image,
239+
grid: {
240+
rows: 4,
241+
columns: 14,
242+
spriteWidth: 42,
243+
spriteHeight: 60
244+
},
245+
spacing: {
246+
originOffset: { x: 11, y: 2 },
247+
margin: { x: 23, y: 5 }
248+
}
249+
});
250+
251+
const newImage = await ss.getSpriteAsImage(0, 0);
252+
expect(newImage.width).toBe(42);
253+
expect(newImage.height).toBe(60);
254+
expect(newImage).toBeInstanceOf(Image);
255+
});
256+
257+
it('can produce a unique Sprite Object by x,y', async () => {
258+
const image = new ex.ImageSource('/src/spec/assets/images/SpriteSheetSpec/kenny-cards.png');
259+
260+
await image.load();
261+
const ss = ex.SpriteSheet.fromImageSource({
262+
image,
263+
grid: {
264+
rows: 4,
265+
columns: 14,
266+
spriteWidth: 42,
267+
spriteHeight: 60
268+
},
269+
spacing: {
270+
originOffset: { x: 11, y: 2 },
271+
margin: { x: 23, y: 5 }
272+
}
273+
});
274+
275+
const newSprite = await ss.getSpriteAsStandalone(0, 0);
276+
expect(newSprite.width).toBe(42);
277+
expect(newSprite.height).toBe(60);
278+
expect(newSprite).toBeInstanceOf(ex.Sprite);
279+
});
232280
});

0 commit comments

Comments
 (0)