/// <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); }
/// <summary> /// Create the atlas image /// </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."); } //create the save directory if it does not already exist if (!Directory.Exists(Atlas.AtlasSaveDirectory)) { Directory.CreateDirectory(Atlas.AtlasSaveDirectory); } //set the mapfile name Atlas.MapFile = string.Format("{0}.xml", Path.GetFileNameWithoutExtension(Atlas.AtlasFile)); Logging.Debug("[atlas file {0}]: set map name as {1}", Path.GetFileName(Atlas.AtlasFile), Path.GetFileName(Atlas.MapFile)); //set location to extract original WG atlas files tempAtlasImageFile = Path.Combine(Settings.RelhaxTempFolderPath, Atlas.AtlasFile); tempAtlasMapFile = Path.Combine(Settings.RelhaxTempFolderPath, Atlas.MapFile); //delete the temp files if they exist if (File.Exists(tempAtlasImageFile)) { File.Delete(tempAtlasImageFile); } if (File.Exists(tempAtlasMapFile)) { File.Delete(tempAtlasMapFile); } //extract the map and atlas files Logging.Info("[atlas file {0}]: unpack of atlas and map starting", Path.GetFileName(Atlas.AtlasFile)); stopwatch.Restart(); Logging.Debug("[atlas file {0}]: atlas file unpack", Path.GetFileName(Atlas.AtlasFile)); lock (AtlasUtils.AtlasLoaderLockObject) { Utils.Unpack(Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.AtlasFile), tempAtlasImageFile); } OnAtlasProgres?.Invoke(this, null); Token.ThrowIfCancellationRequested(); Logging.Debug("[atlas file {0}]: map file unpack", Path.GetFileName(Atlas.AtlasFile)); lock (AtlasUtils.AtlasLoaderLockObject) { Utils.Unpack(Atlas.Pkg, Path.Combine(Atlas.DirectoryInArchive, Atlas.MapFile), tempAtlasMapFile); } OnAtlasProgres?.Invoke(this, null); Token.ThrowIfCancellationRequested(); stopwatch.Stop(); Logging.Info("[atlas file {0}]: unpack completed in {1} msec", Path.GetFileName(Atlas.AtlasFile), stopwatch.ElapsedMilliseconds); stopwatch.Restart(); //parse the xml map file into the list of subtextures Logging.Info("[atlas file {0}]: parsing map file", Path.GetFileName(Atlas.AtlasFile)); Logging.Debug("[atlas file {0}]: using map file {1}", Path.GetFileName(Atlas.AtlasFile), tempAtlasMapFile); Atlas.TextureList = LoadMapFile(tempAtlasMapFile); OnAtlasProgres?.Invoke(this, null); //using the parsed size and location definitions from above, copy each individual subtexture to the texture list Size originalAtlasSize = new Size(); Logging.Info("[atlas file {0}]: loading atlas to bitmap data", Path.GetFileName(Atlas.AtlasFile)); Logging.Debug("[atlas file {0}]: using atlas file {1}", Path.GetFileName(Atlas.AtlasFile), tempAtlasImageFile); stopwatch.Restart(); lock (AtlasUtils.AtlasLoaderLockObject) { //the native library can only be used once at a time atlasImage = LoadDDS(tempAtlasImageFile); } OnAtlasProgres?.Invoke(this, null); Token.ThrowIfCancellationRequested(); //get the size from grumpel code originalAtlasSize = atlasImage.Size; //copy the subtexture bitmap data to each texture bitmap data Logging.Info("[atlas file {0}]: parsing bitmap data", Path.GetFileName(Atlas.AtlasFile)); lock (AtlasUtils.AtlasLoaderLockObject) { //lock the atlas image into memory Rectangle rect = new Rectangle(0, 0, atlasImage.Width, atlasImage.Height); BitmapData atlasLock = atlasImage.LockBits(rect, ImageLockMode.ReadOnly, atlasImage.PixelFormat); foreach (Texture texture in Atlas.TextureList) { Token.ThrowIfCancellationRequested(); //copy the texture bitmap data into the texture bitmap object //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_ //rectangle of desired area to clone Rectangle textureRect = new Rectangle(texture.X, texture.Y, texture.Width, texture.Height); //copy the bitmap try { texture.AtlasImage = atlasImage.Clone(textureRect, atlasImage.PixelFormat); } catch (Exception ex) { Logging.Exception("failed to clone atlas image data"); Logging.Exception(ex.ToString()); try { atlasImage.UnlockBits(atlasLock); atlasImage.Dispose(); } catch { } return(FailCode.ImageImporter); } } atlasImage.UnlockBits(atlasLock); atlasImage.Dispose(); } OnAtlasProgres?.Invoke(this, null); stopwatch.Stop(); Logging.Info("[atlas file {0}]: parsing bitmap data completed in {1} msec", Path.GetFileName(Atlas.AtlasFile), stopwatch.ElapsedMilliseconds); //prepare atlas objects for processing Atlas.AtlasFile = Path.Combine(Atlas.AtlasSaveDirectory, Atlas.AtlasFile); Atlas.MapFile = Path.Combine(Atlas.AtlasSaveDirectory, Atlas.MapFile); // if the arguments in width and/or height of the atlases-creator-config-xml-file are 0 (or below) or not given, work with the original file dimensions to get working width and height if ((Atlas.AtlasHeight < 1) | (Atlas.AtlasWidth < 1)) { //fix atlas width and size parameters if they are wrong if (Atlas.AtlasWidth < 1) { throw new BadMemeException("grumpel..."); } if (Atlas.AtlasHeight < 1) { throw new BadMemeException("grumpel..."); } // if ((originalAtlasSize.Height * originalAtlasSize.Width) == (Atlas.AtlasWidth * Atlas.AtlasHeight)) { Atlas.AtlasHeight = (int)(Atlas.AtlasHeight * 1.5); } else { // this is to be sure that the image size that will be created, is at least the original size while ((originalAtlasSize.Height * originalAtlasSize.Width) > (Atlas.AtlasWidth * Atlas.AtlasHeight)) { Atlas.AtlasHeight = (int)(Atlas.AtlasHeight * 1.2); } } } // if ((originalAtlasSize.Height * originalAtlasSize.Width) > (Atlas.AtlasWidth * Atlas.AtlasHeight)) { Logging.Warning("[atlas file {0}]: max possible size is smaller then original size", Path.GetFileName(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); } else { Logging.Debug("[atlas file {0}]: max possible size of new atlas file-> {1} (h) x {2} (w)", Path.GetFileName(Atlas.AtlasFile), Atlas.AtlasHeight, Atlas.AtlasWidth); } //wait for task here Logging.Info("[atlas file {0}]: waiting for mod texture parse task", Path.GetFileName(Atlas.AtlasFile)); AtlasUtils.ParseModTexturesTask.Wait(); Logging.Info("[atlas file {0}]: mod texture parse task complete, continue execution", Path.GetFileName(Atlas.AtlasFile)); OnAtlasProgres?.Invoke(this, null); //check if any custom mod contour icons were parsed if (AtlasUtils.ModContourIconImages.Count > 0) { Logging.Info("[atlas file {0}]: {1} custom icons parsed", Path.GetFileName(Atlas.AtlasFile), AtlasUtils.ModContourIconImages.Count); } else { Logging.Info("[atlas file {0}]: 0 custom icons parsed for atlas file {1}, no need to make a custom atlas!", AtlasUtils.ModContourIconImages.Count, Path.GetFileName(Atlas.AtlasFile)); return(FailCode.None); } //replace the original atlas textures with the mod ones Logging.Info("[atlas file {0}]: mod images replacing starting", Path.GetFileName(Atlas.AtlasFile)); stopwatch.Restart(); for (int i = 0; i < Atlas.TextureList.Count; i++) { Token.ThrowIfCancellationRequested(); //get the matching texture, if it exists Texture[] originalResults = AtlasUtils.ModContourIconImages.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 Atlas.TextureList[i].AtlasImage.Dispose(); Atlas.TextureList[i].AtlasImage = null; Atlas.TextureList[i].AtlasImage = textureResult.AtlasImage; Atlas.TextureList[i].X = 0; Atlas.TextureList[i].Y = 0; Atlas.TextureList[i].Height = textureResult.AtlasImage.Height; Atlas.TextureList[i].Width = textureResult.AtlasImage.Width; } OnAtlasProgres?.Invoke(this, null); stopwatch.Stop(); Logging.Info("[atlas file {0}]: mod images replacing completed in {1} msec", Path.GetFileName(Atlas.AtlasFile), stopwatch.ElapsedMilliseconds); //(finally) run the atlas creator program Logging.Info("[atlas file {0}]: building starting", Path.GetFileName(Atlas.AtlasFile)); stopwatch.Restart(); // pack the image, generating a map only if desired 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, 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", Path.GetFileName(Atlas.AtlasFile)); //error result 7 = "failed to pack image" most likely it won't fit return(result); } else { Logging.Info("[atlas file {0}]: Success Packing into {1} x {2} pixel", Path.GetFileName(Atlas.AtlasFile), outputImage.Height, outputImage.Width); } //save it to the class for disposal outputAtlasImage = outputImage; //export the atlas file //delete one if it exists if (File.Exists(Atlas.AtlasFile)) { File.Delete(Atlas.AtlasFile); } //then save SaveDDS(Atlas.AtlasFile, outputImage); OnAtlasProgres?.Invoke(this, null); Logging.Info("[atlas file {0}]: successfully created Atlas image: {1}", Path.GetFileName(Atlas.AtlasFile), Atlas.AtlasFile); //export the mapfile //delete one if it exists if (File.Exists(Atlas.MapFile)) { File.Delete(Atlas.MapFile); } //then save SaveMapfile(Atlas.MapFile, outputMap); OnAtlasProgres?.Invoke(this, null); Logging.Info("[atlas file {0}]: successfully created map: {1}", Path.GetFileName(Atlas.AtlasFile), Atlas.MapFile); stopwatch.Stop(); Logging.Info("[atlas file {0}]: building completed in {1} msec", Path.GetFileName(Atlas.AtlasFile), stopwatch.ElapsedMilliseconds); return(FailCode.None); }