finished untested packing algorithm for texture atlas as well as added some documentation.
This commit is contained in:
parent
c4661b1fef
commit
22b327e72b
@ -11,69 +11,206 @@ using SixLabors.ImageSharp.PixelFormats;
|
||||
namespace RecrownedAthenaeum.ConsoleTools.TextureAtlasTools
|
||||
{
|
||||
|
||||
internal class TexturePacker
|
||||
internal class TexturePacker : IDisposable
|
||||
{
|
||||
private enum SupportedExtensions
|
||||
{
|
||||
jpeg, jpg, png
|
||||
}
|
||||
|
||||
int powLimit;
|
||||
|
||||
Rectangle theoreticalSpace;
|
||||
Node masterNode;
|
||||
ImageHandler[] imageHandlers;
|
||||
Queue<ImageHandler> imageHandlerQueue;
|
||||
int textureLength;
|
||||
|
||||
internal TexturePacker(string rootDirectoryPath)
|
||||
/// <summary>
|
||||
/// Machine to pack multiple textures into one large texture.
|
||||
/// </summary>
|
||||
/// <param name="rootDirectoryPath">Path to textures.</param>
|
||||
/// <param name="powLimit">Power of two limit for auto expanding texture. Default is 12 which is a 4096x4096 texture.</param>
|
||||
/// <param name="startingPower">What power to start at and build up from. Default is 8 which is a 256x256 texture.</param>
|
||||
internal TexturePacker(string rootDirectoryPath, int powLimit = 12, int startingPower = 8)
|
||||
{
|
||||
this.powLimit = powLimit;
|
||||
string[] paths = Directory.GetFiles(rootDirectoryPath);
|
||||
|
||||
int minArea = 0;
|
||||
int currentPoTArea = 2 * 2;
|
||||
List<TheoreticalImage> theoreticalImages = new List<TheoreticalImage>();
|
||||
int currentPoT = startingPower;
|
||||
List<ImageHandler> imageHandlers = new List<ImageHandler>();
|
||||
for (int pathID = 0; pathID < paths.Length; pathID++)
|
||||
{
|
||||
SupportedExtensions extension;
|
||||
if (Enum.TryParse(Path.GetExtension(paths[pathID]), out extension))
|
||||
{
|
||||
using (FileStream stream = new FileStream(paths[pathID], FileMode.Open))
|
||||
{
|
||||
IImageInfo info = Image.Identify(stream);
|
||||
TheoreticalImage ti = new TheoreticalImage(info.Width, info.Height, paths[pathID]);
|
||||
theoreticalImages.Add(ti);
|
||||
|
||||
minArea += ti.Area;
|
||||
while (currentPoTArea < minArea)
|
||||
{
|
||||
currentPoTArea *= 2 * 2;
|
||||
}
|
||||
}
|
||||
ImageHandler image = new ImageHandler(paths[pathID]);
|
||||
imageHandlers.Add(image);
|
||||
}
|
||||
}
|
||||
imageHandlers.Sort();
|
||||
this.imageHandlers = imageHandlers.ToArray();
|
||||
imageHandlerQueue = new Queue<ImageHandler>(imageHandlers);
|
||||
textureLength = currentPoT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a texture atlas.
|
||||
/// </summary>
|
||||
/// <param name="AutoCorrectAtlasSize">Whether or not to automatically upscale atlas' texture in the case it is too small. Goes up to 4096 by default.</param>
|
||||
public void Build(bool AutoCorrectAtlasSize = true)
|
||||
{
|
||||
masterNode = new Node();
|
||||
masterNode.region.Width = textureLength;
|
||||
masterNode.region.Height = textureLength;
|
||||
|
||||
ImageHandler imageHandler;
|
||||
while (imageHandlerQueue.TryDequeue(out imageHandler))
|
||||
{
|
||||
try
|
||||
{
|
||||
masterNode.InsertImageHandler(imageHandler);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
textureLength *= 2;
|
||||
if (!AutoCorrectAtlasSize || textureLength > Math.Pow(2, powLimit))
|
||||
{
|
||||
throw new InvalidOperationException("Dimensions of texture goes past limit amount of " + powLimit + " which is " + Math.Pow(2, powLimit) + ". New texture size would be " + textureLength + "x" + textureLength + ".");
|
||||
}
|
||||
Build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the build into a PNG file and generates the respective <see cref="TextureAtlasData"/> meant for serialization and later to be loaded.
|
||||
/// </summary>
|
||||
public void Save()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (ImageHandler imageHandler in imageHandlers)
|
||||
{
|
||||
imageHandler.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private class Node
|
||||
{
|
||||
public Node parent;
|
||||
public Node[] branches = new Node[2];
|
||||
public readonly TheoreticalImage theoreticalImage;
|
||||
private Node a, b;
|
||||
public Node childA { get { if (a == null) a = new Node(this); return a; } set { value.parent = this; a = value; } }
|
||||
public Node childB { get { if (b == null) { b = new Node(this); } return b; } set { value.parent = this; b = value; } }
|
||||
public Rectangle region;
|
||||
public ImageHandler imageHandler;
|
||||
public bool Filled { get { return (imageHandler != null) || (a != null && b != null && a.Filled && b.Filled); } }
|
||||
|
||||
public Node(TheoreticalImage theoreticalImage)
|
||||
public Node(Node parent = null)
|
||||
{
|
||||
this.theoreticalImage = theoreticalImage;
|
||||
this.parent = parent;
|
||||
region = parent.region;
|
||||
}
|
||||
|
||||
public void SetNodeRegion(int x, int y, int width, int height, ImageHandler imageHandler)
|
||||
{
|
||||
region.X = x;
|
||||
region.Y = y;
|
||||
region.Width = width;
|
||||
region.Height = height;
|
||||
this.imageHandler = imageHandler;
|
||||
}
|
||||
|
||||
public void SetNodeRegion(ImageHandler imageHandler)
|
||||
{
|
||||
SetNodeRegion(imageHandler.x, imageHandler.y, imageHandler.Width, imageHandler.Height, imageHandler);
|
||||
}
|
||||
|
||||
|
||||
public Node InsertImageHandler(ImageHandler imageHandler)
|
||||
{
|
||||
if (imageHandler.Width != region.Width)
|
||||
{
|
||||
if (imageHandler.Width < region.Width)
|
||||
{
|
||||
if (a == null)
|
||||
{
|
||||
childA.region.Width = imageHandler.Width;
|
||||
childB.region.Width = region.Width - childA.region.Width;
|
||||
}
|
||||
if (!childA.Filled && imageHandler.Width <= childA.region.Width)
|
||||
{
|
||||
return childA.InsertImageHandler(imageHandler);
|
||||
}
|
||||
|
||||
if (!childB.Filled)
|
||||
{
|
||||
return childB.InsertImageHandler(imageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (imageHandler.Height != region.Height)
|
||||
{
|
||||
if (imageHandler.Height < region.Height)
|
||||
{
|
||||
if (a == null)
|
||||
{
|
||||
childA.region.Height = imageHandler.Height;
|
||||
childB.region.Height = region.Height - childA.region.Height;
|
||||
}
|
||||
|
||||
if (!childA.Filled && imageHandler.Width <= childA.region.Width)
|
||||
{
|
||||
return childA.InsertImageHandler(imageHandler);
|
||||
}
|
||||
|
||||
if (!childB.Filled)
|
||||
{
|
||||
return childB.InsertImageHandler(imageHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
imageHandler.x = region.X;
|
||||
imageHandler.y = region.Y;
|
||||
this.imageHandler = imageHandler;
|
||||
return this;
|
||||
}
|
||||
|
||||
throw new ArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private struct TheoreticalImage
|
||||
private class ImageHandler : IComparable<ImageHandler>, IDisposable
|
||||
{
|
||||
public Rectangle bounds;
|
||||
public readonly string path;
|
||||
public int Area { get { return bounds.Width * bounds.Height; } }
|
||||
public readonly Image<Rgba32> image;
|
||||
public int Area { get { return image.Width * image.Height; } }
|
||||
public string Name { get { return Path.GetFileName(path); } }
|
||||
public int Width { get { return image.Width; } }
|
||||
public int Height { get { return image.Height; } }
|
||||
public int x, y;
|
||||
|
||||
public TheoreticalImage(int width, int height, string path)
|
||||
internal ImageHandler(String path)
|
||||
{
|
||||
bounds = new Rectangle();
|
||||
bounds.Width = width;
|
||||
bounds.Height = height;
|
||||
this.path = path;
|
||||
using (FileStream stream = new FileStream(path, FileMode.Open))
|
||||
{
|
||||
image = Image.Load(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(ImageHandler tImage)
|
||||
{
|
||||
return Area - tImage.Area;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
image.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user