Example #1
0
        internal static bool packSprites(string sheetFile, string mapFile = "")
        {
            bool result = true;
            // generate our output
            ImagePacker imagePacker = new ImagePacker();
            Bitmap      outputSheet;
            Dictionary <string, Rectangle> outputMap;

            try
            {
                // pack the image, generating a map only if desired
                if (imagePacker.PackImage(images, false, true, 1024, 1024, 3, mapFile != "", out outputSheet, out outputMap) == 0)
                {
                    outputSheet.Save(sheetFile);
                    if (mapFile != "")
                    {
                        saveMap(mapFile, outputMap);
                    }
                    Console.WriteLine("Generated Sprite Sheet: {0}.", sheetFile);
                }
                else
                {
                    Console.WriteLine("There was an error making the image sheet {0}.", sheetFile);
                    result = false;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Error saving sheet: {0} {1}", sheetFile, e.Message);
                result = false;
            }
            return(result);
        }
Example #2
0
        public override AtlasContent Process(AtlasDeclaration input, ContentProcessorContext context)
        {
            Dictionary <int, Bitmap> images     = new Dictionary <int, Bitmap>();
            Dictionary <int, string> imageNames = new Dictionary <int, string>();


            ImagePacker imagePacker = new ImagePacker();
            var         imgFiles    = input.Images.Select(i => Path.Combine(input.AtlasRootDir, i.Replace('/', '\\')));

            if (imgFiles.Count() == 0)
            {
                throw new ArgumentException("No Image found");
            }
            Bitmap output;
            Dictionary <string, Sprite> map;

            imagePacker.PackImage(imgFiles, true, true, 4096, 4096, 0, true, out output, out map);


            var finalSprites = map.Select(s => { s.Value.Name = s.Key.Substring(0, s.Key.LastIndexOf('.')).Substring(input.AtlasRootDir.Length + 1).Replace('\\', '/').Trim('.', '/'); return(s.Value); }).ToArray();
            var atlasPngPath = Path.Combine(input.AtlasRootDir, input.Name + ".png");

            using (FileStream outputSpriteFile = new FileStream(atlasPngPath, FileMode.Create))
            {
                output.Save(outputSpriteFile, ImageFormat.Png);
            }
            context.AddOutputFile(atlasPngPath);
            ExternalReference <TextureContent> texture = new ExternalReference <TextureContent>(atlasPngPath);

            texture = BuildTexture($"{input.Name}Texture", texture, context);

            return(new AtlasContent {
                Texture = texture, Sprites = finalSprites
            });
        }
Example #3
0
    public static int Pack(string atlasDir, string imageOutputDir, string codeOutputDir, Func <string, string> namingFunc)
    {
        string imgFile  = Path.Combine(imageOutputDir, "Atlas.png");
        string codeFile = Path.Combine(codeOutputDir, "Atlas.cs");

        // find all images
        List <string> images = new List <string>();

        foreach (var file in Directory.GetFiles(atlasDir, "*.*", SearchOption.AllDirectories))
        {
            FileInfo info = new FileInfo(file);
            if (info.Extension == ".png")
            {
                images.Add(info.FullName);
            }
        }

        // PACKIT!
        var    imagePacker = new ImagePacker();
        Bitmap outputImage;
        Dictionary <string, Rectangle> outputMap;
        int result = imagePacker.PackImage(images, true, true, MAXIMGSIZE, MAXIMGSIZE, 1, true, out outputImage, out outputMap);

        if (result != 0)
        {
            Dbg.Write("There was an error making the image sheet.");
            return(result);
        }

        if (File.Exists(imgFile))
        {
            File.Delete(imgFile);
        }
        IImageExporter imageExporter = new PngImageExporter();

        imageExporter.Save(imgFile, outputImage);

        if (File.Exists(codeFile))
        {
            File.Delete(codeFile);
        }
        CsGenExporter mapExporter = new CsGenExporter();

        mapExporter.namerFunc = namingFunc;
        mapExporter.atlasSize = outputImage.Width;
        mapExporter.Save(codeFile, outputMap);

        return(200);
    }
Example #4
0
        public void PackingWorks()
        {
            var files = GetSpriteFiles();

            Assert.NotEmpty(files);

            var packer = new ImagePacker();

            packer.PackImage(files, true, true, 4096, 4096, 2, out var image, out var map);

            Assert.NotNull(image);
            Assert.NotNull(map);

            var outFile = new FileInfo("TestOutput.png");

            using (var stream = outFile.Open(FileMode.Create))
            {
                image.Save(stream, new PngEncoder());
            }
        }
Example #5
0
        void PackOnce()
        {
            if (mPackTasks.Count == 0)
            {
                return;
            }
            // generate our output
            ImagePacker imagePacker = new ImagePacker();
            Bitmap      outputImage;
            Dictionary <string, System.Drawing.Rectangle> outputMap;
            PackTask pt = mPackTasks.Dequeue();

            Console.WriteLine("Packing {0} ({1} left to pack)", pt.outputFile, mPackTasks.Count);

            // pack the image, generating a map only if desired
            int result = imagePacker.PackImage(pt.inputFiles, REQUIRE_POW2, REQUIRE_SQUARE, 4096, 4096, PADDING, true, out outputImage, out outputMap);

            if (result != 0)
            {
                Console.WriteLine("There was an error making the image sheet.");
                return;
            }

            // try to save using our exporters
            try
            {
                if (File.Exists(pt.outputFile))
                {
                    File.Delete(pt.outputFile);
                }
                mImageExporter.Save(pt.outputFile, outputImage);
                Console.WriteLine("Saved atlas {0}.", pt.outputFile);
            }
            catch (Exception e)
            {
                Console.WriteLine("Error saving file: " + e.Message);
                return;
            }

            if (mMapExporter != null)
            {
                try
                {
                    if (File.Exists(pt.outputMap))
                    {
                        File.Delete(pt.outputMap);
                    }
                    mMapExporter.Save(pt.outputMap, outputMap);
                    Console.WriteLine("Saved atlas map {0}.", pt.outputMap);
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error saving file: " + e.Message);
                    return;
                }
            }
            foreach (string filename in pt.inputFiles)
            {
                if (File.Exists(filename))
                {
                    try
                    {
                        File.Delete(filename);
                    }
                    catch (IOException)
                    {
                        // Welp
                    }
                }
            }
        }
Example #6
0
        /// <summary>
        /// Create the atlas image and map xml
        /// </summary>
        /// <returns>Success code if complete, any other FailCode otherwise</returns>
        public FailCode CreateAtlas()
        {
            //input checks
            if (Atlas == null)
            {
                throw new BadMemeException("you forgot to set the atlas object. nice.");
            }
            totalMillisecondsToCreateImage = 0;
            stopwatch.Restart();

            //configure names and paths
            //set the name of the mapfile based on the filename of the atlas image, if not set from xml load
            Logging.Info("[atlas file {0}]: Preparing to create atlas", Atlas.AtlasFile);
            if (string.IsNullOrEmpty(Atlas.MapFile))
            {
                Atlas.MapFile = string.Format("{0}.xml", Path.GetFileNameWithoutExtension(Atlas.AtlasFile));
            }
            //set the paths of the created image and map file
            Atlas.AtlasImageFilePath = Path.Combine(Atlas.AtlasSaveDirectory, Atlas.AtlasFile);
            Atlas.AtlasMapFilePath   = Path.Combine(Atlas.AtlasSaveDirectory, Atlas.MapFile);
            //set location to extract original WG atlas files. If not custom set, then set them to the RelhaxTempfolder location
            if (string.IsNullOrEmpty(Atlas.TempAtlasImageFilePath))
            {
                Atlas.TempAtlasImageFilePath = Path.Combine(Settings.RelhaxTempFolderPath, Atlas.AtlasFile);
            }
            if (string.IsNullOrEmpty(Atlas.TempAtlasMapFilePath))
            {
                Atlas.TempAtlasMapFilePath = Path.Combine(Settings.RelhaxTempFolderPath, Atlas.MapFile);
            }

            //prepare the temp and output directories (lock to prevent multiple threads creating folders. Could get messy.
            lock (AtlasUtils.AtlasLoaderLockObject)
            {
                //create temp directory if it does not already exist
                if (!Directory.Exists(Path.GetDirectoryName(Atlas.TempAtlasImageFilePath)))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(Atlas.TempAtlasImageFilePath));
                }

                //create the save directory if it does not already exist
                if (!Directory.Exists(Atlas.AtlasSaveDirectory))
                {
                    Directory.CreateDirectory(Atlas.AtlasSaveDirectory);
                }
            }
            //delete the temp files if they exist
            if (File.Exists(Atlas.TempAtlasImageFilePath))
            {
                File.Delete(Atlas.TempAtlasImageFilePath);
            }
            if (File.Exists(Atlas.TempAtlasMapFilePath))
            {
                File.Delete(Atlas.TempAtlasMapFilePath);
            }
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Preparing to create atlas completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //extract the map and atlas files
            Logging.Info("[atlas file {0}]: Unpack of atlas and map starting", Atlas.AtlasFile);
            Logging.Debug("[atlas file {0}]: Atlas file unpack: pkg={1}, sourcePath={2}, dest={3}",
                          Path.GetFileName(Atlas.AtlasFile), Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.AtlasFile), Atlas.TempAtlasImageFilePath);
            lock (AtlasUtils.AtlasLoaderLockObject)
            {
                FileUtils.Unpack(Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.AtlasFile), Atlas.TempAtlasImageFilePath);
            }
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();

            Logging.Debug("[atlas file {0}]: Map file unpack: pkg={1}, sourcePath={2}, dest={3}",
                          Path.GetFileName(Atlas.AtlasFile), Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.MapFile), Atlas.TempAtlasMapFilePath);
            //because of the potential to use the same package for multiple threads, it's safer to do one at a time
            lock (AtlasUtils.AtlasLoaderLockObject)
            {
                FileUtils.Unpack(Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.MapFile), Atlas.TempAtlasMapFilePath);
            }
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Unpack completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //parse the xml map file into the list of sub-textures
            Logging.Info("[atlas file {0}]: Parsing map file", Atlas.AtlasFile);
            Logging.Debug("[atlas file {0}]: Using map file path: {1}", Atlas.AtlasFile, Atlas.TempAtlasMapFilePath);
            Atlas.TextureList = mapHandler.LoadMapFile(Atlas.TempAtlasMapFilePath);
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Parsing map completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //using the parsed size and location definitions from above, copy each individual sub-texture to the texture list
            Logging.Info("[atlas file {0}]: Parsing atlas to bitmap data", Atlas.AtlasFile);
            Logging.Debug("[atlas file {0}]: Using atlas file {1}", Atlas.AtlasFile, Atlas.TempAtlasImageFilePath);
            //the native library can only be used once at a time
            lock (AtlasUtils.AtlasLoaderLockObject)
            {
                atlasImage = imageHandler.LoadDDS(Atlas.TempAtlasImageFilePath);
            }
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Parsing atlas completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Stop();

            //if the max width and height weren't given, then use 1.2x width and height of the original
            Size originalAtlasSize = new Size();

            originalAtlasSize = atlasImage.Size;
            if ((Atlas.AtlasHeight < 1) || (Atlas.AtlasWidth < 1))
            {
                Logging.Debug("Atlas width and/or height were not provided, using a 1.2x multiplier instead");
                Atlas.AtlasHeight = (int)(originalAtlasSize.Height * 1.2);
                Atlas.AtlasWidth  = (int)(originalAtlasSize.Width * 1.2);
            }
            else if ((originalAtlasSize.Height * originalAtlasSize.Width) > (Atlas.AtlasWidth * Atlas.AtlasHeight))
            {
                Logging.Warning("[atlas file {0}]: Max possible size is smaller then original size", Atlas.AtlasFile);
                Logging.Warning("Original h x w:     {1} x {2}", originalAtlasSize.Height, originalAtlasSize.Width);
                Logging.Warning("Max possible h x w: {3} x {4}", Atlas.AtlasHeight, Atlas.AtlasWidth);
                Logging.Warning("Using a 1.2x multiplier instead");
                Atlas.AtlasHeight = (int)(originalAtlasSize.Height * 1.2);
                Atlas.AtlasWidth  = (int)(originalAtlasSize.Width * 1.2);
            }
            else
            {
                Logging.Debug("[atlas file {0}]: Max possible size of new atlas file-> {1} (h) x {2} (w)", Atlas.AtlasFile, Atlas.AtlasHeight, Atlas.AtlasWidth);
            }

            //copy the sub-texture bitmap data to each texture bitmap data
            stopwatch.Start();
            Logging.Info("[atlas file {0}]: Parsing bitmap data", Atlas.AtlasFile);
            //get the overall size of the bitmap
            Rectangle rect = new Rectangle(0, 0, atlasImage.Width, atlasImage.Height);

            foreach (Texture texture in Atlas.TextureList)
            {
                Rectangle textureRect = new Rectangle(texture.X, texture.Y, texture.Width, texture.Height);
                //copy the texture bitmap data from the atlas bitmap into the texture bitmap
                //https://docs.microsoft.com/en-us/dotnet/api/system.drawing.bitmap.clone?redirectedfrom=MSDN&view=netframework-4.8#System_Drawing_Bitmap_Clone_System_Drawing_Rectangle_System_Drawing_Imaging_PixelFormat
                texture.AtlasImage = atlasImage.Clone(textureRect, atlasImage.PixelFormat);
                //do a quick lock on the bits to ensure that the image data is deep copied. Clone() seems to only shallow copy
                //https://stackoverflow.com/a/13935966/3128017
                BitmapData data = texture.AtlasImage.LockBits(new Rectangle(0, 0, texture.AtlasImage.Width, texture.AtlasImage.Height), ImageLockMode.ReadOnly, texture.AtlasImage.PixelFormat);
                texture.AtlasImage.UnlockBits(data);
            }
            //dispose of the original cause now we're done with it
            atlasImage.Dispose();
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Parsing bitmap data completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //wait for parsing of mod/custom images task here
            Logging.Info("[atlas file {0}]: Waiting for mod texture parse task", Atlas.AtlasFile);
            ParseCustomTexturesTask.Wait();
            Logging.Info("[atlas file {0}]: Mod texture parse task complete, continue execution", Atlas.AtlasFile);
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();

            //check if any custom mod contour icons were parsed. if not, then there's no need to make a new one
            if (CustomContourIconImages.Count > 0)
            {
                Logging.Info("[atlas file {0}]: {1} custom icons parsed", Atlas.AtlasFile, CustomContourIconImages.Count);
            }
            else
            {
                Logging.Warning("[atlas file {0}]: 0 custom icons parsed for atlas file {1}, no need to make a custom atlas (is this the intent?)", CustomContourIconImages.Count, Atlas.AtlasFile);
                return(FailCode.None);
            }
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //replace the original atlas textures with the mod ones
            Logging.Info("[atlas file {0}]: Replacing stock WG images with custom/mod images", Atlas.AtlasFile);
            for (int i = 0; i < Atlas.TextureList.Count; i++)
            {
                Token.ThrowIfCancellationRequested();
                Texture tex = Atlas.TextureList[i];

                //get the matching texture, if it exists
                Texture[] originalResults = CustomContourIconImages.Where(texturee => texturee.Name.Equals(Atlas.TextureList[i].Name)).ToArray();
                if (originalResults.Count() == 0)
                {
                    continue;
                }

                Texture textureResult = originalResults[originalResults.Count() - 1];
                //here means the count is one, replace the WG original subtexture with the mod one
                tex.AtlasImage.Dispose();
                tex.AtlasImage = null;
                tex.AtlasImage = textureResult.AtlasImage;
                tex.X          = 0;
                tex.Y          = 0;
                tex.Height     = textureResult.AtlasImage.Height;
                tex.Width      = textureResult.AtlasImage.Width;
            }
            OnAtlasProgres?.Invoke(this, null);
            Logging.Info("[atlas file {0}]: Replacing stock WG images completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //run the atlas creator program
            Logging.Info("[atlas file {0}]: Atlas image packing starting", Atlas.AtlasFile);
            FailCode result = imagePacker.PackImage(Atlas.TextureList, Atlas.PowOf2, Atlas.Square, Atlas.FastImagePacker, Atlas.AtlasWidth, Atlas.AtlasHeight,
#pragma warning disable IDE0068 // Use recommended dispose pattern
                                                    Atlas.Padding, Atlas.AtlasFile, out Bitmap outputImage, out Dictionary <string, Rectangle> outputMap);

#pragma warning restore IDE0068 // Use recommended dispose pattern
            OnAtlasProgres?.Invoke(this, null);
            if (result != 0)
            {
                Logging.Error("[atlas file {0}]: There was an error making the image sheet", Atlas.AtlasFile);
                return(result);
            }
            else
            {
                Logging.Info("[atlas file {0}]: Success packing into {1} x {2} pixel", Atlas.AtlasFile, outputImage.Height, outputImage.Width);
            }
            OnAtlasProgres?.Invoke(this, null);
            Token.ThrowIfCancellationRequested();
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Atlas image packing completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //save it to the class for disposal
            outputAtlasImage = outputImage;

            //check if we're on a 32bit process. if we are and the atlas size is above the 2GB (estimated) limit, then return an error code.
            //honestly why are you on a 32bit system to begin with. it's 2020. like come on.
            if (!Environment.Is64BitProcess)
            {
                Logging.Warning("This is a 32bit process, need to check if the atlas file is too large to process");
                int outputImageArea = outputImage.Width * outputImage.Height;
                if (outputImageArea > MAX_ATLAS_SIZE_32BIT)
                {
                    Logging.Error("The output image is dimensions: W={0}, H={1}, Area={2}. Maximum area for processing on a 32bit system is {3} (W={4}, H={5}).",
                                  outputImage.Width, outputImage.Height, outputImageArea, MAX_ATLAS_SIZE_32BIT, 8000, 8000);
                    return(FailCode.OutOfMemory32bit);
                }
            }
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //export the atlas image file
            Logging.Info("[atlas file {0}]: Atlas image creation starting", Atlas.AtlasFile);
            if (File.Exists(Atlas.AtlasImageFilePath))
            {
                Logging.Info("[atlas file {0}]: File already exists before write, deleting", Atlas.AtlasFile);
                File.Delete(Atlas.AtlasImageFilePath);
            }
            if (!imageHandler.SaveDDS(Atlas.AtlasImageFilePath, outputImage))
            {
                Logging.Error("[atlas file {0}]: Failed to create atlas image: {1}", Atlas.AtlasFile, Atlas.AtlasFile);
                return(FailCode.ImageExporter);
            }
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Atlas image creation completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Restart();

            //export the atlas map file
            Logging.Info("[atlas file {0}]: Atlas map creation starting", Atlas.AtlasFile);
            if (File.Exists(Atlas.AtlasMapFilePath))
            {
                File.Delete(Atlas.AtlasMapFilePath);
            }
            mapHandler.SaveMapfile(Atlas.AtlasMapFilePath, outputMap);
            stopwatch.Stop();
            Logging.Info("[atlas file {0}]: Atlas map creation completed in {1} msec", Atlas.AtlasFile, stopwatch.ElapsedMilliseconds);
            totalMillisecondsToCreateImage += stopwatch.ElapsedMilliseconds;
            stopwatch.Stop();

            //done
            Logging.Info("[atlas file {0}]: Creating atlas process completed in {1} msec", Atlas.AtlasFile, totalMillisecondsToCreateImage);

            return(FailCode.None);
        }
Example #7
0
        public static int Main(string[] args)
        {
            var app             = new CommandLineApplication(false);
            var folderOption    = app.Option("-f | --folder", "Specifies input folder", CommandOptionType.SingleValue);
            var outputOption    = app.Option("-o | --output", "Specifies the output image's file name", CommandOptionType.SingleValue);
            var mapOption       = app.Option("-m | --map", "Specifies the map's file name", CommandOptionType.SingleValue);
            var powTwoOption    = app.Option("-2 | --pow2", "Forces that the output to have power of two dimensions", CommandOptionType.NoValue);
            var squareOption    = app.Option("-s | --square", "Forces that the output to be have equal width and length", CommandOptionType.NoValue);
            var maxWidthOption  = app.Option("-w | --maxwidth", "Specifies the maximum allowed output width", CommandOptionType.SingleValue);
            var maxHeightOption = app.Option("-h | --maxwidth", "Specifies the maximum allowed output height", CommandOptionType.SingleValue);
            var paddingOption   = app.Option("-p | --padding", "Specifies the padding in pixel between packed subimages", CommandOptionType.SingleValue);

            app.HelpOption("-? | -h | --help");
            app.OnExecute(() =>
            {
                if (!folderOption.HasValue() || !outputOption.HasValue())
                {
                    Console.WriteLine("An input folder and an output filename are required");
                    return(1);
                }

                var inputDir   = new DirectoryInfo(folderOption.Value());
                var inputFiles = inputDir.GetFiles().Where(d => ImagePacker.SupportedImageExtensions.Contains(d.Extension.ToLower())).ToArray();
                if (!inputFiles.Any())
                {
                    Console.WriteLine("No supported files found");
                    return(1);
                }

                var outFile    = new FileInfo(outputOption.Value());
                var outEncoder = ImagePacker.GetEncoderFromExtension(outFile.Extension);
                if (outEncoder == null)
                {
                    Console.WriteLine("Unsupported output file format");
                    return(1);
                }

                var outMap = mapOption.HasValue() ? new FileInfo(mapOption.Value()) : null;
                IMapGenerator outGenerator = null;
                if (outMap != null)
                {
                    outGenerator = MapGenerators.FirstOrDefault(d => d.MapExtension == outMap.Extension.ToLower());
                    if (outGenerator == null)
                    {
                        Console.WriteLine("Unsupported output map format");
                        return(1);
                    }
                }

                var valueParsed = int.TryParse(maxWidthOption.Value(), out var maxWidth);
                if (!valueParsed)
                {
                    maxWidth = DefaultSize;
                }
                valueParsed = int.TryParse(maxHeightOption.Value(), out var maxHeight);
                if (!valueParsed)
                {
                    maxHeight = DefaultSize;
                }
                valueParsed = int.TryParse(paddingOption.Value(), out var padding);
                if (!valueParsed)
                {
                    padding = DefaultPadding;
                }

                var packer = new ImagePacker();
                packer.PackImage(inputFiles, powTwoOption.HasValue(), squareOption.HasValue(), maxWidth, maxHeight, padding, out var packedImage, out var packedMap);

                using (var outStream = outFile.Open(FileMode.Create))
                {
                    packedImage.Save(outStream, outEncoder);
                }

                if (outGenerator != null)
                {
                    var mapBytes = outGenerator.Generate(packedMap);
                    using (var outStream = outMap.Open(FileMode.Create))
                    {
                        outStream.Write(mapBytes, 0, mapBytes.Length);
                    }
                }

                return(0);
            });

            return(app.Execute(args));
        }
        public void Test02_TextureTest()
        {
            Logfile log = UnitTestHelper.CreateLogfile();

            Assert.IsNotNull(log);
            Assert.IsTrue(log.CanWrite);

            //create objects
            List <Texture> texturelist     = null;
            XmlNodeList    xmlTextureList  = null;
            MapHandler     mapHandler      = new MapHandler();
            ImagePacker    imagePacker     = new ImagePacker();
            XmlDocument    textureDocument = new XmlDocument();

            //setup paths
            string testFileIn = Path.Combine(UnitTestHelper.ResourcesFolder, "battleAtlas.xml");

            Assert.IsTrue(File.Exists(testFileIn));
            string testFileOut = Path.Combine(UnitTestHelper.ResourcesFolder, "battleAtlas2.xml");

            if (File.Exists(testFileOut))
            {
                File.Delete(testFileOut);
            }

            //load sub-texture count to xml
            textureDocument.Load(testFileIn);
            xmlTextureList = textureDocument.SelectNodes("//SubTexture");
            int numSubTextures = xmlTextureList.Count;

            //load texture list
            log.Write("Asserting to load a xml texture file to Texture list");
            texturelist = mapHandler.LoadMapFile(testFileIn);
            log.Write(string.Format("Texture class load status: {0}", texturelist != null));
            Assert.IsNotNull(texturelist);

            //compare texture count
            log.Write(string.Format("Xml node textures: {0}, parsed: {1}", numSubTextures, texturelist.Count));
            Assert.AreEqual(numSubTextures, texturelist.Count);

            //for the packer, need to create bitmaps. we won't use them
            foreach (Texture tex in texturelist)
            {
                tex.AtlasImage = new Bitmap(1, 1);
            }

            //compare each individual texture
            log.Write("Asserting each texture matches from xml to texture list");
            for (int i = 0; i < texturelist.Count; i++)
            {
                Texture    texture    = texturelist[i];
                XmlElement xmlTexture = xmlTextureList[i] as XmlElement;

                //properties match lowercase xml properties names
                foreach (string propertyName in texture.PropertiesForSerializationElements())
                {
                    XmlElement element = xmlTexture.SelectSingleNode(propertyName.ToLower()) as XmlElement;
                    Assert.IsNotNull(element);
                    string xmlValue = element.InnerText.Trim();
                    //for reference
                    //PropertyInfo property = listObjectType.GetProperty(attributeName);
                    PropertyInfo property = typeof(Texture).GetProperty(propertyName);
                    Assert.IsNotNull(property);
                    object value = property.GetValue(texture);
                    Assert.IsNotNull(value);
                    Assert.AreEqual(xmlValue, value.ToString());
                }
            }

            //pack textures
            FailCode code = imagePacker.PackImage(texturelist, true, false, true, 8192, 8192, 1, "battleAtlas.dds", out Bitmap map, out Dictionary <string, Rectangle> imageSizes);

            log.Write(string.Format("Packer fail code: {0}", code.ToString()));
            Assert.AreEqual(FailCode.None, code);

            //for the packer, need to dispose bitmaps
            map.Dispose();
            foreach (Texture tex in texturelist)
            {
                tex.AtlasImage.Dispose();
            }

            //save to new xml file
            mapHandler.SaveMapfile(testFileOut, imageSizes);
            Assert.IsTrue(File.Exists(testFileOut));

            //compare texture count
            textureDocument = new XmlDocument();
            textureDocument.Load(testFileOut);
            xmlTextureList = textureDocument.SelectNodes("//SubTexture");
            numSubTextures = xmlTextureList.Count;
            log.Write(string.Format("Xml node textures: {0}, parsed: {1}", numSubTextures, imageSizes.Count));
            Assert.AreEqual(numSubTextures, imageSizes.Count);

            //compare each individual texture
            log.Write("Asserting each texture matches from xml to dictionary");
            for (int i = 0; i < xmlTextureList.Count; i++)
            {
                XmlElement xmlTexture = xmlTextureList[i] as XmlElement;
                Assert.IsNotNull(xmlTexture);
                string    textureName = xmlTexture.SelectSingleNode(nameof(Texture.Name).ToLower()).InnerText.Trim();
                Rectangle imageSize   = imageSizes[textureName];
                Assert.IsNotNull(imageSize);

                string[] propertiesToCheck = { nameof(imageSize.X), nameof(imageSize.Y), nameof(imageSize.Width), nameof(imageSize.Height) };
                foreach (string propertyName in propertiesToCheck)
                {
                    XmlElement xmlProperty = xmlTexture.SelectSingleNode(propertyName.ToLower()) as XmlElement;
                    Assert.IsNotNull(xmlProperty);

                    PropertyInfo property = imageSize.GetType().GetProperty(propertyName);
                    Assert.IsNotNull(property);
                    object propertyValue = property.GetValue(imageSize);
                    Assert.IsNotNull(propertyValue);
                    Assert.IsTrue(propertyValue is int);

                    Assert.AreEqual(xmlProperty.InnerText.Trim(), propertyValue.ToString());
                }
            }

            File.Delete(testFileOut);

            UnitTestHelper.DestroyLogfile(ref log, false);
            Assert.IsNull(log);
        }