UNPKG

unity-find-fault

Version:

A tool to find fault in unity project.

278 lines 11.7 kB
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