forked from noxious/server
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 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 widths = await Promise.all(sprites.map(async (base64) => {
|
||||||
const imgBuffer = Buffer.from(uri, 'base64')
|
const uri = base64.split(';base64,').pop()
|
||||||
const image = await sharp(imgBuffer).metadata()
|
if (!uri) return 0
|
||||||
return image.width ?? 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 heights = await Promise.all(sprites.map(async (base64) => {
|
||||||
const imgBuffer = Buffer.from(uri, 'base64')
|
const uri = base64.split(';base64,').pop()
|
||||||
const image = await sharp(imgBuffer).metadata()
|
if (!uri) return 0
|
||||||
return image.height ?? 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user