forked from noxious/server
Better sprite-sheet generating
This commit is contained in:
parent
1cbf116ad4
commit
43fe6ab33e
12
package-lock.json
generated
12
package-lock.json
generated
@ -949,9 +949,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.34.2",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.2.tgz",
|
||||
"integrity": "sha512-eUzeCswrKbQDE1WY8h4ZTBtynOzCU5qx9felFdYOmIJrsy0warDahHKUiCZ6dUCs6ZxYMGtcaciIMcAf1L54yw==",
|
||||
"version": "5.34.3",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.3.tgz",
|
||||
"integrity": "sha512-S8/V11w7p6jYAGvv+00skLza/4inTOupWPe0uCD8mZSUiYKzvmW4/YEB+KVjZI2CC2oD3KJ3t7/KkUd31MxMig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.6.0",
|
||||
@ -1843,9 +1843,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
|
||||
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
|
@ -82,8 +82,9 @@ export default class SpriteUpdateEvent {
|
||||
const buffersWithDimensions = await Promise.all(
|
||||
sprites.map(async (sprite: string) => {
|
||||
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
||||
const { width, height } = await sharp(buffer).metadata()
|
||||
return { buffer, width, height }
|
||||
const normalizedBuffer = await normalizeSprite(buffer)
|
||||
const { width, height } = await sharp(normalizedBuffer).metadata()
|
||||
return { buffer: normalizedBuffer, width, height }
|
||||
})
|
||||
)
|
||||
|
||||
@ -100,24 +101,70 @@ export default class SpriteUpdateEvent {
|
||||
)
|
||||
}
|
||||
|
||||
async function normalizeSprite(buffer: Buffer): Promise<Buffer> {
|
||||
const image = sharp(buffer)
|
||||
|
||||
// Remove any transparent edges
|
||||
const trimmed = await image
|
||||
.trim()
|
||||
.toBuffer()
|
||||
|
||||
// Optional: Ensure dimensions are even numbers
|
||||
const metadata = await sharp(trimmed).metadata()
|
||||
const width = Math.ceil(metadata.width! / 2) * 2
|
||||
const height = Math.ceil(metadata.height! / 2) * 2
|
||||
|
||||
return sharp(trimmed)
|
||||
.resize(width, height, {
|
||||
fit: 'contain',
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
})
|
||||
.toBuffer()
|
||||
}
|
||||
|
||||
async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) {
|
||||
const publicFolder = getPublicPath('sprites', id)
|
||||
await mkdir(publicFolder, { recursive: true })
|
||||
|
||||
await Promise.all(
|
||||
processedActions.map(async ({ action, buffersWithDimensions, frameWidth, frameHeight }) => {
|
||||
// Get and validate all frame dimensions first
|
||||
// First pass: analyze all frames to determine the consistent dimensions
|
||||
const frames = await Promise.all(
|
||||
buffersWithDimensions.map(async ({ buffer }) => {
|
||||
const image = sharp(buffer)
|
||||
|
||||
// Get trim boundaries to find actual sprite content
|
||||
const { info: trimData } = await image
|
||||
.trim()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
|
||||
// Get original metadata
|
||||
const metadata = await sharp(buffer).metadata()
|
||||
|
||||
return {
|
||||
buffer,
|
||||
width: metadata.width!,
|
||||
height: metadata.height!
|
||||
height: metadata.height!,
|
||||
trimmed: {
|
||||
width: trimData.width,
|
||||
height: trimData.height,
|
||||
left: metadata.width! - trimData.width,
|
||||
top: metadata.height! - trimData.height
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Calculate the average center point of all frames
|
||||
const centerPoints = frames.map(frame => ({
|
||||
x: Math.floor(frame.trimmed.left + frame.trimmed.width / 2),
|
||||
y: Math.floor(frame.trimmed.top + frame.trimmed.height / 2)
|
||||
}))
|
||||
|
||||
const avgCenterX = Math.round(centerPoints.reduce((sum, p) => sum + p.x, 0) / frames.length)
|
||||
const avgCenterY = Math.round(centerPoints.reduce((sum, p) => sum + p.y, 0) / frames.length)
|
||||
|
||||
// Create the combined image with precise alignment
|
||||
const combinedImage = await sharp({
|
||||
create: {
|
||||
width: frameWidth * frames.length,
|
||||
@ -127,13 +174,24 @@ export default class SpriteUpdateEvent {
|
||||
}
|
||||
})
|
||||
.composite(
|
||||
frames.map(({ buffer, width, height }, index) => ({
|
||||
input: buffer,
|
||||
// Center horizontally based on the exact middle of each frame
|
||||
left: index * frameWidth + ((frameWidth - width) >> 1),
|
||||
// Top position is always 0
|
||||
top: 0
|
||||
}))
|
||||
frames.map(({ buffer, width, height }, index) => {
|
||||
// Calculate offset to maintain consistent center point
|
||||
const frameCenterX = Math.floor(width / 2)
|
||||
const frameCenterY = Math.floor(height / 2)
|
||||
|
||||
const adjustedLeft = index * frameWidth + (frameWidth / 2) - frameCenterX
|
||||
const adjustedTop = (frameHeight / 2) - frameCenterY
|
||||
|
||||
// Round to nearest even number to prevent sub-pixel rendering
|
||||
const left = Math.round(adjustedLeft / 2) * 2
|
||||
const top = Math.round(adjustedTop / 2) * 2
|
||||
|
||||
return {
|
||||
input: buffer,
|
||||
left,
|
||||
top
|
||||
}
|
||||
})
|
||||
)
|
||||
.png()
|
||||
.toBuffer()
|
||||
|
Loading…
x
Reference in New Issue
Block a user