优化图片对比速度

This commit is contained in:
biaosong 2024-12-10 18:14:38 +08:00
parent 2931b60ab5
commit 0c235c839a
18 changed files with 482 additions and 369 deletions

Binary file not shown.

View File

@ -1,3 +0,0 @@
call main.exe -src image -target image_2 -distance_difference 0.06
pause

View File

@ -1,4 +1,5 @@
#if UNITY_EDITOR
using Sirenix.OdinInspector;
using System;
using Unity.Mathematics;
using UnityEngine;
@ -8,8 +9,10 @@ namespace UguiToolkit.Editor
{
public interface IEntity
{
void SetOriginalMatrix(Transform tf);
void ApplyTransform(Transform tf);
void ApplyTransformByParent(Transform parentTf);
void InternalApplyTransform(Transform tf);
void ApplyTransformByParent(Transform parentTf, Transform tf);
bool IsInside(Transform tf);
void ApplyData<T>(T ui) where T: Component;
void InitPreview();
@ -23,6 +26,8 @@ namespace UguiToolkit.Editor
// ElementInfo
private T2 m_elementInfo;
private UnityEngine.UI.Image m_selectionImg;
private Matrix4x4 lastMoveMatrix;
private Matrix4x4 oldTransformMatrix;
public T2 ElementInfo => m_elementInfo;
public void ShowSelectionImg(bool show)
@ -33,13 +38,45 @@ namespace UguiToolkit.Editor
}
}
public abstract void ApplyTransform(Transform tf);
public abstract void InitPreview();
protected abstract void OnApplyTransform(Transform tf);
protected abstract void OnApplyData(T1 ui);
public virtual void ApplyTransformByParent(Transform parentTf) { }
public virtual void ApplyTransformByParent(Transform parentTf, Transform tf)
{
var position = lastMoveMatrix.GetColumn(3); // 提取位置
var rotation = Quaternion.LookRotation(lastMoveMatrix.GetColumn(2), lastMoveMatrix.GetColumn(1)); // 提取旋转
Debug.Log($"[I] ApplyTransformByParent {position}, {rotation.eulerAngles}");
parentTf.position += new Vector3(position.x, position.y, position.z);
parentTf.rotation *= rotation;
if (tf)
{
var lastTransformMatrixInverse = lastMoveMatrix.inverse;
position = lastTransformMatrixInverse.GetColumn(3); // 提取位置
rotation = Quaternion.LookRotation(lastTransformMatrixInverse.GetColumn(2), lastTransformMatrixInverse.GetColumn(1)); // 提取旋转
tf.position += new Vector3(position.x, position.y, position.z);
tf.rotation *= rotation;
}
}
public void SetOriginalMatrix(Transform tf)
{
oldTransformMatrix = Matrix4x4.TRS(tf.position, tf.rotation, tf.localScale);
}
public void InternalApplyTransform(Transform tf)
{
OnApplyTransform(tf);
}
public void ApplyTransform(Transform tf)
{
Debug.Log($"[I] ApplyTransform {tf.name}");
OnApplyTransform(tf);
Matrix4x4 newTransformMatrix = Matrix4x4.TRS(tf.position, tf.rotation, tf.localScale);
lastMoveMatrix = newTransformMatrix * oldTransformMatrix.inverse;
}
public bool IsInside(Transform tf)
{
@ -74,7 +111,7 @@ namespace UguiToolkit.Editor
rtf.sizeDelta = new Vector2(m_elementInfo.w, m_elementInfo.h);
m_selectionImg = go.AddComponent<UnityEngine.UI.Image>();
m_selectionImg.color = new Color(0, 1, 0, 0.3f);
m_selectionImg.color = new Color(0, 1, 0, 0.4f);
}
}
}

View File

@ -22,6 +22,7 @@ namespace UguiToolkit.Editor
[ShowInInspector]
private Matrix4x4 lastTransformMatrix;
private Image m_previewImage;
// ²éÕÒʱµ÷ÓÃ
@ -34,14 +35,6 @@ namespace UguiToolkit.Editor
this.needFillTransform = true;
}
public override void ApplyTransformByParent(Transform parentTf)
{
var position = lastTransformMatrix.GetColumn(3); // ÌáȡλÖÃ
var rotation = Quaternion.LookRotation(lastTransformMatrix.GetColumn(2), lastTransformMatrix.GetColumn(1)); // ÌáÈ¡Ðýת
parentTf.position += new Vector3(position.x, position.y, position.z);
parentTf.rotation *= rotation;
}
private void LoadImageFromFile(string path)
{
if (System.IO.File.Exists(path))
@ -83,14 +76,11 @@ namespace UguiToolkit.Editor
}
public override void ApplyTransform(Transform tf)
protected override void OnApplyTransform(Transform tf)
{
var rt = tf as RectTransform;
if (needFillTransform)
{
Matrix4x4 oldTransformMatrix = Matrix4x4.TRS(tf.position, tf.rotation, tf.localScale);
var pos = ElementInfo.Position;
var worldPos = StageManager.Instance.PrefabContentsRoot.transform.TransformPoint(new Vector3(pos.x, pos.y, 0));
var anchorMin = rt.anchorMin;
@ -118,6 +108,7 @@ namespace UguiToolkit.Editor
// µ÷Õû´óС
rt.anchorMin = rt.pivot;
rt.anchorMax = rt.pivot;
var oldSizeDelta = rt.sizeDelta;
rt.sizeDelta = new Vector2(size.x, size.y);
rt.anchorMin = anchorMin;
rt.anchorMax = anchorMax;
@ -131,10 +122,6 @@ namespace UguiToolkit.Editor
rt.pivot = oldPiovt;
var offsetRectPos = oldRect.position - rt.rect.position;
rt.Translate(offsetRectPos);
Matrix4x4 newTransformMatrix = Matrix4x4.TRS(tf.position, tf.rotation, tf.localScale);
lastTransformMatrix = newTransformMatrix * oldTransformMatrix.inverse;
}
else
{

View File

@ -13,7 +13,7 @@ namespace UguiToolkit.Editor
{
public class PrefabEntity : BaseEntity<Transform, LayoutInfo.PrefabInfo>
{
public override void ApplyTransform(Transform tf)
protected override void OnApplyTransform(Transform tf)
{
var position = ElementInfo.Position;
tf.position = StageManager.Instance.PrefabContentsRoot.transform.TransformPoint(new Vector3(position.x, position.y, 0));
@ -22,10 +22,9 @@ namespace UguiToolkit.Editor
public override void InitPreview()
{
// ´´½¨Ô¤ÖÆÌå
var setting = GlobalManager.Instance.setting;
var asset = GetPrefabAsset(setting.commonPrefabForUIDirPath);
if (asset != null) {
var go = GameObject.Instantiate(asset, transform);
var asset = GetPrefabAsset(GetCommonDirPath());
if (asset) {
var go = PrefabUtility.InstantiatePrefab(asset, transform) as GameObject;
EntityHelper.UpdateHierarchyAndSceneVisibilityOfEntity(false, go);
go.name = asset.name;
@ -43,12 +42,7 @@ namespace UguiToolkit.Editor
ApplyTransform(ui);
}
public GameObject CopyPrefabGo()
{
return null;
}
private GameObject GetPrefabAsset(string commonDirPath)
public GameObject GetPrefabAsset(string commonDirPath)
{
var elementInfo = ElementInfo;
var guids = AssetDatabase.FindAssets(elementInfo.prefabName, new string[] { commonDirPath });
@ -73,6 +67,12 @@ namespace UguiToolkit.Editor
var setting = GlobalManager.Instance.setting;
return PrefabUtility.IsAnyPrefabInstanceRoot(asset) && PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(asset).StartsWith(setting.commonPrefabForUIDirPath);
}
public static string GetCommonDirPath()
{
var setting = GlobalManager.Instance.setting;
return setting.commonPrefabForUIDirPath;
}
}
}

View File

@ -36,7 +36,7 @@ namespace UguiToolkit.Editor
ApplyTransform(transform);
}
public override void ApplyTransform(Transform tf)
protected override void OnApplyTransform(Transform tf)
{
var position = ElementInfo.Position;
tf.position = StageManager.Instance.PrefabContentsRoot.transform.TransformPoint(new Vector3(position.x, position.y, 0));

View File

@ -13,7 +13,7 @@ namespace UguiToolkit.Editor
{
private TextMeshProUGUI m_previewText;
public override void ApplyTransform(Transform tf)
protected override void OnApplyTransform(Transform tf)
{
var rt = tf as RectTransform;
@ -37,11 +37,6 @@ namespace UguiToolkit.Editor
protected override void OnApplyData(TextMeshProUGUI ui)
{
ui.text = ElementInfo.text;
ui.fontSize = (int)ElementInfo.size;
ui.color = ElementInfo.color;
ui.alignment = TextAlignmentOptions.Center;
SetTMPByTextInfo(ui, ElementInfo);
}
@ -60,9 +55,15 @@ namespace UguiToolkit.Editor
private static void SetTMPByTextInfo(TextMeshProUGUI ui, LayoutInfo.TextInfo textInfo)
{
// TODO: 自行扩展
float2 sizeOffset = float2.zero;
// TODO: 自行扩展
if (!ui.hasStringID) ui.text = textInfo.text;
ui.fontSize = (int)textInfo.size;
ui.color = textInfo.color;
ui.alignment = TextAlignmentOptions.Center;
if (GetTextFontPreset(textInfo, out var textFontPreset))
{
ui.font = textFontPreset.tmpAsset;

View File

@ -1,92 +0,0 @@

#if UNITY_EDITOR
using UnityEngine;
using System.IO;
using Newtonsoft.Json;
using System.Threading.Tasks;
using System;
namespace UguiToolkit.Editor
{
public static class CommandHelper
{
public static void CalcRotScale(string srcImgDirPath, string targetImgDirPath, float distanceDifference, Action<RotScaleJsonData> callback)
{
var rotScaleInfoFilePath = Path.GetFullPath(EditorConst.RotScaleInfoFilePath);
var rotScaleInfoToolFilePath = Path.GetFullPath(EditorConst.RotScaleInfoToolFilePath);
if (File.Exists(rotScaleInfoFilePath)) File.Delete(rotScaleInfoFilePath);
var cmd = string.Format("{0} -src {1} -target {2} -distance_difference {3} -output_path {4} -filter __background__.png",
rotScaleInfoToolFilePath,
Path.GetFullPath(srcImgDirPath),
Path.GetFullPath(targetImgDirPath),
distanceDifference,
Path.GetFullPath(rotScaleInfoFilePath));
Debug.Log("cmd: " + cmd);
_ = RunCmdAsync(cmd, (output, error) =>
{
Debug.Log(output);
if (!File.Exists(rotScaleInfoFilePath))
{
Debug.LogError($"[E] 文件{rotScaleInfoFilePath} 未能正确获得");
Debug.LogError(error);
return;
}
using (StreamReader reader = File.OpenText(rotScaleInfoFilePath))
{
var jsonData = reader.ReadToEnd();
RotScaleJsonData rotScaleJsonData = JsonConvert.DeserializeObject<RotScaleJsonData>(jsonData);
callback(rotScaleJsonData);
}
});
}
// 执行 cmd 命令
public static void RunCmd(in string cmd)
{
var p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + cmd; // 使用 /c 参数执行命令并关闭 cmd 窗口
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
p.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;
p.Start();
var output = p.StandardOutput.ReadToEnd();
var error = p.StandardError.ReadToEnd();
p.WaitForExit();
UnityEngine.Debug.Log("cmd output : " + output);
UnityEngine.Debug.Log("cmd error : " + error);
}
// 异步执行 cmd 命令
public static async Task RunCmdAsync(string cmd, Action<string, string> callback = null)
{
var p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + cmd; // 使用 /c 参数执行命令并关闭 cmd 窗口
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.StandardOutputEncoding = System.Text.Encoding.UTF8;
p.StartInfo.StandardErrorEncoding = System.Text.Encoding.UTF8;
p.Start();
var output = await p.StandardOutput.ReadToEndAsync();
var error = await p.StandardError.ReadToEndAsync();
// 异步等待进程退出
await Task.Run(() => p.WaitForExit());
// 在主线程上调用回调函数
UnityMainThreadDispatcher.Instance().Enqueue(() => callback?.Invoke(output, error));
}
}
}
#endif

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: c5a6dac91d434baab2b66ee97122eaf3
timeCreated: 1718813946

View File

@ -1,7 +1,10 @@
#if UNITY_EDITOR
using OpenCVForUnity.CoreModule;
using OpenCVForUnity.Features2dModule;
using OpenCVForUnity.ImgprocModule;
using OpenCVForUnity.Calib3dModule;
using OpenCVForUnity.UnityUtils;
using System;
using System.IO;
using System.Collections.Generic;
@ -9,9 +12,16 @@ using UnityEngine;
using OpenCVForUnity.ImgcodecsModule;
using System.Threading.Tasks;
using UguiToolkit.Editor;
using System.Security.Cryptography;
using System.Text;
public static class ImageUtils
{
public static void SetDebugMode(bool debug)
{
Utils.setDebugMode(debug);
}
public static string FormatImgFilePath(string imgFilePath)
{
string projectPath = Directory.GetParent(Application.dataPath).FullName;
@ -31,31 +41,60 @@ public static class ImageUtils
}
}
public static async Task ProcessFolderAsync(List<Mat> images, string targetFilePath, RotationScaleDetector detector,
double distanceDifference, Action<int, (double, (double, double))> callback,
static List<Task<(double?, (double, double)?)>> tasks;
static ObjectPool<RotationScaleDetector> detectorPool;
public static async Task ProcessFolderAsync(List<Mat> images, string targetFilePath,
double distanceDifference, Action<int, (double, (double, double), bool)> callback,
Action endCallback)
{
Debug.Log("GetRotationScaleAsync: " + targetFilePath);
if (tasks == null) tasks = new (images.Count);
if (detectorPool == null) detectorPool = new (images.Count);
Mat targetImage = Imgcodecs.imread(targetFilePath);
for (int index = 0; index < images.Count; index++)
List<RotationScaleDetector> detectors = new(images.Count);
tasks.Clear();
foreach (var img in images)
{
RotationScaleDetector detector = detectorPool.GetObject();
tasks.Add(detector.GetRotationScaleAsync(targetImage, img, distanceDifference));
detectors.Add(detector);
}
var resultsTask = await Task.WhenAll(tasks.ToArray());
foreach (var detector in detectors) detectorPool.ReturnObject(detector);
for (int index = 0; index < resultsTask.Length; index++)
{
var img = images[index];
var result = await detector.GetRotationScaleAsync(targetImage, img, distanceDifference);
int _index = index;
var result = resultsTask[index];
UnityMainThreadDispatcher.Instance().Enqueue(() =>
{
if (result.Item1.HasValue && result.Item2.HasValue)
if (result.Item1.HasValue)
{
double rotationAngleDegrees = result.Item1.Value;
var scale = result.Item2.Value;
double scaleX = scale.Item1;
double scaleY = scale.Item2;
double scaleX = 0;
double scaleY = 0;
if (result.Item2.HasValue)
{
var scale = result.Item2.Value;
scaleX = scale.Item1;
scaleY = scale.Item2;
callback(_index, (rotationAngleDegrees, (scaleX, scaleY), false));
}
else
{ // SimilarityCalc
callback(_index, (rotationAngleDegrees, (scaleX, scaleY), true));
}
callback(_index, (rotationAngleDegrees, (scaleX, scaleY)));
Debug.Log($"Target Image -> Image {_index}");
Debug.Log($"Rotation Angle: {rotationAngleDegrees} degrees");
Debug.Log($"Scale X: {scaleX}");
Debug.Log($"Scale Y: {scaleY}");
Debug.Log($"SimilarityCalc : {result.Item2 == null}");
}
});
}
@ -80,6 +119,9 @@ public class RotationScaleDetector
private List<DMatch> goodMatches;
private List<Point> srcPts;
private List<Point> dstPts;
private Mat resizedImage;
private Mat dctImage;
StringBuilder sb;
public RotationScaleDetector()
{
@ -94,6 +136,9 @@ public class RotationScaleDetector
goodMatches = new List<DMatch>();
srcPts = new List<Point>();
dstPts = new List<Point>();
resizedImage = new Mat();
dctImage = new Mat();
sb = new StringBuilder();
}
private KeyPoint[] Sift(Mat image, Mat descriptors)
@ -103,59 +148,192 @@ public class RotationScaleDetector
return keypoints.toArray();
}
public async Task<(double?, (double, double)?)> GetRotationScaleAsync(Mat img0, Mat img1, double distanceDifference)
#region MD5
public string CalculateMD5(Mat image)
{
KeyPoint[] kp1 = Sift(img0, descriptors1);
KeyPoint[] kp2 = Sift(img1, descriptors2);
// 将Mat转换为字节数组
byte[] imageBytes = new byte[image.total() * image.elemSize()];
image.get(0, 0, imageBytes);
if (kp1.Length == 0 || kp2.Length == 0)
// 创建MD5哈希算法实例
using (MD5 md5 = MD5.Create())
{
return (null, null);
// 计算哈希值
byte[] hashBytes = md5.ComputeHash(imageBytes);
// 将哈希字节数组转换为十六进制字符串
sb.Clear();
foreach (byte b in hashBytes)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
#endregion
#region Phash
private Mat CalculatePhash(Mat image)
{
Imgproc.cvtColor(image, gray, Imgproc.COLOR_BGR2GRAY);
// 调整大小为32x32并转换为32位浮点类型
Imgproc.resize(gray, resizedImage, new Size(32, 32), 0, 0, Imgproc.INTER_AREA);
resizedImage.convertTo(resizedImage, CvType.CV_32F);
// 进行离散余弦变换DCT
Core.dct(resizedImage, dctImage);
// 检查矩阵大小
if (dctImage.rows() < 8 || dctImage.cols() < 8)
{
Debug.LogError("DCT matrix is too small!");
return new Mat();
}
// 取左上角8x8的DCT系数
Mat dctLowFreq = dctImage.submat(new OpenCVForUnity.CoreModule.Rect(0, 0, 8, 8));
// 将DCT系数转换为数组
float[] dctArray = new float[64];
dctLowFreq.get(0, 0, dctArray);
// 计算中值
float medianValue = GetMedian(dctArray);
// 生成pHash
Mat phash = new Mat(dctLowFreq.size(), CvType.CV_8U);
for (int i = 0; i < dctLowFreq.rows(); i++)
{
for (int j = 0; j < dctLowFreq.cols(); j++)
{
phash.put(i, j, dctLowFreq.get(i, j)[0] > medianValue ? 1 : 0);
}
}
return phash;
}
private float GetMedian(float[] array)
{
// 排序数组
System.Array.Sort(array);
// 计算中值
int middle = array.Length / 2;
if (array.Length % 2 == 0)
{
return (array[middle - 1] + array[middle]) / 2.0f;
}
else
{
return array[middle];
}
}
private bool IsPhashValid(Mat phash)
{
// 计算pHash中为1的位数
return Core.countNonZero(phash) >= 13;
}
private int CalculateHammingDistance(Mat phash1, Mat phash2)
{
if (phash1.rows() != phash2.rows() || phash1.cols() != phash2.cols())
{
Debug.LogError("pHash sizes do not match!");
return -1;
}
knnMatches.Clear();
goodMatches.Clear();
bf.knnMatch(descriptors1, descriptors2, knnMatches, 2);
foreach (MatOfDMatch matofDMatch in knnMatches)
int hammingDistance = 0;
for (int i = 0; i < phash1.rows(); i++)
{
DMatch[] matches = matofDMatch.toArray();
if (matches[0].distance < distanceDifference * matches[1].distance)
for (int j = 0; j < phash1.cols(); j++)
{
goodMatches.Add(matches[0]);
if (phash1.get(i, j)[0] != phash2.get(i, j)[0])
{
hammingDistance++;
}
}
}
if (goodMatches.Count < 3)
{
return (null, null);
}
return hammingDistance;
}
srcPts.Clear();
dstPts.Clear();
foreach (DMatch match in goodMatches)
{
srcPts.Add(kp1[match.queryIdx].pt);
dstPts.Add(kp2[match.trainIdx].pt);
}
#endregion
MatOfPoint2f srcMatOfPoint2f = new MatOfPoint2f(srcPts.ToArray());
MatOfPoint2f dstMatOfPoint2f = new MatOfPoint2f(dstPts.ToArray());
public async Task<(double?, (double, double)?)> GetRotationScaleAsync(Mat img0, Mat img1, double distanceDifference)
{
return await Task.Run<(double?, (double, double)?)>(() => {
try
{
string md5Hash0 = CalculateMD5(img0);
string md5Hash1 = CalculateMD5(img1);
if (md5Hash0 == md5Hash1)
{
return (0, null);
}
Mat M = Calib3d.estimateAffinePartial2D(srcMatOfPoint2f, dstMatOfPoint2f, inliers, Calib3d.RANSAC, 5);
if (M.empty())
{
return (null, null);
}
Mat phash1 = CalculatePhash(img0);
Mat phash2 = CalculatePhash(img1);
Mat R = M.colRange(0, 2);
double theta = Math.Atan2(R.get(1, 0)[0], R.get(0, 0)[0]);
double rotationAngleDegrees = theta * 180.0 / Math.PI;
if (IsPhashValid(phash1) && IsPhashValid(phash2) && CalculateHammingDistance(phash1, phash2) < 10)
{
return (0, null);
}
double scaleX = Core.norm(R.row(0));
double scaleY = Core.norm(R.row(1));
KeyPoint[] kp1 = Sift(img0, descriptors1);
KeyPoint[] kp2 = Sift(img1, descriptors2);
return (rotationAngleDegrees, (scaleX, scaleY));
if (kp1.Length == 0 || kp2.Length == 0)
{
return (null, null);
}
knnMatches.Clear();
goodMatches.Clear();
bf.knnMatch(descriptors1, descriptors2, knnMatches, 2);
foreach (MatOfDMatch matofDMatch in knnMatches)
{
DMatch[] matches = matofDMatch.toArray();
if (matches[0].distance < distanceDifference * matches[1].distance)
{
goodMatches.Add(matches[0]);
}
}
if (goodMatches.Count < 3)
{
return (null, null);
}
srcPts.Clear();
dstPts.Clear();
foreach (DMatch match in goodMatches)
{
srcPts.Add(kp1[match.queryIdx].pt);
dstPts.Add(kp2[match.trainIdx].pt);
}
MatOfPoint2f srcMatOfPoint2f = new MatOfPoint2f(srcPts.ToArray());
MatOfPoint2f dstMatOfPoint2f = new MatOfPoint2f(dstPts.ToArray());
Mat M = Calib3d.estimateAffinePartial2D(srcMatOfPoint2f, dstMatOfPoint2f, inliers, Calib3d.RANSAC, 5);
if (M.empty())
{
return (null, null);
}
Mat R = M.colRange(0, 2);
double theta = Math.Atan2(R.get(1, 0)[0], R.get(0, 0)[0]);
double rotationAngleDegrees = theta * 180.0 / Math.PI;
double scaleX = Core.norm(R.row(0));
double scaleY = Core.norm(R.row(1));
return (rotationAngleDegrees, (scaleX, scaleY));
}
catch (Exception e)
{
Debug.LogException(e);
return (null, null);
}
});
}
}
#endif

View File

@ -0,0 +1,49 @@
#if UNITY_EDITOR
using System.Collections.Generic;
namespace UguiToolkit.Editor
{
public class ObjectPool<T> where T : new()
{
public int initialSize = 10; // 初始大小
private Queue<T> pool;
public ObjectPool(int initialSize)
{
this.initialSize = initialSize;
this.pool = new Queue<T>(initialSize);
// 预先创建一些对象
for (int i = 0; i < initialSize; i++)
{
T obj = new T();
pool.Enqueue(obj);
}
}
// 获取对象
public T GetObject()
{
if (pool.Count > 0)
{
T obj = pool.Dequeue();
return obj;
}
else
{
// 如果池中没有可用对象,则创建新的对象
T obj = new T();
return obj;
}
}
// 释放对象
public void ReturnObject(T obj)
{
pool.Enqueue(obj);
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dc8e87f03da56414b9de0b42bc4fd282
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -35,6 +35,23 @@ namespace UguiToolkit.Editor
PanelCacheWindow.CloseWindow();
EditWindow.CloseWindow();
}
/// <summary>
/// 创建热键 SPACE
/// </summary>
/// <param name="menuCommand"></param>
[MenuItem("GameObject/拼接助手/应用上次变换 _SPACE", false, 5)]
public static void JumpOfGameCameraByCurPos(MenuCommand menuCommand)
{
if (StageManager.Instance)
{
var entityMng = StageManager.Instance.GetManager<EntityManager>();
if (entityMng && Selection.activeGameObject)
{
entityMng.EffectLastApplyTransform(Selection.activeGameObject.transform);
}
}
}
}
}
#endif

View File

@ -36,14 +36,10 @@ namespace UguiToolkit.Editor
private bool m_useTMP;
private EditWindow editWindow;
private string m_runningImgDirPath = null;
private bool isRunning = false;
private HashSet<string> m_runningImgFilePaths = new();
private List<string> m_preCheckImgDirPaths = new ();
private bool isCalcRotationScaleRunning = false;
private List<OpenCVForUnity.CoreModule.Mat> targetImages;
private List<string> targetPaths;
private RotationScaleDetector detector;
public Transform Background => m_background;
@ -62,7 +58,13 @@ namespace UguiToolkit.Editor
editWindow.showHierarchyOfEntityChanged += OnUpdateHierarchyOfEntityAllEntity;
editWindow.showBackgroundChanged += OnUpdateBackgroundShow;
editWindow.createAllTextEntity += CreateAllTextEntity;
editWindow.createAllPrefabEntity += CreateAllPrefabEntity;
editWindow.effectLastApplyTransform += EffectLastApplyTransform;
}
EditorApplication.hierarchyWindowItemOnGUI += HandleHierarchyWindowItemOnGUI;
ImageUtils.SetDebugMode(true);
}
private void OnDisable()
@ -74,7 +76,15 @@ namespace UguiToolkit.Editor
editWindow.showHierarchyOfEntityChanged -= OnUpdateHierarchyOfEntityAllEntity;
editWindow.showBackgroundChanged -= OnUpdateBackgroundShow;
editWindow.createAllTextEntity -= CreateAllTextEntity;
editWindow.createAllPrefabEntity -= CreateAllPrefabEntity;
editWindow.effectLastApplyTransform -= EffectLastApplyTransform;
}
EditorApplication.hierarchyWindowItemOnGUI -= HandleHierarchyWindowItemOnGUI;
ImageUtils.SetDebugMode(false);
if (m_stageManager.PrefabAsset || m_panelCache != null)
GlobalManager.Instance.SaveCache(m_stageManager.PrefabAsset, m_panelCache);
}
private void Update()
@ -100,7 +110,7 @@ namespace UguiToolkit.Editor
entity.ApplyTransform(tf);
if (PrefabEntity.IsPrefab(tf.gameObject))
{
entity.ApplyData(tf);
entity.ApplyData(tf);
}
else if (tf.TryGetComponent<UnityEngine.UI.Image>(out var image))
{
@ -115,7 +125,6 @@ namespace UguiToolkit.Editor
entity.ApplyData(temp);
}
if (editWindow) editWindow.SetLastApplyEntity(entity);
m_lastSelectionGo = m_curSelectionGo;
m_lastSelectionEntity = entity;
@ -124,58 +133,33 @@ namespace UguiToolkit.Editor
}
}
}
// 及时更新PanelCache
CheckPanelCache();
}
public void AddCheckImgDirPath(string dirPath)
public void EffectLastApplyTransform(Transform parent)
{
//if (string.IsNullOrEmpty(dirPath)) return;
//dirPath = dirPath.Replace("\\", "/");
//if (m_runningImgDirPath != null && m_runningImgDirPath == dirPath) return;
//if (m_panelCache.IsValidOfImgDirPath(dirPath))
//{
// return;
//}
//if (Directory.Exists(dirPath))
//{
// if (m_preCheckImgDirPaths.Contains(dirPath))
// {
// m_preCheckImgDirPaths.Remove(dirPath);
// }
// Debug.Log($"[I] AddCheckImgDirPath {dirPath}");
// m_preCheckImgDirPaths.Insert(0, dirPath);
//}
}
private string GetCheckImgDirPath()
{
if (m_preCheckImgDirPaths.Count > 0)
if (m_lastSelectionEntity != null)
{
string dirPath = m_preCheckImgDirPaths[0];
m_preCheckImgDirPaths.RemoveAt(0);
if (Directory.Exists(dirPath))
{
return dirPath;
}
m_lastSelectionEntity.ApplyTransformByParent(parent, m_lastSelectionGo ? m_lastSelectionGo.transform: null);
}
return null;
}
private void CheckPanelCache()
private void HandleHierarchyWindowItemOnGUI(int instanceID, Rect selectionRect)
{
if (m_runningImgDirPath == null)
if (!m_stageManager) return;
var go = EditorUtility.InstanceIDToObject(instanceID) as GameObject;
if (!go) return;
if (go == m_lastSelectionGo)
{
var srcImgDirPath = GetCheckImgDirPath();
if (srcImgDirPath != null)
var goLabel = new GUIContent(go.name);
var goSize = EditorStyles.linkLabel.CalcSize(goLabel) + new Vector2(20, 0);
var label = new GUIContent(" <-----");
var size = EditorStyles.linkLabel.CalcSize(label) + new Vector2(10, 0);
var offsetRect =
new Rect(selectionRect.position + new Vector2(goSize.x, 0), size);
EditorGUI.LabelField(offsetRect, label, new GUIStyle()
{
UpdatePanelCache(srcImgDirPath, m_panelCache.TargetImgDirPath);
}
normal = new GUIStyleState() { textColor = Color.green },
});
}
}
@ -184,7 +168,6 @@ namespace UguiToolkit.Editor
var root = m_stageManager.PrefabContentsRoot;
var textsTf = root.transform.Find("__texts__");
if (textsTf) DestroyImmediate(textsTf.gameObject);
if (m_textEntities == null) return;
var textsGo = new GameObject("__texts__", typeof(RectTransform));
textsGo.transform.parent = root.transform;
@ -199,6 +182,28 @@ namespace UguiToolkit.Editor
}
}
private void CreateAllPrefabEntity()
{
var root = m_stageManager.PrefabContentsRoot;
var prefabsTf = root.transform.Find("__prefabs__");
if (prefabsTf) DestroyImmediate(prefabsTf.gameObject);
if (m_textEntities == null) return;
var prefabsGo = new GameObject("__prefabs__", typeof(RectTransform));
prefabsGo.transform.parent = root.transform;
foreach (var prefabEntity in m_prefabEntities)
{
var asset = prefabEntity.GetPrefabAsset(PrefabEntity.GetCommonDirPath());
if (asset != null)
{
var go = GameObject.Instantiate(asset, prefabsGo.transform);
go.name = asset.name;
prefabEntity.ApplyData(go.transform);
}
}
}
private void InitBackground()
{
if (m_background) DestroyImmediate(m_background.gameObject);
@ -242,10 +247,10 @@ namespace UguiToolkit.Editor
private void UpdatePanelCache(string srcImgPath)
{
float distanceDifference = GlobalManager.Instance.setting.distanceDifference;
m_runningImgFilePaths.Add(srcImgPath);
isRunning = true;
isCalcRotationScaleRunning = true;
List<RotScaleInfoItem> rotScaleItemList = new();
_ = ImageUtils.ProcessFolderAsync(targetImages, srcImgPath, detector, distanceDifference, (index, result) =>
Debug.Log("GetRotationScaleAsync: " + srcImgPath);
_ = ImageUtils.ProcessFolderAsync(targetImages, srcImgPath, distanceDifference, (index, result) =>
{
if (!m_stageManager) return;
rotScaleItemList.Add(new()
@ -253,13 +258,13 @@ namespace UguiToolkit.Editor
imgPath = ImageUtils.FormatImgFilePath(targetPaths[index]),
rotiation = (float)result.Item1,
scale = new float2((float)result.Item2.Item1, (float)result.Item2.Item1),
similarityCalc = false
similarityCalc = result.Item3
});
}, () =>
{
isRunning = false;
Debug.Log("End GetRotationScaleAsync: " + srcImgPath);
isCalcRotationScaleRunning = false;
if (!m_stageManager) return;
m_runningImgFilePaths.Remove(srcImgPath);
m_panelCache.AddRotScaleInfo(srcImgPath, rotScaleItemList);
if (rotScaleItemList.Count > 0)
@ -267,26 +272,6 @@ namespace UguiToolkit.Editor
});
}
public void UpdatePanelCache(string srcImgDirPath, string targetImgDirPath)
{
float distanceDifference = GlobalManager.Instance.setting.distanceDifference;
m_runningImgDirPath = srcImgDirPath;
CacheScriptObject.CalcRotScaleInfos(srcImgDirPath, targetImgDirPath, distanceDifference,(rotScaleInfoMap) =>
{
m_runningImgDirPath = null;
if (!m_stageManager) return;
m_panelCache.AddImgDirPathTimestamp(srcImgDirPath);
m_panelCache.ClearRotScaleInfoItem(srcImgDirPath);
// 拷贝数据
foreach (var kv in rotScaleInfoMap) m_panelCache.AddRotScaleInfo(kv.Key, kv.Value);
// 保存缓存
GlobalManager.Instance.SaveCache(m_stageManager.PrefabAsset, m_panelCache);
});
}
private void OnSelectionChanged()
{
if (m_noSelection || !Selection.activeGameObject || Selection.gameObjects.Length > 1) return;
@ -310,6 +295,7 @@ namespace UguiToolkit.Editor
{
prefabEntity.ShowSelectionImg(true);
prefabEntity.gameObject.SetActive(true);
prefabEntity.SetOriginalMatrix(activeGameObject.transform);
m_selectionEntities.Add(prefabEntity);
@ -345,6 +331,7 @@ namespace UguiToolkit.Editor
if (!string.IsNullOrEmpty(srcImgPath) && m_panelCache.HaveRotScaleInfo(srcImgPath, out var rotScaleInfoItems))
{
m_entityRoot.gameObject.SetActive(true);
bool isFind;
bool IsInside = false;
foreach (var imgEntity in m_imageEntities)
@ -364,8 +351,9 @@ namespace UguiToolkit.Editor
imgEntity.SetTransform(rotScale.rotiation, rotScale.scale, false);
}
imgEntity.ApplyTransform(imgEntity.transform);
imgEntity.InternalApplyTransform(imgEntity.transform);
imgEntity.ShowSelectionImg(true);
imgEntity.SetOriginalMatrix(activeGameObject.transform);
m_selectionEntities.Add(imgEntity);
if (!IsInside && imgEntity.IsInside(activeGameObject.transform)) IsInside = true;
@ -409,6 +397,7 @@ namespace UguiToolkit.Editor
{
textEntity.ShowSelectionImg(true);
textEntity.gameObject.SetActive(true);
textEntity.SetOriginalMatrix(activeGameObject.transform);
m_selectionEntities.Add(textEntity);
@ -444,7 +433,7 @@ namespace UguiToolkit.Editor
{
var srcImgPath = AssetDatabase.GetAssetPath(image.sprite);
if (!string.IsNullOrEmpty(srcImgPath) && !m_panelCache.HaveRotScaleInfo(srcImgPath) &&
!isRunning)
!isCalcRotationScaleRunning)
{
UpdatePanelCache(srcImgPath);
@ -467,7 +456,6 @@ namespace UguiToolkit.Editor
targetImages = new ();
targetPaths = new ();
ImageUtils.LoadPngImagesFromFolder(panelCache.TargetImgDirPath, targetImages, targetPaths);
detector = new RotationScaleDetector();
}
private void OnUpdateBackgroundShow(bool show)
@ -482,8 +470,10 @@ namespace UguiToolkit.Editor
{
UpdateHierarchyOfEntity(show, m_entityRoot.gameObject);
UpdateHierarchyOfEntity(show, m_background.gameObject);
foreach (Transform entity in m_background.transform) UpdateHierarchyOfEntity(show, entity.gameObject);
foreach (var entity in m_imageEntities) UpdateHierarchyOfEntity(show, entity.gameObject);
foreach (var entity in m_textEntities) UpdateHierarchyOfEntity(show, entity.gameObject);
foreach (var entity in m_prefabEntities) UpdateHierarchyOfEntity(show, entity.gameObject);
}
private void UpdateHierarchyOfEntity(in bool show, in GameObject entity)

View File

@ -53,35 +53,6 @@ namespace UguiToolkit.Editor
return layoutInfo;
}
}
public static void CalcRotScaleInfos(string srcImgDirPath, string targetImgDirPath, float distanceDifference, Action<Dictionary<string, List<RotScaleInfoItem>>> callback)
{
// 执行cmd
CommandHelper.CalcRotScale(srcImgDirPath, targetImgDirPath, distanceDifference,(jsonData) =>
{
if (jsonData == null || jsonData.data == null) return;
Dictionary<string, List<RotScaleInfoItem>> rotScaleInfos = new();
string projectPath = Directory.GetParent(Application.dataPath).FullName;
foreach (var kv in jsonData.data)
{
List<RotScaleInfoItem> rotScaleItemList = new();
rotScaleInfos[ImageUtils.FormatImgFilePath(kv.Key)] = rotScaleItemList;
foreach (var jsonItemData in kv.Value)
{
rotScaleItemList.Add(new()
{
imgPath = ImageUtils.FormatImgFilePath(jsonItemData.targetPath),
rotiation = jsonItemData.rot,
scale = new float2(jsonItemData.scale[0], jsonItemData.scale[1]),
similarityCalc = jsonItemData.similarityCalc
});
}
}
callback(rotScaleInfos);
});
}
}
[Serializable]
@ -94,8 +65,6 @@ namespace UguiToolkit.Editor
[Serializable]
public class PanelCache
{
[LabelText("项目内导出图片文件夹"), FolderPath]
public string srcImgDirPath;
[LabelText("目标图片信息文件(psd导出)"), Sirenix.OdinInspector.FilePath(AbsolutePath = true, Extensions = "layout.txt")]
public string layoutInfoFilePath; // Sample.layout.txt
[LabelText("目标图片文件夹路径")]
@ -123,15 +92,12 @@ namespace UguiToolkit.Editor
public LayoutInfo layoutInfo;
[SerializeField, HideInInspector]
private string m_targetImgDirPath;
[SerializeField]
public Dictionary<string, long> imgDirPathTimestamp = new();
private PanelCache() { }
// public PanelCache(string srcImgDirPath, string layoutInfoFilePath)
public PanelCache(string layoutInfoFilePath, string srcImgDirPath, bool isVertical = false)
public PanelCache(string layoutInfoFilePath, bool isVertical = false)
{
this.srcImgDirPath = srcImgDirPath;
this.layoutInfoFilePath = layoutInfoFilePath;
this.isVertical = isVertical;
@ -141,7 +107,6 @@ namespace UguiToolkit.Editor
public void Copy(PanelCache panelCache)
{
rotScaleInfos = panelCache.rotScaleInfos;
imgDirPathTimestamp = panelCache.imgDirPathTimestamp;
}
public bool HaveRotScaleInfo(string srcImgPath)
@ -159,32 +124,32 @@ namespace UguiToolkit.Editor
rotScaleInfos[srcImgPath] = rotScaleInfo;
}
public bool IsValidOfImgDirPath(string imgDirPath)
public void InitRotScaleInfos()
{
if (!Directory.Exists(imgDirPath)) return false;
if (imgDirPathTimestamp.TryGetValue(imgDirPath, out var timestamp))
HashSet<string> keys = new();
foreach (var srcImgPath in rotScaleInfos.Keys)
{
var curTimestamp = GetTimestampByDirPath(imgDirPath);
return curTimestamp == timestamp;
if (!File.Exists(srcImgPath))
{
keys.Add(srcImgPath);
}
}
foreach (var key in keys)
{
rotScaleInfos.Remove(key);
}
return false;
}
public void ClearRotScaleInfoItem(string imgDirPath)
{
List<int> indeces = new();
List<int> indeces = new();
foreach (var rotScaleInfo in rotScaleInfos.Values)
{
indeces.Clear();
for (var i = 0; i < rotScaleInfo.Count; i++)
{
{
var item = rotScaleInfo[i];
if (item.imgPath.StartsWith(imgDirPath))
if (!File.Exists(item.imgPath))
{
indeces.Add(i);
}
}
}
indeces.Sort();
@ -197,27 +162,9 @@ namespace UguiToolkit.Editor
}
}
public void AddImgDirPathTimestamp(string imgDirPath)
{
var curTimestamp = GetTimestampByDirPath(imgDirPath);
imgDirPathTimestamp[imgDirPath] = curTimestamp;
}
public void InitRotScaleInfos()
{
foreach(var dirPath in imgDirPathTimestamp.Keys)
{
if (!IsValidOfImgDirPath(dirPath))
{
ClearRotScaleInfoItem(dirPath);
}
}
}
public void ClearRotScaleInfos()
{
rotScaleInfos.Clear();
imgDirPathTimestamp.Clear();
}
public IEnumerable<T> GetLayoutElementInfos<T>() where T : LayoutInfo.ElementInfo
@ -243,14 +190,6 @@ namespace UguiToolkit.Editor
return System.IO.Path.Join(dirName, split[0]);
}
public static long GetTimestampByDirPath(string dirPath)
{
DirectoryInfo directoryInfo = new DirectoryInfo(dirPath);
// 获取文件夹的最后修改时间
DateTime lastModified = directoryInfo.LastWriteTime;
return new DateTimeOffset(lastModified).ToUnixTimeSeconds();
}
}
[Serializable]

View File

@ -24,6 +24,16 @@ namespace UguiToolkit.Editor.Windows
/// </summary>
public event Action createAllTextEntity;
/// <summary>
/// 创建所有PrefabEntity
/// </summary>
public event Action createAllPrefabEntity;
/// <summary>
/// 应用上次变换
/// </summary>
public event Action<Transform> effectLastApplyTransform;
[SerializeField, HideInInspector]
private bool m_showHierarchyOfEntityChange = false;
@ -59,31 +69,26 @@ namespace UguiToolkit.Editor.Windows
createAllTextEntity?.Invoke();
}
[Button("创建所有通用预制体")]
private void CreateAllPrefabEntity()
{
createAllPrefabEntity?.Invoke();
}
[Title("工具")]
[LabelText("上次变换的对象"), SerializeField]
private IEntity m_lastApplyEntity;
[LabelText("需要应用变换的对象"), SerializeField]
private Transform m_targetTransform;
[Button("应用上次变换")]
[Button("应用上次变换 (SPACE)")]
private void EffectLastApplyTransform()
{
if (m_targetTransform)
{
var m_lastApplyTransform = m_lastApplyEntity.gameObject.transform;
var parent = m_lastApplyTransform.parent;
m_lastApplyTransform.parent = null;
m_lastApplyEntity.ApplyTransformByParent(m_targetTransform);
m_lastApplyTransform.parent = parent;
effectLastApplyTransform?.Invoke(m_targetTransform);
}
}
public void SetLastApplyEntity(IEntity lastApplyTransform)
{
this.m_lastApplyEntity = lastApplyTransform;
}
public override string GettitleContent()
{
return "助手编辑界面";

View File

@ -17,8 +17,6 @@ namespace UguiToolkit.Editor.Windows
private bool m_isVertical = false;
[Title("psd导出数据设置")]
[LabelText("项目中切图文件夹"), FolderPath, SerializeField]
private string m_srcImgDirPath;
[LabelText("layout.txt文件"), Sirenix.OdinInspector.FilePath(AbsolutePath = true, Extensions = "layout.txt"), SerializeField]
private string m_layoutInfoFilePath;
@ -30,6 +28,12 @@ namespace UguiToolkit.Editor.Windows
[Button("开启助手", ButtonSizes.Medium)]
private void DoStart()
{
if (string.IsNullOrEmpty(m_layoutInfoFilePath))
{
EditorUtility.DisplayDialog("提示", "请先填写配置", "好的");
return;
}
var stageManager = UguiToolkit.StageManager.CreateStageManager(m_prefabStage.scene, m_prefabStage.prefabContentsRoot, m_prefab);
stageManager.gameObject.AddComponent<UnityMainThreadDispatcher>();
@ -37,7 +41,7 @@ namespace UguiToolkit.Editor.Windows
EditWindow.ShowWindow(null);
var targetImgDirPath = PanelCache.GetTargetImgDirPath(m_layoutInfoFilePath);
var panelCache = new PanelCache(m_layoutInfoFilePath, m_srcImgDirPath, m_isVertical);
var panelCache = new PanelCache(m_layoutInfoFilePath, m_isVertical);
panelCache.layoutInfo = CacheScriptObject.PaserLayout(m_layoutInfoFilePath, targetImgDirPath);
if (m_panelCache != null)
@ -55,8 +59,6 @@ namespace UguiToolkit.Editor.Windows
// stageManager.CreateSubManager<SelectionManager>();
entityManager.Init(panelCache);
if (!string.IsNullOrEmpty(m_srcImgDirPath))
entityManager.AddCheckImgDirPath(m_srcImgDirPath);
CloseWindow();
}
@ -82,12 +84,10 @@ namespace UguiToolkit.Editor.Windows
var panelCache = GlobalManager.Instance.GetCache(m_prefab, m_isVertical);
if (panelCache != null)
{
m_srcImgDirPath = panelCache.srcImgDirPath;
m_layoutInfoFilePath = panelCache.layoutInfoFilePath;
m_panelCache = panelCache;
}
else {
m_srcImgDirPath = null;
m_layoutInfoFilePath = null;
m_panelCache = null;
}

View File

@ -5,7 +5,7 @@
> 1. psd 导出自动切图,并处理九宫格
> 2. unity拼接时对psd上所标记的像素图层进行吸附设置位置和旋转、缩放
> 3. unity拼接时对psd上所标记的文本图层进行吸附设置位置和旋转、缩放并设置字体、字体大小、字体颜色、描边等
> 4. (未实现) unity拼接时对公共组件预制体进行设置位置和旋转、缩放
> 4. unity拼接时对公共组件预制体进行设置位置和旋转、缩放
## 工作流
![](./.res/流程图.png)
@ -32,13 +32,10 @@
### unity中如何操作
上述`yueka_output`为最终进入项目的切图,可自行根据分类放入 `client\Assets\res\ui\atlas`
#### 进入预制体场景
点击开启助手,设置如下路径后,点击`开启助手`
1. 项目内导出图片文件夹:填入项目内该功能的切图目录
2. 目标图片信息文件夹: 填入导出psd数据的 ` yueka.layout.txt`
1. 目标图片信息文件夹: 填入导出psd数据的 ` yueka.layout.txt`
![img](F:\c1workspace\svn\__workspace__dev__\client\PackagesSource\com.txcombo.c1.ugui-toolkit\.res\开启助手.png)
@ -49,6 +46,6 @@
##### 文本
![](./.res/创建text.png)
![](./.res/创建text后的结果.png)
##### 预制体 (未实现)
1. 取消节点选中后,会显示界面所有可供创建的预制体预览
##### 预制体
1. 选中预制体节点后,会显示界面所有可供创建的预制体预览
2. 将鼠标光标放入预制体预览框,会自动创建预制体