using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using Hjg.Pngcs; using Hjg.Pngcs.Chunks; namespace Automap { public class Snapshotter { public readonly int chunkSize; private const string customTimeFormat = @"yyyy.MM.dd.HH.mm.ssZzz"; public string fileName; public string chunkPath; public ColumnsMetadata cols; //TODO: Refactor - so Edges are set at construction time, as ColumnsMetadata is async updating in real time! public int NorthEdge => cols.North_mostChunk; public int WestEdge => cols.West_mostChunk; public int Width => (cols.East_mostChunk - WestEdge + 1); public int Height => (cols.South_mostChunk - NorthEdge + 1); public Snapshotter(string path, ColumnsMetadata cols, int chunkSize, int worldSeed) { this.fileName = Path.Combine(path, $"snapshot_{worldSeed}_{DateTime.Now.ToString(customTimeFormat)}.png"); this.chunkPath = Path.Combine(path, AutomapSystem._chunkPath); this.cols = cols; this.chunkSize = chunkSize; } /// /// takes a snapshot. this should be called from an extra thread. /// /// path to the map dir /// name of the chunks dir part thing /// /// public async void Take() { var t = new Stopwatch(); t.Start(); Console.WriteLine("snapshot started"); ImageInfo info = new ImageInfo(Width * chunkSize, Height * chunkSize, 8, false); PngWriter snapWriter = FileHelper.CreatePngWriter(fileName, info, true); PngMetadata meta = snapWriter.GetMetadata( ); meta.SetTimeNow( ); var transparencyChunk = meta.CreateTRNSChunk( ); transparencyChunk.SetRGB(0, 0, 0);//CHECK: This is pure black. meta.SetText("Northmost", NorthEdge.ToString("D")); meta.SetText("Eastmost", WestEdge.ToString("D")); meta.SetText("Width", Width.ToString("D")); meta.SetText("Height", Height.ToString("D")); /* Red: 2 bytes, range 0 .. (2^bitdepth)-1 Green: 2 bytes, range 0 .. (2^bitdepth)-1 Blue: 2 bytes, range 0 .. (2^bitdepth)-1 */ snapWriter.CompLevel = 5; snapWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Filtered; var orderedList = from ch in cols group ch by ch.Location.Y into g orderby g.Key select g; // that sorts things in ascending order so we can only create (chunkSize) lines at once int row = 0, lastPosY = 0, posY = 0; foreach (var chunkGroup in orderedList) { // oh god here we go... posY = chunkGroup.Key - NorthEdge; var delta = posY - lastPosY; lastPosY = posY; // if there is more than 1 blank step then the gap needs to be filled. for (int i = 1; i < delta; i++) { byte[] blankLine = new byte[info.BytesPerRow]; for (int j = 0; j < chunkSize; j++) snapWriter.WriteRowByte(blankLine, row++); } var inGroup = (await ReadAllInGroup(chunkGroup)).ToArray(); for (int r = 0; r < chunkSize; r++) { var line = new byte[info.BytesPerRow]; for (int chunk = 0; chunk < inGroup.Length; chunk++) { var posX = inGroup[chunk].Key * chunkSize * info.BytesPixel; inGroup[chunk].Value?[r].CopyTo(line, posX); } snapWriter.WriteRowByte(line, row++); } } snapWriter.ShouldCloseStream = true; try { snapWriter.End(); } catch (Exception) { Console.WriteLine("Snapshot exception!"); } Console.WriteLine($"snapshot finished in {t.ElapsedMilliseconds}"); } private async Task> ReadAllInGroup(IGrouping group) { var taskGroup = new Dictionary(group.Count()); foreach (var shardMeta in group) { var shardPath = Path.Combine(chunkPath, $"{shardMeta.Location.X}_{shardMeta.Location.Y}.png"); taskGroup.Add(shardMeta.Location.X - WestEdge, await ReadNoThrow(shardPath)); } return taskGroup; } private async Task ReadNoThrow(string readPath) { try { return await Task.Run(() => FileHelper.CreatePngReader(readPath).ReadRowsByte().ScanlinesB); } catch (Exception e) { Console.WriteLine(e.Message); return null; } // do nothing on error } } }