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