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"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
"node_modules/bullmq": {
|
"node_modules/bullmq": {
|
||||||
"version": "5.34.2",
|
"version": "5.34.3",
|
||||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.2.tgz",
|
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.3.tgz",
|
||||||
"integrity": "sha512-eUzeCswrKbQDE1WY8h4ZTBtynOzCU5qx9felFdYOmIJrsy0warDahHKUiCZ6dUCs6ZxYMGtcaciIMcAf1L54yw==",
|
"integrity": "sha512-S8/V11w7p6jYAGvv+00skLza/4inTOupWPe0uCD8mZSUiYKzvmW4/YEB+KVjZI2CC2oD3KJ3t7/KkUd31MxMig==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cron-parser": "^4.6.0",
|
"cron-parser": "^4.6.0",
|
||||||
@ -1843,9 +1843,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
@ -82,8 +82,9 @@ export default class SpriteUpdateEvent {
|
|||||||
const buffersWithDimensions = await Promise.all(
|
const buffersWithDimensions = await Promise.all(
|
||||||
sprites.map(async (sprite: string) => {
|
sprites.map(async (sprite: string) => {
|
||||||
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
||||||
const { width, height } = await sharp(buffer).metadata()
|
const normalizedBuffer = await normalizeSprite(buffer)
|
||||||
return { buffer, width, height }
|
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[]) {
|
async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) {
|
||||||
const publicFolder = getPublicPath('sprites', id)
|
const publicFolder = getPublicPath('sprites', id)
|
||||||
await mkdir(publicFolder, { recursive: true })
|
await mkdir(publicFolder, { recursive: true })
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
processedActions.map(async ({ action, buffersWithDimensions, frameWidth, frameHeight }) => {
|
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(
|
const frames = await Promise.all(
|
||||||
buffersWithDimensions.map(async ({ buffer }) => {
|
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()
|
const metadata = await sharp(buffer).metadata()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
buffer,
|
buffer,
|
||||||
width: metadata.width!,
|
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({
|
const combinedImage = await sharp({
|
||||||
create: {
|
create: {
|
||||||
width: frameWidth * frames.length,
|
width: frameWidth * frames.length,
|
||||||
@ -127,13 +174,24 @@ export default class SpriteUpdateEvent {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.composite(
|
.composite(
|
||||||
frames.map(({ buffer, width, height }, index) => ({
|
frames.map(({ buffer, width, height }, index) => {
|
||||||
input: buffer,
|
// Calculate offset to maintain consistent center point
|
||||||
// Center horizontally based on the exact middle of each frame
|
const frameCenterX = Math.floor(width / 2)
|
||||||
left: index * frameWidth + ((frameWidth - width) >> 1),
|
const frameCenterY = Math.floor(height / 2)
|
||||||
// Top position is always 0
|
|
||||||
top: 0
|
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()
|
.png()
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user