//#define PACKING_DEBUG
using System;
using UnityEngine;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace UnityEditor.U2D.Common
{
internal static class ImagePacker
{
///
/// Given an array of rects, the method returns an array of rects arranged within outPackedWidth and outPackedHeight
///
/// Rects to pack
/// Padding between each rect
/// Rects arranged within outPackedWidth and outPackedHeight
/// Width of the packed rects
/// Height of the packed rects
public static void Pack(RectInt[] rects, int padding, out RectInt[] outPackedRects, out int outPackedWidth, out int outPackedHeight)
{
var packNode = InternalPack(rects, padding);
outPackedWidth = packNode.rect.width;
outPackedHeight = packNode.rect.height;
var visitor = new CollectPackNodePositionVisitor();
packNode.AcceptVisitor(visitor);
outPackedRects = new RectInt[rects.Length];
for (int i = 0; i < rects.Length; ++i)
outPackedRects[i] = new RectInt(visitor.positions[i].x + padding, visitor.positions[i].y + padding, rects[i].width, rects[i].height);
#if PACKING_DEBUG
var emptyNodeCollector = new CollectEmptyNodePositionVisitor();
packNode.AcceptVisitor(emptyNodeCollector);
Array.Resize(ref outPackedRects, rects.Length + emptyNodeCollector.emptyAreas.Count);
for (int i = rects.Length; i < outPackedRects.Length; ++i)
outPackedRects[i] = emptyNodeCollector.emptyAreas[i - rects.Length];
#endif
}
///
/// Packs image buffer into a single buffer. Image buffers are assumed to be 4 bytes per pixel in RGBA format
///
/// Image buffers to pack
/// Image buffers width
/// Image buffers height
/// Padding between each packed image
/// Packed image buffer
/// Packed image buffer's width
/// Packed iamge buffer's height
/// Location of each image buffers in the packed buffer
/// Translation data from image original buffer to packed buffer
public static void Pack(NativeArray[] buffers, int width, int height, int padding, out NativeArray outPackedBuffer, out int outPackedBufferWidth, out int outPackedBufferHeight, out RectInt[] outPackedRect, out Vector2Int[] outUVTransform)
{
UnityEngine.Profiling.Profiler.BeginSample("Pack");
// Determine the area that contains data in the buffer
outPackedBuffer = default(NativeArray);
try
{
var tightRects = FindTightRectJob.Execute(buffers, width, height);
Pack(tightRects, padding, out outPackedRect, out outPackedBufferWidth, out outPackedBufferHeight);
outUVTransform = new Vector2Int[tightRects.Length];
for (int i = 0; i < outUVTransform.Length; ++i)
{
outUVTransform[i] = new Vector2Int(outPackedRect[i].x - tightRects[i].x, outPackedRect[i].y - tightRects[i].y);
}
outPackedBuffer = new NativeArray(outPackedBufferWidth * outPackedBufferHeight, Allocator.Persistent);
Blit(outPackedBuffer, outPackedRect, outPackedBufferWidth, buffers, tightRects, width, padding);
}
catch (Exception ex)
{
if (outPackedBuffer.IsCreated)
outPackedBuffer.Dispose();
throw ex;
}
finally
{
UnityEngine.Profiling.Profiler.EndSample();
}
}
static ImagePackNode InternalPack(RectInt[] rects, int padding)
{
if (rects == null || rects.Length == 0)
return new ImagePackNode() { rect = new RectInt(0, 0, 0, 0)};
var sortedRects = new ImagePackRect[rects.Length];
for (int i = 0; i < rects.Length; ++i)
{
sortedRects[i] = new ImagePackRect();
sortedRects[i].rect = rects[i];
sortedRects[i].index = i;
}
Array.Sort(sortedRects);
var root = new ImagePackNode();
root.rect = new RectInt(0, 0, (int)NextPowerOfTwo((ulong)rects[0].width), (int)NextPowerOfTwo((ulong)rects[0].height));
for (int i = 0; i < rects.Length; ++i)
{
if (!root.Insert(sortedRects[i], padding)) // we can't fit
{
int newWidth = root.rect.width , newHeight = root.rect.height;
if (root.rect.width < root.rect.height)
newWidth = (int)NextPowerOfTwo((ulong)root.rect.width + 1);
else
newHeight = (int)NextPowerOfTwo((ulong)root.rect.height + 1);
// Reset all packing and try again
root = new ImagePackNode();
root.rect = new RectInt(0, 0, newWidth, newHeight);
i = -1;
}
}
return root;
}
public static unsafe void Blit(NativeArray buffer, RectInt[] blitToArea, int bufferbytesPerRow, NativeArray[] originalBuffer, RectInt[] blitFromArea, int bytesPerRow, int padding)
{
UnityEngine.Profiling.Profiler.BeginSample("Blit");
var c = (Color32*)buffer.GetUnsafePtr();
for (int bufferIndex = 0; bufferIndex < blitToArea.Length && bufferIndex < originalBuffer.Length && bufferIndex < blitFromArea.Length; ++bufferIndex)
{
var b = (Color32*)originalBuffer[bufferIndex].GetUnsafeReadOnlyPtr();
var rectFrom = blitFromArea[bufferIndex];
var rectTo = blitToArea[bufferIndex];
for (int i = 0; i < rectFrom.height; ++i)
{
for (int j = 0; j < rectFrom.width; ++j)
{
Color32 cc = b[(rectFrom.y + i) * bytesPerRow + rectFrom.x + j];
c[((rectTo.y + i) * bufferbytesPerRow) + rectTo.x + j] = cc;
}
}
}
#if PACKING_DEBUG
var emptyColors = new Color32[]
{
new Color32((byte)255, (byte)0, (byte)0, (byte)255),
new Color32((byte)255, (byte)255, (byte)0, (byte)255),
new Color32((byte)255, (byte)0, (byte)255, (byte)255),
new Color32((byte)255, (byte)255, (byte)255, (byte)255),
new Color32((byte)0, (byte)255, (byte)0, (byte)255),
new Color32((byte)0, (byte)0, (byte)255, (byte)255)
};
for (int k = originalBuffer.Length; k < blitToArea.Length; ++k)
{
var rectFrom = blitToArea[k];
for (int i = 0; i < rectFrom.height; ++i)
{
for (int j = 0; j < rectFrom.width; ++j)
{
c[((rectFrom.y + i) * bufferbytesPerRow) + rectFrom.x + j] =
emptyColors[k % emptyColors.Length];
}
}
}
#endif
UnityEngine.Profiling.Profiler.EndSample();
}
internal static ulong NextPowerOfTwo(ulong v)
{
v -= 1;
v |= v >> 16;
v |= v >> 8;
v |= v >> 4;
v |= v >> 2;
v |= v >> 1;
return v + 1;
}
internal class ImagePackRect : IComparable
{
public RectInt rect;
public int index;
public int CompareTo(ImagePackRect obj)
{
var lhsArea = rect.width * rect.height;
var rhsArea = obj.rect.width * obj.rect.height;
if (lhsArea > rhsArea)
return -1;
if (lhsArea < rhsArea)
return 1;
if (index < obj.index)
return -1;
return 1;
}
}
}
}