feat(aklive2d): add protrait images and live2d

This commit is contained in:
Haoyu Xu
2023-02-25 23:58:09 -05:00
parent 7e2a2a1d40
commit 3eed10772c
17 changed files with 90 additions and 29 deletions

View File

@@ -3,12 +3,12 @@ import path from "path";
export default class AlphaComposite {
async process(filename, extractedDir) {
async process(filename, maskFilename, extractedDir) {
const image = sharp(path.join(extractedDir, filename))
.removeAlpha()
const imageMeta = await image.metadata()
const imageBuffer = await image.toBuffer()
const mask = await sharp(path.join(extractedDir, `${path.parse(filename).name}[alpha].png`))
const mask = await sharp(path.join(extractedDir, maskFilename))
.extractChannel("blue")
.resize(imageMeta.width, imageMeta.height)
.toBuffer();
@@ -16,7 +16,16 @@ export default class AlphaComposite {
return sharp(imageBuffer)
.joinChannel(mask)
.toBuffer()
}
async crop(buffer, rect) {
const left = rect.y
const top = rect.x
const width = rect.h
const height = rect.w
const rotate = rect.rotate === 0 ? -90 : 0
const newImage = await sharp(buffer).rotate(90).extract({ left: left, top: top, width: width, height: height }).resize(width, height).extract({ left: 0, top: 0, width: width, height: height }).toBuffer()
return await sharp(newImage).rotate(rotate).toBuffer()
}
}

View File

@@ -1,38 +1,63 @@
import path from 'path'
import { copy, read, write } from './file.js'
import { read, write, readSync } from './file.js'
import AlphaComposite from './alpha_composite.js'
export default class AssetsProcessor {
#operatorSourceFolder
#alphaCompositer
#operatorName
#shareFolder
constructor(operatorName) {
constructor(operatorName, shareFolder) {
this.#operatorSourceFolder = path.join(__projetRoot, __config.folder.operator)
this.#alphaCompositer = new AlphaComposite()
this.#operatorName = operatorName
this.#shareFolder = shareFolder
}
async process(extractedDir) {
const fallback_name = __config.operators[this.#operatorName].fallback_name
const fallbackFilename = `${fallback_name}.png`
const fallbackBuffer = await this.#alphaCompositer.process(fallbackFilename, `${path.parse(fallbackFilename).name}[alpha].png`, extractedDir)
await write(fallbackBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename))
// generate portrait
const portraitDir = path.join(this.#shareFolder, "portraits")
const portraitHub = JSON.parse(readSync(path.join(portraitDir, "MonoBehaviour", "portrait_hub.json")))
const portraitAtlas = portraitHub._sprites.find((item) => item.name === fallback_name).atlas
const portraitJson = JSON.parse(readSync(path.join(portraitDir, "MonoBehaviour", `portraits#${portraitAtlas}.json`)))
const item = portraitJson._sprites.find((item) => item.name === fallback_name)
const rect = {
...item.rect,
rotate: item.rotate
}
const protraitFilename = `portraits#${portraitAtlas}.png`
const portraitBuffer = await this.#alphaCompositer.process(protraitFilename, `${path.parse(protraitFilename).name}a.png`, path.join(portraitDir, "Texture2D"))
const croppedBuffer = await this.#alphaCompositer.crop(portraitBuffer, rect)
await write(croppedBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, `${fallback_name}_portrait.png`))
return {
landscape: await this.#generateAssets(__config.operators[this.#operatorName].filename, extractedDir),
portrait: __config.operators[this.#operatorName].portrait ? await this.#generateAssets(__config.operators[this.#operatorName].portrait, extractedDir) : null
}
}
async #generateAssets(filename, extractedDir) {
const BASE64_BINARY_PREFIX = 'data:application/octet-stream;base64,'
const BASE64_PNG_PREFIX = 'data:image/png;base64,'
const BASE64_PNG_PREFIX = 'data:image/png;base64,'
const assetsJson = {}
const skelFilename = `${__config.operators[this.#operatorName].filename}.skel`
const skelFilename = `${filename}.skel`
const skel = await read(path.join(extractedDir, skelFilename), null)
const atlasFilename = `${__config.operators[this.#operatorName].filename}.atlas`
const atlasFilename = `${filename}.atlas`
const atlas = await read(path.join(extractedDir, atlasFilename))
const dimensions = atlas.match(new RegExp(/^size:(.*),(.*)/gm))[0].replace('size: ', '').split(',')
const matches = atlas.match(new RegExp(/(.*).png/g))
for (const item of matches) {
const buffer = await this.#alphaCompositer.process(item, extractedDir)
const buffer = await this.#alphaCompositer.process(item, `${path.parse(item).name}[alpha].png`, extractedDir)
assetsJson[`./assets/${item}`] = BASE64_PNG_PREFIX + buffer.toString('base64')
}
assetsJson[`./assets/${skelFilename.replace('#', '%23')}`] = BASE64_BINARY_PREFIX + skel.toString('base64')
assetsJson[`./assets/${atlasFilename.replace('#', '%23')}`] = BASE64_BINARY_PREFIX + Buffer.from(atlas).toString('base64')
const fallbackFilename = `${__config.operators[this.#operatorName].fallback_name}.png`
const fallbackBuffer = await this.#alphaCompositer.process(fallbackFilename, extractedDir)
await write(fallbackBuffer, path.join(this.#operatorSourceFolder, this.#operatorName, fallbackFilename))
return {
dimensions,
assetsJson

View File

@@ -10,7 +10,7 @@ function process(config) {
// add title
operator.title = `${config.share.title["en-US"]}${operator.codename["en-US"]} - ${config.share.title["zh-CN"]}${operator.codename["zh-CN"]}`
// add type
operator.type = operator.codename["zh-CN"].includes('·') ? 'operator' : 'skin'
operator.type = operator.codename["zh-CN"].includes('·') ? 'skin' : 'operator'
// add link
operator.link = operatorName

View File

@@ -1,6 +1,11 @@
import path from 'path'
import { writeSync, copy, rmdir } from './file.js'
/**
* TODO:
* 1. add voice config -> look up charword table
*/
export default function () {
const targetFolder = path.join(__projetRoot, __config.folder.release, __config.folder.directory);
const sourceFolder = path.join(__projetRoot, __config.folder.operator);
@@ -13,7 +18,9 @@ export default function () {
}
writeSync(JSON.stringify(directoryJson, null), path.join(targetFolder, "directory.json"))
filesToCopy.forEach((key) => {
const filename = `${__config.operators[key].filename}.json`;
copy(path.join(sourceFolder, key, 'assets.json'), path.join(targetFolder, filename))
copy(path.join(sourceFolder, key, 'assets.json'), path.join(targetFolder, `${__config.operators[key].filename}.json`))
if (__config.operators[key].portrait) {
copy(path.join(sourceFolder, key, 'assets_portrait.json'), path.join(targetFolder, `${__config.operators[key].portrait}.json`))
}
})
}