unity-find-fault
Version:
A tool to find fault in unity project.
278 lines • 11.7 kB
JavaScript
import sharp from "sharp";
import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import { UnityHelper } from "./UnityHelper.js";
import { PrefabSearcher } from "./PrefabSearcher.js";
import { toolchain } from "../toolchain.js";
export class ImageCutter {
async syncFrom() {
if (!toolchain.opts.input) {
console.error('input required!');
process.exit(1);
}
const fromProj = process.env[toolchain.opts.input.toUpperCase()];
if (!fromProj) {
console.error('from project not defined');
process.exit(1);
}
const imgRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources/ui/atlas');
const fromImgRoot = path.join(fromProj, 'Assets/AssetSources/ui/atlas');
const imgs = await fg(['**/*.png', '**/*.jpg'], { cwd: imgRoot });
for (const img of imgs) {
const fromFile = path.join(fromImgRoot, img);
if (!fs.existsSync(fromFile)) {
continue;
}
const s2 = sharp(fromFile);
const imgMeta2 = await s2.metadata();
if (imgMeta2.width == null || imgMeta2.height == null) {
console.error('Cannot get width/height:', fromFile);
continue;
}
const file = path.join(imgRoot, img);
const s = sharp(file);
const imgMeta = await s.metadata();
if (imgMeta.width == null || imgMeta.height == null) {
console.error('Cannot get width/height:', file);
continue;
}
if (imgMeta.width < imgMeta2.width || imgMeta.height < imgMeta2.height) {
continue;
}
const meta = await UnityHelper.readTextureMeta(file + '.meta');
if (meta.spriteBorder.x == 0 && meta.spriteBorder.y == 0 && meta.spriteBorder.z == 0 && meta.spriteBorder.w == 0) {
continue;
}
const meta2 = await UnityHelper.readTextureMeta(fromFile + '.meta');
if (meta.spriteBorder.x == meta2.spriteBorder.x && meta.spriteBorder.y == meta2.spriteBorder.y && meta.spriteBorder.z == meta2.spriteBorder.z && meta.spriteBorder.w == meta2.spriteBorder.w) {
continue;
}
let newMetaContent = await fs.readFile(file + '.meta', 'utf-8');
newMetaContent = newMetaContent.replace(/spriteBorder: \{x: \d+, y: \d+, z: \d+, w: \d+\}/, `spriteBorder: {x: ${meta2.spriteBorder.x}, y: ${meta2.spriteBorder.y}, z: ${meta2.spriteBorder.z}, w: ${meta2.spriteBorder.w}}`);
await fs.writeFile(file + '.meta', newMetaContent, 'utf-8');
await this.doCut(file, 0, 0, 0, 0);
break;
}
}
async cut() {
if (!toolchain.opts.input) {
console.error('input required!');
process.exit(1);
}
const arr = toolchain.opts.input.split(',');
if (arr.length < 1) {
console.error('input format error! should be like: D:/xxx.png,left=100,right=100 or D:/xxx.png,top=100,bottom=100');
process.exit(1);
}
let files = [];
let file = arr[0];
const mch = file.match(/\[(\d+):(\d+)\]/);
if (mch != null) {
const from = Number(mch[1]);
const to = Number(mch[2]);
for (let i = from; i <= to; i++) {
files.push(file.replace(mch[0], String(i)));
}
}
else {
files.push(file);
}
let left = 0, right = 0, top = 0, bottom = 0;
for (let i = 1, len = arr.length; i < len; i++) {
const [edge, value] = arr[i].split('=');
if (edge == 'left')
left = Number(value);
if (edge == 'right')
right = Number(value);
if (edge == 'top')
top = Number(value);
if (edge == 'bottom')
bottom = Number(value);
}
for (let file of files) {
if (!path.isAbsolute(file))
file = path.join(toolchain.opts.projectRoot, file);
if (!fs.existsSync(file))
continue;
console.log('cut image:', file);
await this.doCut(file, left, right, top, bottom);
}
}
async doCut(file, left, right, top, bottom) {
if (!left && !right && !top && !bottom) {
// 如果没有指定裁剪参数,则默认以九宫格border为限,给中间部位预留2像素
const meta = await UnityHelper.readTextureMeta(file + '.meta');
let cw = 2, ch = 2;
if (meta.spriteBorder.x > 0) {
left = meta.spriteBorder.x + 2;
cw -= 2;
}
if (meta.spriteBorder.z > 0)
right = meta.spriteBorder.z + cw;
if (meta.spriteBorder.y > 0) {
bottom = meta.spriteBorder.y + 2;
ch -= 2;
}
if (meta.spriteBorder.w > 0)
top = meta.spriteBorder.w + ch;
}
if (!left && !right && !top && !bottom) {
console.error('input format error! should be like: D:/xxx.png,left=100,right=100,top=100,bottom=100');
process.exit(1);
}
console.log(`left: ${left}, right: ${right}, top: ${top}, bottom: ${bottom}`);
if (left > 0 || right > 0) {
const d = (left + right) % 4;
if (d != 0) {
// 不是4的倍数,多留2像素
if (left < right) {
left += 4 - d;
}
else {
right += 4 - d;
}
}
await this.cutIt(file, { left, right });
}
if (top > 0 || bottom > 0) {
const d = (top + bottom) % 4;
if (d != 0) {
// 不是4的倍数,多留2像素
if (top < bottom) {
top += 4 - d;
}
else {
bottom += 4 - d;
}
}
await this.cutIt(file, { top, bottom });
}
const prefabSearcher = new PrefabSearcher();
// 设置所有引用该图片的image type为slice
const guid = await UnityHelper.readGUID(file + '.meta');
const potencials = await fg('**/*.prefab', { cwd: toolchain.opts.projectRoot });
let modifiedCnt = 0;
for (const p of potencials) {
const f = path.join(toolchain.opts.projectRoot, p);
const content = await fs.readFile(f, 'utf-8');
if (!content.includes(guid))
continue;
const usages = await prefabSearcher.findImageUsages(f, [guid]);
// 如果被SpriteRenderer使用,不要切
const sr = usages.find((v) => v.type == 'SpriteRenderer');
if (sr) {
console.error('The texture is used as SpriteRenderer\'s sprite, cut is not allowd. Please roll back.');
process.exit(1);
}
const lines = content.split(/\r?\n/);
let isHit = false;
let modified = false;
for (let i = 0, len = lines.length; i < len; i++) {
const line = lines[i];
if (line.startsWith('--- !u!')) {
isHit = false;
continue;
}
if (line.startsWith(' m_Sprite:') && line.includes('guid: ' + guid)) {
isHit = true;
continue;
}
if (isHit && line == ' m_Type: 0') {
lines[i] = ' m_Type: 1';
modified = true;
}
}
if (modified) {
modifiedCnt++;
console.log('M ', f, `[${usages[0].type}]`, usages[0].path);
await fs.writeFile(f, lines.join('\n'), 'utf-8');
}
else {
console.log('- ', f, `[${usages[0].type}]`, usages[0].path);
}
}
console.log(`${modifiedCnt} related file changed`);
}
async cutIt(file, region) {
const s = sharp(file);
const meta = await s.metadata();
if (meta.width == null || meta.height == null) {
console.error('Cannot get width/height:', file);
return;
}
const tmpSave = 'imgcut.tmp' + path.extname(file);
if ('left' in region && 'right' in region) {
if (region.left > 0 && region.right > 0) {
const left = await s.extract({ left: 0, top: 0, width: region.left, height: meta.height }).extend({
top: 0,
bottom: 0,
left: 0,
right: region.right,
background: { r: 0, g: 0, b: 0, alpha: 0 }
});
const right = await sharp(file).extract({ left: meta.width - region.right, top: 0, width: region.right, height: meta.height }).extend({
top: 0,
bottom: 0,
left: region.left,
right: 0,
background: { r: 0, g: 0, b: 0, alpha: 0 }
});
await left.composite([
{
input: await right.toBuffer(),
blend: 'xor'
}
]).toFile(tmpSave);
}
else if (region.left > 0 && region.right == 0) {
await s.extract({ left: 0, top: 0, width: region.left, height: meta.height }).toFile(tmpSave);
}
else if (region.left == 0 && region.right > 0) {
await s.extract({ left: meta.width - region.right, top: 0, width: region.right, height: meta.height }).toFile(tmpSave);
}
else {
return;
}
}
else if ('top' in region && 'bottom' in region) {
if (region.top > 0 && region.bottom > 0) {
const top = await s.extract({ left: 0, top: 0, width: meta.width, height: region.top }).extend({
top: 0,
bottom: region.bottom,
left: 0,
right: 0,
background: { r: 0, g: 0, b: 0, alpha: 0 }
});
const bottom = await sharp(file).extract({ left: 0, top: meta.height - region.bottom, width: meta.width, height: region.bottom }).extend({
top: region.top,
bottom: 0,
left: 0,
right: 0,
background: { r: 0, g: 0, b: 0, alpha: 0 }
});
await top.composite([
{
input: await bottom.toBuffer(),
blend: 'xor'
}
]).toFile(tmpSave);
}
else if (region.top > 0 && region.bottom == 0) {
await s.extract({ left: 0, top: 0, width: meta.width, height: region.top }).toFile(tmpSave);
}
else if (region.top == 0 && region.bottom > 0) {
await s.extract({ left: 0, top: meta.height - region.bottom, width: meta.width, height: region.bottom }).toFile(tmpSave);
}
else {
return;
}
}
else {
return;
}
await fs.copyFile(tmpSave, file);
await fs.unlink(tmpSave);
}
}
//# sourceMappingURL=ImageCutter.js.map