Combining sprites to generate a spritesheet works again.
This commit is contained in:
parent
dbdc8c9d6e
commit
9d7cee2334
@ -3,6 +3,7 @@ import { UUID } from '#application/types'
|
||||
import SpriteRepository from '#repositories/spriteRepository'
|
||||
import { SpriteAction } from '#entities/spriteAction'
|
||||
import sharp from 'sharp'
|
||||
import fs from 'fs'
|
||||
|
||||
type Payload = {
|
||||
id: UUID
|
||||
@ -52,11 +53,14 @@ export default class SpriteUpdateEvent extends BaseEvent {
|
||||
.setSprites(actionData.sprites)
|
||||
.setOriginX(actionData.originX)
|
||||
.setOriginY(actionData.originY)
|
||||
.setFrameWidth(await this.calculateWidth(actionData.sprites[0]))
|
||||
.setFrameHeight(await this.calculateHeight(actionData.sprites[0]))
|
||||
.setFrameWidth(await this.calculateMaxWidth(actionData.sprites))
|
||||
.setFrameHeight(await this.calculateMaxHeight(actionData.sprites))
|
||||
.setFrameRate(actionData.frameRate)
|
||||
|
||||
await spriteRepository.getEntityManager().persistAndFlush(spriteAction)
|
||||
|
||||
// Generate sprite sheet for this action
|
||||
await this.generateSpriteSheet(actionData.sprites, sprite.getId(), actionData.action)
|
||||
}
|
||||
|
||||
return callback(true)
|
||||
@ -66,27 +70,103 @@ export default class SpriteUpdateEvent extends BaseEvent {
|
||||
}
|
||||
}
|
||||
|
||||
private async calculateWidth(base64: string): Promise<number> {
|
||||
const uri = base64.split(';base64,').pop()
|
||||
if (!uri) return 0
|
||||
private async calculateMaxWidth(sprites: string[]): Promise<number> {
|
||||
if (!sprites.length) return 0
|
||||
|
||||
const imgBuffer = Buffer.from(uri, 'base64')
|
||||
const image = await sharp(imgBuffer).metadata()
|
||||
return image.width ?? 0
|
||||
const widths = await Promise.all(sprites.map(async (base64) => {
|
||||
const uri = base64.split(';base64,').pop()
|
||||
if (!uri) return 0
|
||||
|
||||
const imgBuffer = Buffer.from(uri, 'base64')
|
||||
const image = await sharp(imgBuffer).metadata()
|
||||
return image.width ?? 0
|
||||
}))
|
||||
|
||||
return Math.max(...widths)
|
||||
}
|
||||
|
||||
private async calculateHeight(base64: string): Promise<number> {
|
||||
const uri = base64.split(';base64,').pop()
|
||||
if (!uri) return 0
|
||||
private async calculateMaxHeight(sprites: string[]): Promise<number> {
|
||||
if (!sprites.length) return 0
|
||||
|
||||
const imgBuffer = Buffer.from(uri, 'base64')
|
||||
const image = await sharp(imgBuffer).metadata()
|
||||
return image.height ?? 0
|
||||
const heights = await Promise.all(sprites.map(async (base64) => {
|
||||
const uri = base64.split(';base64,').pop()
|
||||
if (!uri) return 0
|
||||
|
||||
const imgBuffer = Buffer.from(uri, 'base64')
|
||||
const image = await sharp(imgBuffer).metadata()
|
||||
return image.height ?? 0
|
||||
}))
|
||||
|
||||
return Math.max(...heights)
|
||||
}
|
||||
|
||||
private generateSpriteSheet(sprites: string[]) {
|
||||
// In here comes a function that generates a sprite sheet from the given sprites using Sharp.
|
||||
// This function takes an array of base64 encoded sprites and generates a single png sprite sheet.
|
||||
// Then proceeds to save ths sprite sheet to the public/sprites/{spriteId}/ directory. The file name is {action}.png.
|
||||
private async generateSpriteSheet(sprites: string[], spriteId: string, action: string): Promise<void> {
|
||||
try {
|
||||
// Skip if no sprites
|
||||
if (!sprites.length) return
|
||||
|
||||
// Process all base64 images to buffers and get their dimensions
|
||||
const imageData = await Promise.all(
|
||||
sprites.map(async (base64) => {
|
||||
const uri = base64.split(';base64,').pop()
|
||||
if (!uri) throw new Error('Invalid base64 image')
|
||||
const buffer = Buffer.from(uri, 'base64')
|
||||
const metadata = await sharp(buffer).metadata()
|
||||
return { buffer, width: metadata.width ?? 0, height: metadata.height ?? 0 }
|
||||
})
|
||||
)
|
||||
|
||||
// Find the largest dimensions
|
||||
const maxWidth = Math.max(...imageData.map(data => data.width))
|
||||
const maxHeight = Math.max(...imageData.map(data => data.height))
|
||||
|
||||
// Extend all images to match the largest dimensions without resizing
|
||||
const resizedBuffers = await Promise.all(
|
||||
imageData.map(async ({ buffer, width, height }) => {
|
||||
// Calculate padding to center the sprite
|
||||
const leftPadding = Math.floor((maxWidth - width) / 2)
|
||||
const topPadding = Math.floor((maxHeight - height) / 2)
|
||||
|
||||
return await sharp(buffer)
|
||||
.extend({
|
||||
top: topPadding,
|
||||
bottom: Math.ceil((maxHeight - height) / 2),
|
||||
left: leftPadding,
|
||||
right: Math.ceil((maxWidth - width) / 2),
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
})
|
||||
.toBuffer()
|
||||
})
|
||||
)
|
||||
|
||||
// Create sprite sheet by combining images horizontally
|
||||
const spriteSheet = await sharp({
|
||||
create: {
|
||||
width: maxWidth * sprites.length,
|
||||
height: maxHeight,
|
||||
channels: 4,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
}
|
||||
})
|
||||
.composite(
|
||||
resizedBuffers.map((buffer, index) => ({
|
||||
input: buffer,
|
||||
left: index * maxWidth,
|
||||
top: 0
|
||||
}))
|
||||
)
|
||||
.png()
|
||||
.toBuffer()
|
||||
|
||||
// Ensure directory exists
|
||||
const dir = `public/sprites/${spriteId}`
|
||||
await fs.promises.mkdir(dir, { recursive: true })
|
||||
|
||||
// Save the sprite sheet
|
||||
await fs.promises.writeFile(`${dir}/${action}.png`, spriteSheet)
|
||||
} catch (error) {
|
||||
console.error('Error generating sprite sheet:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user