Keine Beschreibung
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

ImagePacker.cs 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. //#define PACKING_DEBUG
  2. using System;
  3. using Unity.Burst;
  4. using Unity.Mathematics;
  5. using UnityEngine;
  6. using Unity.Collections;
  7. using Unity.Collections.LowLevel.Unsafe;
  8. namespace UnityEditor.U2D.Aseprite.Common
  9. {
  10. [BurstCompile]
  11. internal static class ImagePacker
  12. {
  13. /// <summary>
  14. /// Given an array of rects, the method returns an array of rects arranged within outPackedWidth and outPackedHeight
  15. /// </summary>
  16. /// <param name="rects">Rects to pack</param>
  17. /// <param name="padding">Padding between each rect</param>
  18. /// <param name="requireSquarePOT">If true, the final texture will be power of two with equally sized sides</param>
  19. /// <param name="outPackedRects">Rects arranged within outPackedWidth and outPackedHeight</param>
  20. /// <param name="outPackedWidth">Width of the packed rects</param>
  21. /// <param name="outPackedHeight">Height of the packed rects</param>
  22. public static void Pack(RectInt[] rects, int padding, bool requireSquarePOT, out RectInt[] outPackedRects, out int outPackedWidth, out int outPackedHeight)
  23. {
  24. var packNode = InternalPack(rects, padding, requireSquarePOT);
  25. outPackedWidth = packNode.rect.width;
  26. outPackedHeight = packNode.rect.height;
  27. var visitor = new CollectPackNodePositionVisitor();
  28. packNode.AcceptVisitor(visitor);
  29. outPackedRects = new RectInt[rects.Length];
  30. for (int i = 0; i < rects.Length; ++i)
  31. outPackedRects[i] = new RectInt(visitor.positions[i].x + padding, visitor.positions[i].y + padding, rects[i].width, rects[i].height);
  32. #if PACKING_DEBUG
  33. var emptyNodeCollector = new CollectEmptyNodePositionVisitor();
  34. packNode.AcceptVisitor(emptyNodeCollector);
  35. Array.Resize(ref outPackedRects, rects.Length + emptyNodeCollector.emptyAreas.Count);
  36. for (int i = rects.Length; i < outPackedRects.Length; ++i)
  37. outPackedRects[i] = emptyNodeCollector.emptyAreas[i - rects.Length];
  38. #endif
  39. }
  40. /// <summary>
  41. /// Packs image buffer into a single buffer. Image buffers are assumed to be 4 bytes per pixel in RGBA format
  42. /// </summary>
  43. /// <param name="buffers">Image buffers to pack</param>
  44. /// <param name="width">Image buffers width</param>
  45. /// <param name="height">Image buffers height</param>
  46. /// <param name="padding">Padding between each packed image</param>
  47. /// <param name="spriteSizeExpand">Pack sprite expand size</param>
  48. /// <param name="requireSquarePOT">If true, the final texture will be power of two with equally sized sides</param>
  49. /// <param name="outPackedBuffer">Packed image buffer</param>
  50. /// <param name="outPackedBufferWidth">Packed image buffer's width</param>
  51. /// <param name="outPackedBufferHeight">Packed image buffer's height</param>
  52. /// <param name="outPackedRect">Location of each image buffers in the packed buffer</param>
  53. /// <param name="outUVTransform">Translation data from image original buffer to packed buffer</param>
  54. public static void Pack(NativeArray<Color32>[] buffers, int[] width, int[] height, int padding, uint spriteSizeExpand, bool requireSquarePOT, out NativeArray<Color32> outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform)
  55. {
  56. UnityEngine.Profiling.Profiler.BeginSample("Pack");
  57. // Determine the area that contains data in the buffer
  58. outPackedBuffer = default(NativeArray<Color32>);
  59. try
  60. {
  61. var tightRects = FindTightRectJob.Execute(buffers, width, height);
  62. var tightRectArea = new RectInt[tightRects.Length];
  63. for (var i = 0; i < tightRects.Length; ++i)
  64. {
  65. var t = tightRects[i];
  66. t.width = tightRects[i].width + (int)spriteSizeExpand;
  67. t.height = tightRects[i].height + (int)spriteSizeExpand;
  68. tightRectArea[i] = t;
  69. }
  70. Pack(tightRectArea, padding, requireSquarePOT, out outPackedRect, out outPackedBufferWidth, out outPackedBufferHeight);
  71. var packBufferSize = (ulong)outPackedBufferWidth * (ulong)outPackedBufferHeight;
  72. if (packBufferSize < 0 || packBufferSize >= int.MaxValue)
  73. {
  74. throw new ArgumentException("Unable to create pack texture. Image size is too big to pack.");
  75. }
  76. outUVTransform = new Vector2Int[tightRectArea.Length];
  77. for (var i = 0; i < outUVTransform.Length; ++i)
  78. {
  79. outUVTransform[i] = new Vector2Int(outPackedRect[i].x - tightRects[i].x, outPackedRect[i].y - tightRects[i].y);
  80. }
  81. outPackedBuffer = new NativeArray<Color32>(outPackedBufferWidth * outPackedBufferHeight, Allocator.Persistent);
  82. Blit(outPackedBuffer, outPackedRect, outPackedBufferWidth, buffers, tightRects, width, padding);
  83. }
  84. catch (Exception ex)
  85. {
  86. if (outPackedBuffer.IsCreated)
  87. outPackedBuffer.Dispose();
  88. throw ex;
  89. }
  90. finally
  91. {
  92. UnityEngine.Profiling.Profiler.EndSample();
  93. }
  94. }
  95. static ImagePackNode InternalPack(RectInt[] rects, int padding, bool requireSquarePOT)
  96. {
  97. if (rects == null || rects.Length == 0)
  98. return new ImagePackNode() { rect = new RectInt(0, 0, 0, 0) };
  99. var sortedRects = new ImagePackRect[rects.Length];
  100. for (var i = 0; i < rects.Length; ++i)
  101. {
  102. sortedRects[i] = new ImagePackRect();
  103. sortedRects[i].rect = rects[i];
  104. sortedRects[i].index = i;
  105. }
  106. var initialWidth = (int)NextPowerOfTwo((ulong)rects[0].width);
  107. var initialHeight = (int)NextPowerOfTwo((ulong)rects[0].height);
  108. if (requireSquarePOT)
  109. initialWidth = initialHeight = (initialWidth > initialHeight) ? initialWidth : initialHeight;
  110. Array.Sort<ImagePackRect>(sortedRects);
  111. var root = new ImagePackNode();
  112. root.rect = new RectInt(0, 0, initialWidth, initialHeight);
  113. for (var i = 0; i < rects.Length; ++i)
  114. {
  115. if (!root.Insert(sortedRects[i], padding)) // we can't fit
  116. {
  117. int newWidth = root.rect.width, newHeight = root.rect.height;
  118. if (root.rect.width < root.rect.height)
  119. {
  120. newWidth = (int)NextPowerOfTwo((ulong)root.rect.width + 1);
  121. // Every time height changes, we reset height to grow again.
  122. newHeight = initialHeight;
  123. }
  124. else
  125. newHeight = (int)NextPowerOfTwo((ulong)root.rect.height + 1);
  126. if (requireSquarePOT)
  127. newWidth = newHeight = (newWidth > newHeight) ? newWidth : newHeight;
  128. // Reset all packing and try again
  129. root = new ImagePackNode();
  130. root.rect = new RectInt(0, 0, newWidth, newHeight);
  131. i = -1;
  132. }
  133. }
  134. return root;
  135. }
  136. public static void Blit(NativeArray<Color32> buffer, RectInt[] blitToArea, int bufferBytesPerRow, NativeArray<Color32>[] originalBuffer, RectInt[] blitFromArea, int[] bytesPerRow, int padding)
  137. {
  138. UnityEngine.Profiling.Profiler.BeginSample("Blit");
  139. for (var bufferIndex = 0; bufferIndex < blitToArea.Length && bufferIndex < originalBuffer.Length && bufferIndex < blitFromArea.Length; ++bufferIndex)
  140. {
  141. var fromArea = new int4(blitFromArea[bufferIndex].x, blitFromArea[bufferIndex].y, blitFromArea[bufferIndex].width, blitFromArea[bufferIndex].height);
  142. var toArea = new int4(blitToArea[bufferIndex].x, blitToArea[bufferIndex].y, blitToArea[bufferIndex].width, blitToArea[bufferIndex].height);
  143. unsafe
  144. {
  145. var originalBufferPtr = (Color32*)originalBuffer[bufferIndex].GetUnsafeReadOnlyPtr();
  146. var outputBufferPtr = (Color32*)buffer.GetUnsafePtr();
  147. BurstedBlit(originalBufferPtr, in fromArea, in toArea, bytesPerRow[bufferIndex], bufferBytesPerRow, outputBufferPtr);
  148. }
  149. }
  150. #if PACKING_DEBUG
  151. var emptyColors = new Color32[]
  152. {
  153. new Color32((byte)255, (byte)0, (byte)0, (byte)255),
  154. new Color32((byte)255, (byte)255, (byte)0, (byte)255),
  155. new Color32((byte)255, (byte)0, (byte)255, (byte)255),
  156. new Color32((byte)255, (byte)255, (byte)255, (byte)255),
  157. new Color32((byte)0, (byte)255, (byte)0, (byte)255),
  158. new Color32((byte)0, (byte)0, (byte)255, (byte)255)
  159. };
  160. for (int k = originalBuffer.Length; k < blitToArea.Length; ++k)
  161. {
  162. var rectFrom = blitToArea[k];
  163. for (int i = 0; i < rectFrom.height; ++i)
  164. {
  165. for (int j = 0; j < rectFrom.width; ++j)
  166. {
  167. c[((rectFrom.y + i) * bufferbytesPerRow) + rectFrom.x + j] =
  168. emptyColors[k % emptyColors.Length];
  169. }
  170. }
  171. }
  172. #endif
  173. UnityEngine.Profiling.Profiler.EndSample();
  174. }
  175. [BurstCompile]
  176. static unsafe void BurstedBlit(Color32* originalBuffer, in int4 rectFrom, in int4 rectTo, int bytesPerRow, int bufferBytesPerRow, Color32* outputBuffer)
  177. {
  178. var c = outputBuffer;
  179. var b = originalBuffer;
  180. var toXStart = (int)(rectTo.z * 0.5f - rectFrom.z * 0.5f);
  181. var toYStart = (int)(rectTo.w * 0.5f - rectFrom.w * 0.5f);
  182. toXStart = toXStart <= 0 ? rectTo.x : toXStart + rectTo.x;
  183. toYStart = toYStart <= 0 ? rectTo.y : toYStart + rectTo.y;
  184. for (var i = 0; i < rectFrom.w && i < rectTo.w; ++i)
  185. {
  186. for (var j = 0; j < rectFrom.z && j < rectTo.z; ++j)
  187. {
  188. var cc = b[(rectFrom.y + i) * bytesPerRow + rectFrom.x + j];
  189. c[((toYStart + i) * bufferBytesPerRow) + toXStart + j] = cc;
  190. }
  191. }
  192. }
  193. internal static ulong NextPowerOfTwo(ulong v)
  194. {
  195. v -= 1;
  196. v |= v >> 16;
  197. v |= v >> 8;
  198. v |= v >> 4;
  199. v |= v >> 2;
  200. v |= v >> 1;
  201. return v + 1;
  202. }
  203. internal class ImagePackRect : IComparable<ImagePackRect>
  204. {
  205. public RectInt rect;
  206. public int index;
  207. public int CompareTo(ImagePackRect obj)
  208. {
  209. var lhsArea = rect.width * rect.height;
  210. var rhsArea = obj.rect.width * obj.rect.height;
  211. if (lhsArea > rhsArea)
  212. return -1;
  213. if (lhsArea < rhsArea)
  214. return 1;
  215. if (index < obj.index)
  216. return -1;
  217. return 1;
  218. }
  219. }
  220. }
  221. }