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 21KB

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