1
0
forked from noxious/server

Combining sprites to generate a spritesheet works again.

This commit is contained in:
Dennis Postma 2025-01-28 15:34:21 +01:00
parent dbdc8c9d6e
commit 9d7cee2334

View File

@ -3,6 +3,7 @@ import { UUID } from '#application/types'
import SpriteRepository from '#repositories/spriteRepository' import SpriteRepository from '#repositories/spriteRepository'
import { SpriteAction } from '#entities/spriteAction' import { SpriteAction } from '#entities/spriteAction'
import sharp from 'sharp' import sharp from 'sharp'
import fs from 'fs'
type Payload = { type Payload = {
id: UUID id: UUID
@ -52,11 +53,14 @@ export default class SpriteUpdateEvent extends BaseEvent {
.setSprites(actionData.sprites) .setSprites(actionData.sprites)
.setOriginX(actionData.originX) .setOriginX(actionData.originX)
.setOriginY(actionData.originY) .setOriginY(actionData.originY)
.setFrameWidth(await this.calculateWidth(actionData.sprites[0])) .setFrameWidth(await this.calculateMaxWidth(actionData.sprites))
.setFrameHeight(await this.calculateHeight(actionData.sprites[0])) .setFrameHeight(await this.calculateMaxHeight(actionData.sprites))
.setFrameRate(actionData.frameRate) .setFrameRate(actionData.frameRate)
await spriteRepository.getEntityManager().persistAndFlush(spriteAction) await spriteRepository.getEntityManager().persistAndFlush(spriteAction)
// Generate sprite sheet for this action
await this.generateSpriteSheet(actionData.sprites, sprite.getId(), actionData.action)
} }
return callback(true) return callback(true)
@ -66,27 +70,103 @@ export default class SpriteUpdateEvent extends BaseEvent {
} }
} }
private async calculateWidth(base64: string): Promise<number> { private async calculateMaxWidth(sprites: string[]): Promise<number> {
const uri = base64.split(';base64,').pop() if (!sprites.length) return 0
if (!uri) return 0
const imgBuffer = Buffer.from(uri, 'base64') const widths = await Promise.all(sprites.map(async (base64) => {
const image = await sharp(imgBuffer).metadata() const uri = base64.split(';base64,').pop()
return image.width ?? 0 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> { private async calculateMaxHeight(sprites: string[]): Promise<number> {
const uri = base64.split(';base64,').pop() if (!sprites.length) return 0
if (!uri) return 0
const imgBuffer = Buffer.from(uri, 'base64') const heights = await Promise.all(sprites.map(async (base64) => {
const image = await sharp(imgBuffer).metadata() const uri = base64.split(';base64,').pop()
return image.height ?? 0 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[]) { private async generateSpriteSheet(sprites: string[], spriteId: string, action: string): Promise<void> {
// In here comes a function that generates a sprite sheet from the given sprites using Sharp. try {
// This function takes an array of base64 encoded sprites and generates a single png sprite sheet. // Skip if no sprites
// Then proceeds to save ths sprite sheet to the public/sprites/{spriteId}/ directory. The file name is {action}.png. 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
}
} }
} }