No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SpriteFrameModule.cs 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. using System;
  2. using System.IO;
  3. using UnityEngine;
  4. using UnityEditorInternal;
  5. using System.Collections.Generic;
  6. using System.Text;
  7. using UnityTexture2D = UnityEngine.Texture2D;
  8. using UnityEditor.ShortcutManagement;
  9. namespace UnityEditor.U2D.Sprites
  10. {
  11. [RequireSpriteDataProvider(typeof(ITextureDataProvider))]
  12. internal partial class SpriteFrameModule : SpriteFrameModuleBase
  13. {
  14. public enum AutoSlicingMethod
  15. {
  16. DeleteAll = 0,
  17. Smart = 1,
  18. Safe = 2
  19. }
  20. private bool[] m_AlphaPixelCache;
  21. SpriteFrameModuleContext m_SpriteFrameModuleContext;
  22. public event Action onModuleDeactivated = () => { };
  23. private const float kOverlapTolerance = 0.00001f;
  24. private StringBuilder m_SpriteNameStringBuilder;
  25. private List<Rect> m_PotentialRects;
  26. public List<Rect> potentialRects
  27. {
  28. set => m_PotentialRects = value;
  29. }
  30. public SpriteFrameModule(ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad) :
  31. base("Sprite Editor", sw, es, us, ad)
  32. {}
  33. class SpriteFrameModuleContext : IShortcutContext
  34. {
  35. SpriteFrameModule m_SpriteFrameModule;
  36. public SpriteFrameModuleContext(SpriteFrameModule spriteFrame)
  37. {
  38. m_SpriteFrameModule = spriteFrame;
  39. }
  40. public bool active
  41. {
  42. get { return true; }
  43. }
  44. public SpriteFrameModule spriteFrameModule
  45. {
  46. get { return m_SpriteFrameModule; }
  47. }
  48. }
  49. [FormerlyPrefKeyAs("Sprite Editor/Trim", "#t")]
  50. [Shortcut("Sprite Editor/Trim", typeof(SpriteFrameModuleContext), KeyCode.T, ShortcutModifiers.Shift)]
  51. static void ShortcutTrim(ShortcutArguments args)
  52. {
  53. if (!string.IsNullOrEmpty(GUI.GetNameOfFocusedControl()))
  54. return;
  55. var spriteFrameContext = (SpriteFrameModuleContext)args.context;
  56. spriteFrameContext.spriteFrameModule.TrimAlpha();
  57. spriteFrameContext.spriteFrameModule.spriteEditor.RequestRepaint();
  58. }
  59. public override void OnModuleActivate()
  60. {
  61. base.OnModuleActivate();
  62. spriteEditor.enableMouseMoveEvent = true;
  63. m_SpriteFrameModuleContext = new SpriteFrameModuleContext(this);
  64. ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SpriteFrameModuleContext);
  65. m_SpriteNameStringBuilder = new StringBuilder(GetSpriteNamePrefix() + "_");
  66. m_PotentialRects = null;
  67. }
  68. public override void OnModuleDeactivate()
  69. {
  70. base.OnModuleDeactivate();
  71. ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SpriteFrameModuleContext);
  72. m_PotentialRects = null;
  73. m_AlphaPixelCache = null;
  74. onModuleDeactivated();
  75. }
  76. public static SpriteImportMode GetSpriteImportMode(ISpriteEditorDataProvider dataProvider)
  77. {
  78. return dataProvider == null ? SpriteImportMode.None : dataProvider.spriteImportMode;
  79. }
  80. public override bool CanBeActivated()
  81. {
  82. return GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.Polygon;
  83. }
  84. private string GenerateSpriteNameWithIndex(int startIndex)
  85. {
  86. int originalLength = m_SpriteNameStringBuilder.Length;
  87. m_SpriteNameStringBuilder.Append(startIndex);
  88. var name = m_SpriteNameStringBuilder.ToString();
  89. m_SpriteNameStringBuilder.Length = originalLength;
  90. return name;
  91. }
  92. private int AddSprite(Rect frame, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, int originalCount, ref int nameIndex)
  93. {
  94. int outSprite = -1;
  95. switch (slicingMethod)
  96. {
  97. case AutoSlicingMethod.DeleteAll:
  98. {
  99. while (outSprite == -1)
  100. {
  101. outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
  102. }
  103. }
  104. break;
  105. case AutoSlicingMethod.Smart:
  106. {
  107. outSprite = GetExistingOverlappingSprite(frame, originalCount, true);
  108. if (outSprite != -1)
  109. {
  110. var existingRect = m_RectsCache.spriteRects[outSprite];
  111. existingRect.rect = frame;
  112. existingRect.alignment = (SpriteAlignment)alignment;
  113. existingRect.pivot = pivot;
  114. }
  115. else
  116. {
  117. while (outSprite == -1)
  118. {
  119. outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
  120. }
  121. }
  122. }
  123. break;
  124. case AutoSlicingMethod.Safe:
  125. {
  126. outSprite = GetExistingOverlappingSprite(frame, originalCount);
  127. while (outSprite == -1)
  128. {
  129. outSprite = AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(nameIndex++), Vector4.zero);
  130. }
  131. }
  132. break;
  133. }
  134. return outSprite;
  135. }
  136. private int GetExistingOverlappingSprite(Rect rect, int originalCount, bool bestFit = false)
  137. {
  138. var count = Math.Min(originalCount, m_RectsCache.spriteRects.Count);
  139. int bestRect = -1;
  140. float rectArea = rect.width * rect.height;
  141. if (rectArea < kOverlapTolerance)
  142. return bestRect;
  143. float bestRatio = float.MaxValue;
  144. float bestArea = float.MaxValue;
  145. for (int i = 0; i < count; i++)
  146. {
  147. Rect existingRect = m_RectsCache.spriteRects[i].rect;
  148. if (existingRect.Overlaps(rect))
  149. {
  150. if (bestFit)
  151. {
  152. float dx = Math.Min(rect.xMax, existingRect.xMax) - Math.Max(rect.xMin, existingRect.xMin);
  153. float dy = Math.Min(rect.yMax, existingRect.yMax) - Math.Max(rect.yMin, existingRect.yMin);
  154. float overlapArea = dx * dy;
  155. float overlapRatio = Math.Abs((overlapArea / rectArea) - 1.0f);
  156. float existingArea = existingRect.width * existingRect.height;
  157. if (overlapRatio < bestRatio || (overlapRatio < kOverlapTolerance && existingArea < bestArea))
  158. {
  159. bestRatio = overlapRatio;
  160. if (overlapRatio < kOverlapTolerance)
  161. bestArea = existingArea;
  162. bestRect = i;
  163. }
  164. }
  165. else
  166. {
  167. bestRect = i;
  168. break;
  169. }
  170. }
  171. }
  172. return bestRect;
  173. }
  174. private bool PixelHasAlpha(int x, int y, UnityTexture2D texture)
  175. {
  176. if (m_AlphaPixelCache == null)
  177. {
  178. m_AlphaPixelCache = new bool[texture.width * texture.height];
  179. Color32[] pixels = texture.GetPixels32();
  180. for (int i = 0; i < pixels.Length; i++)
  181. m_AlphaPixelCache[i] = pixels[i].a != 0;
  182. }
  183. int index = y * (int)texture.width + x;
  184. return m_AlphaPixelCache[index];
  185. }
  186. private int AddSprite(Rect rect, int alignment, Vector2 pivot, string name, Vector4 border)
  187. {
  188. if (m_RectsCache.IsNameUsed(name))
  189. return -1;
  190. SpriteRect spriteRect = new SpriteRect();
  191. spriteRect.rect = rect;
  192. spriteRect.alignment = (SpriteAlignment)alignment;
  193. spriteRect.pivot = pivot;
  194. spriteRect.name = name;
  195. spriteRect.originalName = spriteRect.name;
  196. spriteRect.border = border;
  197. spriteRect.spriteID = GUID.Generate();
  198. if (!m_RectsCache.Add(spriteRect))
  199. return -1;
  200. spriteEditor.SetDataModified();
  201. return m_RectsCache.spriteRects.Count - 1;
  202. }
  203. private string GetSpriteNamePrefix()
  204. {
  205. return Path.GetFileNameWithoutExtension(spriteAssetPath);
  206. }
  207. public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod)
  208. {
  209. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Automatic Slicing");
  210. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  211. m_RectsCache.Clear();
  212. var textureToUse = GetTextureToSlice();
  213. List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles((UnityTexture2D)textureToUse, minimumSpriteSize, 0));
  214. if (frames.Count == 0)
  215. frames.Add(new Rect(0, 0, textureToUse.width, textureToUse.height));
  216. int index = 0;
  217. int originalCount = m_RectsCache.spriteRects.Count;
  218. foreach (Rect frame in frames)
  219. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  220. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  221. m_RectsCache.ClearUnusedFileID();
  222. selected = null;
  223. spriteEditor.SetDataModified();
  224. Repaint();
  225. }
  226. UnityTexture2D GetTextureToSlice()
  227. {
  228. int width, height;
  229. m_TextureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
  230. var readableTexture = m_TextureDataProvider.GetReadableTexture2D();
  231. if (readableTexture == null || (readableTexture.width == width && readableTexture.height == height))
  232. return readableTexture;
  233. // we want to slice based on the original texture slice. Upscale the imported texture
  234. var texture = UnityEditor.SpriteUtility.CreateTemporaryDuplicate(readableTexture, width, height);
  235. return texture;
  236. }
  237. public IEnumerable<Rect> GetGridRects(Vector2 size, Vector2 offset, Vector2 padding, bool keepEmptyRects)
  238. {
  239. var textureToUse = GetTextureToSlice();
  240. return InternalSpriteUtility.GenerateGridSpriteRectangles((UnityTexture2D)textureToUse, offset, size, padding, keepEmptyRects);
  241. }
  242. public void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false)
  243. {
  244. var frames = GetGridRects(size, offset, padding, keepEmptyRects);
  245. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Grid Slicing");
  246. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  247. m_RectsCache.Clear();
  248. int index = 0;
  249. int originalCount = m_RectsCache.spriteRects.Count;
  250. foreach (Rect frame in frames)
  251. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  252. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  253. m_RectsCache.ClearUnusedFileID();
  254. selected = null;
  255. spriteEditor.SetDataModified();
  256. Repaint();
  257. }
  258. public IEnumerable<Rect> GetIsometricRects(Vector2 size, Vector2 offset, bool isAlternate, bool keepEmptyRects)
  259. {
  260. var textureToUse = GetTextureToSlice();
  261. var gradient = (size.x / 2) / (size.y / 2);
  262. bool isAlt = isAlternate;
  263. float x = offset.x;
  264. if (isAlt)
  265. x += size.x / 2;
  266. float y = textureToUse.height - offset.y;
  267. while (y - size.y >= 0)
  268. {
  269. while (x + size.x <= textureToUse.width)
  270. {
  271. var rect = new Rect(x, y - size.y, size.x, size.y);
  272. if (!keepEmptyRects)
  273. {
  274. int sx = (int)rect.x;
  275. int sy = (int)rect.y;
  276. int width = (int)size.x;
  277. int odd = ((int)size.y) % 2;
  278. int topY = ((int)size.y / 2) - 1;
  279. int bottomY = topY + odd;
  280. int totalPixels = 0;
  281. int alphaPixels = 0;
  282. {
  283. for (int ry = 0; ry <= topY; ry++)
  284. {
  285. var pixelOffset = Mathf.CeilToInt(gradient * ry);
  286. for (int rx = pixelOffset; rx < width - pixelOffset; ++rx)
  287. {
  288. if (PixelHasAlpha(sx + rx, sy + topY - ry, textureToUse))
  289. alphaPixels++;
  290. if (PixelHasAlpha(sx + rx, sy + bottomY + ry, textureToUse))
  291. alphaPixels++;
  292. totalPixels += 2;
  293. }
  294. }
  295. }
  296. if (odd > 0)
  297. {
  298. int ry = topY + 1;
  299. for (int rx = 0; rx < size.x; ++rx)
  300. {
  301. if (PixelHasAlpha(sx + rx, sy + ry, textureToUse))
  302. alphaPixels++;
  303. totalPixels++;
  304. }
  305. }
  306. if (totalPixels > 0 && ((float)alphaPixels) / totalPixels > 0.01f)
  307. yield return rect;
  308. }
  309. else
  310. yield return rect;
  311. x += size.x;
  312. }
  313. isAlt = !isAlt;
  314. x = offset.x;
  315. if (isAlt)
  316. x += size.x / 2;
  317. y -= size.y / 2;
  318. }
  319. }
  320. public void DoIsometricGridSlicing(Vector2 size, Vector2 offset, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false, bool isAlternate = false)
  321. {
  322. var frames = GetIsometricRects(size, offset, isAlternate, keepEmptyRects);
  323. List<Vector2[]> outlines = new List<Vector2[]>(4);
  324. outlines.Add(new[] { new Vector2(0.0f, -size.y / 2)
  325. , new Vector2(size.x / 2, 0.0f)
  326. , new Vector2(0.0f, size.y / 2)
  327. , new Vector2(-size.x / 2, 0.0f)});
  328. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Isometric Grid Slicing");
  329. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  330. m_RectsCache.Clear();
  331. int index = 0;
  332. var spriteRects = m_RectsCache.GetSpriteRects();
  333. int originalCount = spriteRects.Count;
  334. foreach (var frame in frames)
  335. {
  336. var spriteIndex = AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  337. var outlineRect = new OutlineSpriteRect(spriteRects[spriteIndex]);
  338. outlineRect.outlines = outlines;
  339. spriteRects[spriteIndex] = outlineRect;
  340. }
  341. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  342. m_RectsCache.ClearUnusedFileID();
  343. selected = null;
  344. spriteEditor.SetDataModified();
  345. Repaint();
  346. }
  347. public void ScaleSpriteRect(Rect r)
  348. {
  349. if (selected != null)
  350. {
  351. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Scale sprite");
  352. selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight);
  353. selected.border = ClampSpriteBorderToRect(selected.border, selected.rect);
  354. spriteEditor.SetDataModified();
  355. }
  356. }
  357. public void TrimAlpha()
  358. {
  359. var texture = GetTextureToSlice();
  360. if (texture == null)
  361. return;
  362. Rect rect = selected.rect;
  363. int xMin = (int)rect.xMax;
  364. int xMax = (int)rect.xMin;
  365. int yMin = (int)rect.yMax;
  366. int yMax = (int)rect.yMin;
  367. for (int y = (int)rect.yMin; y < (int)rect.yMax; y++)
  368. {
  369. for (int x = (int)rect.xMin; x < (int)rect.xMax; x++)
  370. {
  371. if (PixelHasAlpha(x, y, texture))
  372. {
  373. xMin = Mathf.Min(xMin, x);
  374. xMax = Mathf.Max(xMax, x);
  375. yMin = Mathf.Min(yMin, y);
  376. yMax = Mathf.Max(yMax, y);
  377. }
  378. }
  379. }
  380. // Case 582309: Return an empty rectangle if no pixel has an alpha
  381. if (xMin > xMax || yMin > yMax)
  382. rect = new Rect(0, 0, 0, 0);
  383. else
  384. rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
  385. if (rect.width <= 0 && rect.height <= 0)
  386. {
  387. m_RectsCache.Remove(selected);
  388. spriteEditor.SetDataModified();
  389. selected = null;
  390. }
  391. else
  392. {
  393. rect = ClampSpriteRect(rect, texture.width, texture.height);
  394. if (selected.rect != rect)
  395. spriteEditor.SetDataModified();
  396. selected.rect = rect;
  397. PopulateSpriteFrameInspectorField();
  398. }
  399. }
  400. public void DuplicateSprite()
  401. {
  402. if (selected != null)
  403. {
  404. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Duplicate sprite");
  405. var index = 0;
  406. var createdIndex = -1;
  407. while (createdIndex == -1)
  408. {
  409. createdIndex = AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border);
  410. }
  411. selected = m_RectsCache.spriteRects[createdIndex];
  412. }
  413. }
  414. public void CreateSprite(Rect rect)
  415. {
  416. rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight);
  417. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Create sprite");
  418. var index = 0;
  419. var createdIndex = -1;
  420. while (createdIndex == -1)
  421. {
  422. createdIndex = AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero);
  423. }
  424. selected = m_RectsCache.spriteRects[createdIndex];
  425. }
  426. public void DeleteSprite()
  427. {
  428. if (selected != null)
  429. {
  430. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Delete sprite");
  431. m_RectsCache.Remove(selected);
  432. selected = null;
  433. spriteEditor.SetDataModified();
  434. }
  435. }
  436. public bool IsOnlyUsingDefaultNamedSpriteRects()
  437. {
  438. var onlyDefaultNames = true;
  439. var names = m_RectsCache.spriteNames;
  440. var defaultName = m_SpriteNameStringBuilder.ToString();
  441. foreach (var name in names)
  442. {
  443. if (!name.Contains(defaultName))
  444. {
  445. onlyDefaultNames = false;
  446. break;
  447. }
  448. }
  449. return onlyDefaultNames;
  450. }
  451. }
  452. }