From 22b327e72be80ba0125588ce78d4e7df54d63c8d Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Fri, 7 Dec 2018 02:20:53 -0600 Subject: [PATCH] finished untested packing algorithm for texture atlas as well as added some documentation. --- .../TextureAtlasTools/TexturePacker.cs | 195 +++++++++++++++--- 1 file changed, 166 insertions(+), 29 deletions(-) diff --git a/RecrownedAthenaeum.ConsoleTools/TextureAtlasTools/TexturePacker.cs b/RecrownedAthenaeum.ConsoleTools/TextureAtlasTools/TexturePacker.cs index 1041cfb..a494f85 100644 --- a/RecrownedAthenaeum.ConsoleTools/TextureAtlasTools/TexturePacker.cs +++ b/RecrownedAthenaeum.ConsoleTools/TextureAtlasTools/TexturePacker.cs @@ -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 imageHandlerQueue; + int textureLength; - internal TexturePacker(string rootDirectoryPath) + /// + /// Machine to pack multiple textures into one large texture. + /// + /// Path to textures. + /// Power of two limit for auto expanding texture. Default is 12 which is a 4096x4096 texture. + /// What power to start at and build up from. Default is 8 which is a 256x256 texture. + 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 theoreticalImages = new List(); + int currentPoT = startingPower; + List imageHandlers = new List(); 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(imageHandlers); + textureLength = currentPoT; + } + /// + /// Builds a texture atlas. + /// + /// Whether or not to automatically upscale atlas' texture in the case it is too small. Goes up to 4096 by default. + 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(); + } + } + } + + /// + /// Renders the build into a PNG file and generates the respective meant for serialization and later to be loaded. + /// + 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, IDisposable { - public Rectangle bounds; public readonly string path; - public int Area { get { return bounds.Width * bounds.Height; } } + public readonly Image 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(); } } }