Пример #1
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);
        }
Пример #2
0
        /// <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);
        }