SpriteFrameModule.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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. public SpriteFrameModule(ISpriteEditor sw, IEventSystem es, IUndoSystem us, IAssetDatabase ad) :
  25. base("Sprite Editor", sw, es, us, ad)
  26. {}
  27. class SpriteFrameModuleContext : IShortcutToolContext
  28. {
  29. SpriteFrameModule m_SpriteFrameModule;
  30. public SpriteFrameModuleContext(SpriteFrameModule spriteFrame)
  31. {
  32. m_SpriteFrameModule = spriteFrame;
  33. }
  34. public bool active
  35. {
  36. get { return true; }
  37. }
  38. public SpriteFrameModule spriteFrameModule
  39. {
  40. get { return m_SpriteFrameModule; }
  41. }
  42. }
  43. [FormerlyPrefKeyAs("Sprite Editor/Trim", "#t")]
  44. [Shortcut("Sprite Editor/Trim", typeof(SpriteFrameModuleContext), KeyCode.T, ShortcutModifiers.Shift)]
  45. static void ShortcutTrim(ShortcutArguments args)
  46. {
  47. if (!string.IsNullOrEmpty(GUI.GetNameOfFocusedControl()))
  48. return;
  49. var spriteFrameContext = (SpriteFrameModuleContext)args.context;
  50. spriteFrameContext.spriteFrameModule.TrimAlpha();
  51. spriteFrameContext.spriteFrameModule.spriteEditor.RequestRepaint();
  52. }
  53. public override void OnModuleActivate()
  54. {
  55. base.OnModuleActivate();
  56. spriteEditor.enableMouseMoveEvent = true;
  57. m_SpriteFrameModuleContext = new SpriteFrameModuleContext(this);
  58. ShortcutIntegration.instance.contextManager.RegisterToolContext(m_SpriteFrameModuleContext);
  59. m_SpriteNameStringBuilder = new StringBuilder(GetSpriteNamePrefix() + "_");
  60. }
  61. public override void OnModuleDeactivate()
  62. {
  63. base.OnModuleDeactivate();
  64. ShortcutIntegration.instance.contextManager.DeregisterToolContext(m_SpriteFrameModuleContext);
  65. m_AlphaPixelCache = null;
  66. }
  67. public static SpriteImportMode GetSpriteImportMode(ISpriteEditorDataProvider dataProvider)
  68. {
  69. return dataProvider == null ? SpriteImportMode.None : dataProvider.spriteImportMode;
  70. }
  71. public override bool CanBeActivated()
  72. {
  73. return GetSpriteImportMode(spriteEditor.GetDataProvider<ISpriteEditorDataProvider>()) != SpriteImportMode.Polygon;
  74. }
  75. private string GenerateSpriteNameWithIndex(int startIndex)
  76. {
  77. int originalLength = m_SpriteNameStringBuilder.Length;
  78. m_SpriteNameStringBuilder.Append(startIndex);
  79. var name = m_SpriteNameStringBuilder.ToString();
  80. m_SpriteNameStringBuilder.Length = originalLength;
  81. return name;
  82. }
  83. // Aurajoki-Sweep Rect Sorting(tm)
  84. // 1. Find top-most rectangle
  85. // 2. Sweep it vertically to find out all rects from that "row"
  86. // 3. goto 1.
  87. // This will give us nicely sorted left->right top->down list of rectangles
  88. // Works for most sprite sheets pretty nicely
  89. private List<Rect> SortRects(List<Rect> rects)
  90. {
  91. List<Rect> result = new List<Rect>();
  92. while (rects.Count > 0)
  93. {
  94. // Because the slicing algorithm works from bottom-up, the topmost rect is the last one in the array
  95. Rect r = rects[rects.Count - 1];
  96. Rect sweepRect = new Rect(0, r.yMin, textureActualWidth, r.height);
  97. List<Rect> rowRects = RectSweep(rects, sweepRect);
  98. if (rowRects.Count > 0)
  99. result.AddRange(rowRects);
  100. else
  101. {
  102. // We didn't find any rects, just dump the remaining rects and continue
  103. result.AddRange(rects);
  104. break;
  105. }
  106. }
  107. return result;
  108. }
  109. private List<Rect> RectSweep(List<Rect> rects, Rect sweepRect)
  110. {
  111. if (rects == null || rects.Count == 0)
  112. return new List<Rect>();
  113. List<Rect> containedRects = new List<Rect>();
  114. foreach (Rect rect in rects)
  115. {
  116. if (rect.Overlaps(sweepRect))
  117. containedRects.Add(rect);
  118. }
  119. // Remove found rects from original list
  120. foreach (Rect rect in containedRects)
  121. rects.Remove(rect);
  122. // Sort found rects by x position
  123. containedRects.Sort((a, b) => a.x.CompareTo(b.x));
  124. return containedRects;
  125. }
  126. private void AddSprite(Rect frame, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, int originalCount, ref int index)
  127. {
  128. switch (slicingMethod)
  129. {
  130. case AutoSlicingMethod.DeleteAll:
  131. {
  132. while (AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(index++), Vector4.zero, false) == -1)
  133. {}
  134. }
  135. break;
  136. case AutoSlicingMethod.Smart:
  137. {
  138. SpriteRect existingSprite = GetExistingOverlappingSprite(frame, originalCount, true);
  139. if (existingSprite != null)
  140. {
  141. existingSprite.rect = frame;
  142. existingSprite.alignment = (SpriteAlignment)alignment;
  143. existingSprite.pivot = pivot;
  144. }
  145. else
  146. while (AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(index++), Vector4.zero) == -1) {}
  147. }
  148. break;
  149. case AutoSlicingMethod.Safe:
  150. {
  151. if (GetExistingOverlappingSprite(frame, originalCount) == null)
  152. while (AddSprite(frame, alignment, pivot, GenerateSpriteNameWithIndex(index++), Vector4.zero) == -1) {}
  153. }
  154. break;
  155. }
  156. }
  157. private SpriteRect GetExistingOverlappingSprite(Rect rect, int originalCount, bool bestFit = false)
  158. {
  159. var count = Math.Min(originalCount, m_RectsCache.spriteRects.Count);
  160. int bestRect = -1;
  161. float rectArea = rect.width * rect.height;
  162. if (rectArea < kOverlapTolerance)
  163. return null;
  164. float bestRatio = float.MaxValue;
  165. float bestArea = float.MaxValue;
  166. for (int i = 0; i < count; i++)
  167. {
  168. Rect existingRect = m_RectsCache.spriteRects[i].rect;
  169. if (existingRect.Overlaps(rect))
  170. {
  171. if (bestFit)
  172. {
  173. float dx = Math.Min(rect.xMax, existingRect.xMax) - Math.Max(rect.xMin, existingRect.xMin);
  174. float dy = Math.Min(rect.yMax, existingRect.yMax) - Math.Max(rect.yMin, existingRect.yMin);
  175. float overlapArea = dx * dy;
  176. float overlapRatio = Math.Abs((overlapArea / rectArea) - 1.0f);
  177. float existingArea = existingRect.width * existingRect.height;
  178. if (overlapRatio < bestRatio || (overlapRatio < kOverlapTolerance && existingArea < bestArea))
  179. {
  180. bestRatio = overlapRatio;
  181. if (overlapRatio < kOverlapTolerance)
  182. bestArea = existingArea;
  183. bestRect = i;
  184. }
  185. }
  186. else
  187. {
  188. bestRect = i;
  189. break;
  190. }
  191. }
  192. }
  193. if (bestRect >= 0)
  194. return m_RectsCache.spriteRects[bestRect];
  195. return null;
  196. }
  197. private bool PixelHasAlpha(int x, int y, UnityTexture2D texture)
  198. {
  199. if (m_AlphaPixelCache == null)
  200. {
  201. m_AlphaPixelCache = new bool[texture.width * texture.height];
  202. Color32[] pixels = texture.GetPixels32();
  203. for (int i = 0; i < pixels.Length; i++)
  204. m_AlphaPixelCache[i] = pixels[i].a != 0;
  205. }
  206. int index = y * (int)texture.width + x;
  207. return m_AlphaPixelCache[index];
  208. }
  209. private int AddSprite(Rect rect, int alignment, Vector2 pivot, string name, Vector4 border, bool uniqueNameCheck = true)
  210. {
  211. var sed = spriteEditor.GetDataProvider<ISpriteEditorDataProvider>();
  212. long internalID = AssetImporter.MakeLocalFileIDWithHash(spriteType.persistentTypeID, name, 0);
  213. if (m_RectsCache.HasName(name))
  214. return -1;
  215. if (m_RectsCache.HasInternalID(internalID))
  216. return -1;
  217. SpriteRect spriteRect = new SpriteRect();
  218. spriteRect.rect = rect;
  219. spriteRect.alignment = (SpriteAlignment)alignment;
  220. spriteRect.pivot = pivot;
  221. spriteRect.name = name;
  222. spriteRect.originalName = spriteRect.name;
  223. spriteRect.border = border;
  224. spriteRect.internalID = internalID;
  225. spriteRect.spriteID = GUID.CreateGUIDFromSInt64(internalID);
  226. // check if someone is using the internal id, if so, we change it to us.
  227. // Only TextureImporter needs this now.
  228. var ai = sed.targetObject as TextureImporter;
  229. var oldName = "";
  230. if (ai != null && ai.GetNameFromInternalIDMap(internalID, ref oldName))
  231. {
  232. if (string.IsNullOrEmpty(oldName))
  233. return -1;
  234. spriteRect.originalName = oldName;
  235. }
  236. else
  237. {
  238. spriteRect.m_RegisterInternalID = true;
  239. }
  240. m_RectsCache.Add(spriteRect);
  241. spriteEditor.SetDataModified();
  242. return m_RectsCache.spriteRects.Count - 1;
  243. }
  244. private string GetSpriteNamePrefix()
  245. {
  246. return Path.GetFileNameWithoutExtension(spriteAssetPath);
  247. }
  248. public void DoAutomaticSlicing(int minimumSpriteSize, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod)
  249. {
  250. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Automatic Slicing");
  251. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  252. m_RectsCache.Clear();
  253. var textureToUse = GetTextureToSlice();
  254. List<Rect> frames = new List<Rect>(InternalSpriteUtility.GenerateAutomaticSpriteRectangles((UnityTexture2D)textureToUse, minimumSpriteSize, 0));
  255. frames = SortRects(frames);
  256. int index = 0;
  257. int originalCount = m_RectsCache.spriteRects.Count;
  258. foreach (Rect frame in frames)
  259. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  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 void DoGridSlicing(Vector2 size, Vector2 offset, Vector2 padding, int alignment, Vector2 pivot, AutoSlicingMethod slicingMethod, bool keepEmptyRects = false)
  276. {
  277. var textureToUse = GetTextureToSlice();
  278. Rect[] frames = InternalSpriteUtility.GenerateGridSpriteRectangles((UnityTexture2D)textureToUse, offset, size, padding, keepEmptyRects);
  279. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Grid Slicing");
  280. if (slicingMethod == AutoSlicingMethod.DeleteAll)
  281. m_RectsCache.Clear();
  282. int index = 0;
  283. int originalCount = m_RectsCache.spriteRects.Count;
  284. foreach (Rect frame in frames)
  285. AddSprite(frame, alignment, pivot, slicingMethod, originalCount, ref index);
  286. selected = null;
  287. spriteEditor.SetDataModified();
  288. Repaint();
  289. }
  290. public void ScaleSpriteRect(Rect r)
  291. {
  292. if (selected != null)
  293. {
  294. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Scale sprite");
  295. selected.rect = ClampSpriteRect(r, textureActualWidth, textureActualHeight);
  296. selected.border = ClampSpriteBorderToRect(selected.border, selected.rect);
  297. spriteEditor.SetDataModified();
  298. }
  299. }
  300. public void TrimAlpha()
  301. {
  302. var texture = GetTextureToSlice();
  303. if (texture == null)
  304. return;
  305. Rect rect = selected.rect;
  306. int xMin = (int)rect.xMax;
  307. int xMax = (int)rect.xMin;
  308. int yMin = (int)rect.yMax;
  309. int yMax = (int)rect.yMin;
  310. for (int y = (int)rect.yMin; y < (int)rect.yMax; y++)
  311. {
  312. for (int x = (int)rect.xMin; x < (int)rect.xMax; x++)
  313. {
  314. if (PixelHasAlpha(x, y, texture))
  315. {
  316. xMin = Mathf.Min(xMin, x);
  317. xMax = Mathf.Max(xMax, x);
  318. yMin = Mathf.Min(yMin, y);
  319. yMax = Mathf.Max(yMax, y);
  320. }
  321. }
  322. }
  323. // Case 582309: Return an empty rectangle if no pixel has an alpha
  324. if (xMin > xMax || yMin > yMax)
  325. rect = new Rect(0, 0, 0, 0);
  326. else
  327. rect = new Rect(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
  328. if (rect.width <= 0 && rect.height <= 0)
  329. {
  330. m_RectsCache.Remove(selected);
  331. spriteEditor.SetDataModified();
  332. selected = null;
  333. }
  334. else
  335. {
  336. rect = ClampSpriteRect(rect, texture.width, texture.height);
  337. if (selected.rect != rect)
  338. spriteEditor.SetDataModified();
  339. selected.rect = rect;
  340. PopulateSpriteFrameInspectorField();
  341. }
  342. }
  343. public void DuplicateSprite()
  344. {
  345. if (selected != null)
  346. {
  347. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Duplicate sprite");
  348. var index = 0;
  349. var createdIndex = -1;
  350. while (createdIndex == -1)
  351. {
  352. createdIndex = AddSprite(selected.rect, (int)selected.alignment, selected.pivot, GenerateSpriteNameWithIndex(index++), selected.border);
  353. }
  354. selected = m_RectsCache.spriteRects[createdIndex];
  355. }
  356. }
  357. public void CreateSprite(Rect rect)
  358. {
  359. rect = ClampSpriteRect(rect, textureActualWidth, textureActualHeight);
  360. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Create sprite");
  361. var index = 0;
  362. var createdIndex = -1;
  363. while (createdIndex == -1)
  364. {
  365. createdIndex = AddSprite(rect, 0, Vector2.zero, GenerateSpriteNameWithIndex(index++), Vector4.zero);
  366. }
  367. selected = m_RectsCache.spriteRects[createdIndex];
  368. }
  369. public void DeleteSprite()
  370. {
  371. if (selected != null)
  372. {
  373. undoSystem.RegisterCompleteObjectUndo(m_RectsCache, "Delete sprite");
  374. m_RectsCache.Remove(selected);
  375. selected = null;
  376. spriteEditor.SetDataModified();
  377. }
  378. }
  379. }
  380. }