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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  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. m_RectsCache.Add(spriteRect);
  239. spriteEditor.SetDataModified();
  240. return m_RectsCache.spriteRects.Count - 1;
  241. }
  242. private string GetSpriteNamePrefix()
  243. {
  244. return Path.GetFileNameWithoutExtension(spriteAssetPath);
  245. }
  246. public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod)
  247. {
  248. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Automatic Slicing");
  249. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  250. m_RectsCache.Clear();
  251. var textureToUse = GetTextureToSlice();
  252. List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles((UnityTexture2D)textureToUse, minimumSpriteSize, 0));
  253. frames = SortRects(frames);
  254. int index = 0;
  255. int originalCount = m_RectsCache.spriteRects.Count;
  256. foreach (Rect frame in frames)
  257. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  258. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  259. m_RectsCache.ClearUnusedFileID();
  260. selected = null;
  261. spriteEditor.SetDataModified();
  262. Repaint();
  263. }
  264. UnityTexture2D GetTextureToSlice()
  265. {
  266. int width, height;
  267. m_TextureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
  268. var readableTexture = m_TextureDataProvider.GetReadableTexture2D();
  269. if (readableTexture == null || (readableTexture.width == width && readableTexture.height == height))
  270. return readableTexture;
  271. // we want to slice based on the original texture slice. Upscale the imported texture
  272. var texture = UnityEditor.SpriteUtility.CreateTemporaryDuplicate(readableTexture, width, height);
  273. return texture;
  274. }
  275. public IEnumerable<Rect> GetGridRects(Vector2 size, Vector2 offset, Vector2 padding, bool keepEmptyRects)
  276. {
  277. var textureToUse = GetTextureToSlice();
  278. return InternalSpriteUtility.GenerateGridSpriteRectangles((UnityTexture2D)textureToUse, offset, size, padding, keepEmptyRects);
  279. }
  280. public void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false)
  281. {
  282. var frames = GetGridRects(size, offset, padding, keepEmptyRects);
  283. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Grid Slicing");
  284. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  285. m_RectsCache.Clear();
  286. int index = 0;
  287. int originalCount = m_RectsCache.spriteRects.Count;
  288. foreach (Rect frame in frames)
  289. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  290. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  291. m_RectsCache.ClearUnusedFileID();
  292. selected = null;
  293. spriteEditor.SetDataModified();
  294. Repaint();
  295. }
  296. public IEnumerable<Rect> GetIsometricRects(Vector2 size, Vector2 offset, bool isAlternate, bool keepEmptyRects)
  297. {
  298. var textureToUse = GetTextureToSlice();
  299. var gradient = (size.x / 2) / (size.y / 2);
  300. bool isAlt = isAlternate;
  301. float x = offset.x;
  302. if (isAlt)
  303. x += size.x / 2;
  304. float y = textureToUse.height - offset.y;
  305. while (y - size.y >= 0)
  306. {
  307. while (x + size.x <= textureToUse.width)
  308. {
  309. var rect = new Rect(x, y - size.y, size.x, size.y);
  310. if (!keepEmptyRects)
  311. {
  312. int sx = (int)rect.x;
  313. int sy = (int)rect.y;
  314. int width = (int)size.x;
  315. int odd = ((int)size.y) % 2;
  316. int topY = ((int)size.y / 2) - 1;
  317. int bottomY = topY + odd;
  318. int totalPixels = 0;
  319. int alphaPixels = 0;
  320. {
  321. for (int ry = 0; ry <= topY; ry++)
  322. {
  323. var pixelOffset = Mathf.CeilToInt(gradient * ry);
  324. for (int rx = pixelOffset; rx < width - pixelOffset; ++rx)
  325. {
  326. if (PixelHasAlpha(sx + rx, sy + topY - ry, textureToUse))
  327. alphaPixels++;
  328. if (PixelHasAlpha(sx + rx, sy + bottomY + ry, textureToUse))
  329. alphaPixels++;
  330. totalPixels += 2;
  331. }
  332. }
  333. }
  334. if (odd > 0)
  335. {
  336. int ry = topY + 1;
  337. for (int rx = 0; rx < size.x; ++rx)
  338. {
  339. if (PixelHasAlpha(sx + rx, sy + ry, textureToUse))
  340. alphaPixels++;
  341. totalPixels++;
  342. }
  343. }
  344. if (totalPixels > 0 && ((float)alphaPixels) / totalPixels > 0.01f)
  345. yield return rect;
  346. }
  347. else
  348. yield return rect;
  349. x += size.x;
  350. }
  351. isAlt = !isAlt;
  352. x = offset.x;
  353. if (isAlt)
  354. x += size.x / 2;
  355. y -= size.y / 2;
  356. }
  357. }
  358. public void DoIsometricGridSlicing(Vector2 size, Vector2 offset, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false, bool isAlternate = false)
  359. {
  360. var frames = GetIsometricRects(size, offset, isAlternate, keepEmptyRects);
  361. List<Vector2[]> outlines = new List<Vector2[]>(4);
  362. outlines.Add(new[] { new Vector2(0.0f, -size.y / 2)
  363. , new Vector2(size.x / 2, 0.0f)
  364. , new Vector2(0.0f, size.y / 2)
  365. , new Vector2(-size.x / 2, 0.0f)});
  366. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Isometric Grid Slicing");
  367. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  368. m_RectsCache.Clear();
  369. int index = 0;
  370. var spriteRects = m_RectsCache.GetSpriteRects();
  371. int originalCount = spriteRects.Count;
  372. foreach (var frame in frames)
  373. {
  374. var spriteIndex = AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  375. var outlineRect = new OutlineSpriteRect(spriteRects[spriteIndex]);
  376. outlineRect.outlines = outlines;
  377. spriteRects[spriteIndex] = outlineRect;
  378. }
  379. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  380. m_RectsCache.ClearUnusedFileID();
  381. selected = null;
  382. spriteEditor.SetDataModified();
  383. Repaint();
  384. }
  385. public void ScaleSpriteRect(Rect r)
  386. {
  387. if (selected != null)
  388. {
  389. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Scale sprite");
  390. selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight);
  391. selected.border = ClampSpriteBorderToRect(selected.border, selected.rect);
  392. spriteEditor.SetDataModified();
  393. }
  394. }
  395. public void TrimAlpha()
  396. {
  397. var texture = GetTextureToSlice();
  398. if (texture == null)
  399. return;
  400. Rect rect = selected.rect;
  401. int xMin = (int)rect.xMax;
  402. int xMax = (int)rect.xMin;
  403. int yMin = (int)rect.yMax;
  404. int yMax = (int)rect.yMin;
  405. for (int y = (int)rect.yMin; y < (int)rect.yMax; y++)
  406. {
  407. for (int x = (int)rect.xMin; x < (int)rect.xMax; x++)
  408. {
  409. if (PixelHasAlpha(x, y, texture))
  410. {
  411. xMin = Mathf.Min(xMin, x);
  412. xMax = Mathf.Max(xMax, x);
  413. yMin = Mathf.Min(yMin, y);
  414. yMax = Mathf.Max(yMax, y);
  415. }
  416. }
  417. }
  418. // Case 582309: Return an empty rectangle if no pixel has an alpha
  419. if (xMin > xMax || yMin > yMax)
  420. rect = new Rect(0, 0, 0, 0);
  421. else
  422. rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
  423. if (rect.width <= 0 && rect.height <= 0)
  424. {
  425. m_RectsCache.Remove(selected);
  426. spriteEditor.SetDataModified();
  427. selected = null;
  428. }
  429. else
  430. {
  431. rect = ClampSpriteRect(rect, texture.width, texture.height);
  432. if (selected.rect != rect)
  433. spriteEditor.SetDataModified();
  434. selected.rect = rect;
  435. PopulateSpriteFrameInspectorField();
  436. }
  437. }
  438. public void DuplicateSprite()
  439. {
  440. if (selected != null)
  441. {
  442. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Duplicate sprite");
  443. var index = 0;
  444. var createdIndex = -1;
  445. while (createdIndex == -1)
  446. {
  447. createdIndex = AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border);
  448. }
  449. selected = m_RectsCache.spriteRects[createdIndex];
  450. }
  451. }
  452. public void CreateSprite(Rect rect)
  453. {
  454. rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight);
  455. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Create sprite");
  456. var index = 0;
  457. var createdIndex = -1;
  458. while (createdIndex == -1)
  459. {
  460. createdIndex = AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero);
  461. }
  462. selected = m_RectsCache.spriteRects[createdIndex];
  463. }
  464. public void DeleteSprite()
  465. {
  466. if (selected != null)
  467. {
  468. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Delete sprite");
  469. m_RectsCache.Remove(selected);
  470. selected = null;
  471. spriteEditor.SetDataModified();
  472. }
  473. }
  474. }
  475. }