2018-12-06 18:19:30 +00:00
using System ;
using System.Collections.Generic ;
using System.Drawing ;
using System.IO ;
using System.Text ;
using System.Drawing.Drawing2D ;
using System.Drawing.Imaging ;
using SixLabors.ImageSharp ;
using SixLabors.ImageSharp.PixelFormats ;
2018-12-07 08:22:15 +00:00
namespace RecrownedAthenaeum.Tools.TextureAtlas
2018-12-06 18:19:30 +00:00
{
2018-12-07 08:27:34 +00:00
public class TexturePacker : IDisposable
2018-12-06 18:19:30 +00:00
{
private enum SupportedExtensions
{
jpeg , jpg , png
}
2018-12-07 08:20:53 +00:00
int powLimit ;
2018-12-06 18:19:30 +00:00
Rectangle theoreticalSpace ;
Node masterNode ;
2018-12-07 08:20:53 +00:00
ImageHandler [ ] imageHandlers ;
Queue < ImageHandler > imageHandlerQueue ;
int textureLength ;
2018-12-06 18:19:30 +00:00
2018-12-07 08:20:53 +00:00
/// <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 )
2018-12-06 18:19:30 +00:00
{
2018-12-07 08:20:53 +00:00
this . powLimit = powLimit ;
2018-12-06 18:19:30 +00:00
string [ ] paths = Directory . GetFiles ( rootDirectoryPath ) ;
2018-12-07 08:20:53 +00:00
int currentPoT = startingPower ;
List < ImageHandler > imageHandlers = new List < ImageHandler > ( ) ;
2018-12-06 18:19:30 +00:00
for ( int pathID = 0 ; pathID < paths . Length ; pathID + + )
{
SupportedExtensions extension ;
if ( Enum . TryParse ( Path . GetExtension ( paths [ pathID ] ) , out extension ) )
{
2018-12-07 08:20:53 +00:00
ImageHandler image = new ImageHandler ( paths [ pathID ] ) ;
imageHandlers . Add ( image ) ;
}
}
imageHandlers . Sort ( ) ;
this . imageHandlers = imageHandlers . ToArray ( ) ;
imageHandlerQueue = new Queue < ImageHandler > ( imageHandlers ) ;
textureLength = currentPoT ;
}
2018-12-06 18:19:30 +00:00
2018-12-07 08:20:53 +00:00
/// <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 ) )
{
2018-12-07 17:20:46 +00:00
Node activeNode = null ;
do
2018-12-07 08:20:53 +00:00
{
2018-12-07 17:20:46 +00:00
activeNode = masterNode . InsertImageHandler ( imageHandler ) ;
if ( activeNode = = null )
2018-12-07 08:20:53 +00:00
{
2018-12-07 17:20:46 +00:00
if ( ! AutoCorrectAtlasSize | | ( textureLength * = 2 ) > 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 + "." ) ;
}
2018-12-06 18:19:30 +00:00
}
}
2018-12-07 17:20:46 +00:00
while ( activeNode = = null ) ;
2018-12-06 18:19:30 +00:00
}
2018-12-07 08:20:53 +00:00
}
/// <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 ( )
{
2018-12-06 18:19:30 +00:00
}
2018-12-07 08:20:53 +00:00
public void Dispose ( )
{
foreach ( ImageHandler imageHandler in imageHandlers )
{
imageHandler . Dispose ( ) ;
}
}
2018-12-06 18:19:30 +00:00
private class Node
{
public Node parent ;
2018-12-07 08:20:53 +00:00
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 ;
2018-12-07 17:20:46 +00:00
public bool containsImage = false ;
public bool Filled { get { return containsImage | | ( a ! = null & & b ! = null & & a . Filled & & b . Filled ) ; } }
2018-12-07 08:20:53 +00:00
public Node ( Node parent = null )
{
this . parent = parent ;
region = parent . region ;
}
2018-12-06 18:19:30 +00:00
2018-12-07 17:20:46 +00:00
/// <summary>
/// Attempts to insert image within the node. This builds the node to have children if needed.
/// </summary>
/// <param name="imageHandler">the image to insert.</param>
/// <returns>The node the image is placed in.</returns>
2018-12-07 08:20:53 +00:00
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 ;
2018-12-07 17:20:46 +00:00
containsImage = true ;
2018-12-07 08:20:53 +00:00
return this ;
}
2018-12-07 17:20:46 +00:00
return null ;
2018-12-06 18:19:30 +00:00
}
}
2018-12-07 08:20:53 +00:00
private class ImageHandler : IComparable < ImageHandler > , IDisposable
2018-12-06 18:19:30 +00:00
{
public readonly string path ;
2018-12-07 08:20:53 +00:00
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 ;
2018-12-06 18:19:30 +00:00
2018-12-07 08:20:53 +00:00
internal ImageHandler ( String path )
2018-12-06 18:19:30 +00:00
{
this . path = path ;
2018-12-07 08:20:53 +00:00
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 ( ) ;
2018-12-06 18:19:30 +00:00
}
}
}
}