OSDN Git Service

c9973336c7761c1fb5948d7611b77e90059ba678
[automap/automap.git] / Automap / Subsystems / Snapshot.cs
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.Threading;
7 using System.Threading.Tasks;
8
9 using Hjg.Pngcs;
10 using Hjg.Pngcs.Chunks;
11
12 namespace Automap
13 {
14         public class Snapshotter
15         {
16                 public readonly int chunkSize;
17                 private const string customTimeFormat = @"yyyy.MM.dd.HH.mm.ssZzz";
18                 public string fileName;
19                 public string chunkPath;
20                 public ColumnsMetadata cols;
21                 //TODO: Refactor - so Edges are set at construction time, as ColumnsMetadata is async updating in real time!
22                 public int NorthEdge => cols.North_mostChunk;
23                 public int WestEdge => cols.West_mostChunk;
24                 public int Width => (cols.East_mostChunk - WestEdge + 1);
25                 public int Height => (cols.South_mostChunk - NorthEdge + 1);
26
27
28                 public Snapshotter(string path, ColumnsMetadata cols, int chunkSize, int worldSeed)
29                 {
30                         this.fileName = Path.Combine(path, $"snapshot_{worldSeed}_{DateTime.Now.ToString(customTimeFormat)}.png");
31                         this.chunkPath = Path.Combine(path, AutomapSystem._chunkPath);
32                         this.cols = cols;
33                         this.chunkSize = chunkSize;
34                 }
35
36                 /// <summary>
37                 /// takes a snapshot. this should be called from an extra thread.
38                 /// </summary>
39                 /// <param name="path">path to the map dir</param>
40                 /// <param name="chunkPath">name of the chunks dir part thing</param>
41                 /// <param name="cols"></param>
42                 /// <param name="chunkSize"></param>
43                 public async void Take()
44                 {
45                         var t = new Stopwatch();
46                         t.Start();
47
48                         Debug.WriteLine("snapshot started");
49
50                         ImageInfo info = new ImageInfo(Width * chunkSize, Height * chunkSize, 8, false);
51                         PngWriter snapWriter = FileHelper.CreatePngWriter(fileName, info, true);
52                         PngMetadata meta = snapWriter.GetMetadata( );
53                         meta.SetTimeNow( );
54                         var transparencyChunk = meta.CreateTRNSChunk( );
55                         transparencyChunk.SetRGB(0, 0, 0);//CHECK: This is pure black.
56                         meta.SetText("Northmost", NorthEdge.ToString("D"));
57                         meta.SetText("Eastmost", WestEdge.ToString("D"));
58                         meta.SetText("Width", Width.ToString("D"));
59                         meta.SetText("Height", Height.ToString("D"));
60
61
62                 
63                         /*
64                         Red:   2 bytes, range 0 .. (2^bitdepth)-1
65                         Green: 2 bytes, range 0 .. (2^bitdepth)-1
66                         Blue:  2 bytes, range 0 .. (2^bitdepth)-1
67                         */
68
69
70                         snapWriter.CompLevel = 5;
71                         snapWriter.CompressionStrategy = Hjg.Pngcs.Zlib.EDeflateCompressStrategy.Filtered;
72
73
74
75                         var orderedList =
76                                 from ch in cols
77                                 group ch by ch.Location.Y into g
78                                 orderby g.Key
79                                 select g;
80                         // that sorts things in ascending order so we can only create (chunkSize) lines at once
81                         int row = 0, lastPosY = 0, posY = 0;
82                         foreach (var chunkGroup in orderedList)
83                         {
84                                 // oh god here we go...
85                                 posY = chunkGroup.Key - NorthEdge;
86                                 var delta = posY - lastPosY;
87                                 lastPosY = posY;
88
89                                 // if there is more than 1 blank step then the gap needs to be filled.
90                                 for (int i = 1; i < delta; i++)
91                                 {
92                                         byte[] blankLine = new byte[info.BytesPerRow];
93                                         for (int j = 0; j < chunkSize; j++)
94                                                 snapWriter.WriteRowByte(blankLine, row++);
95                                 }
96
97                                 var inGroup = (await ReadAllInGroup(chunkGroup)).ToArray();
98                                 
99                                 for (int r = 0; r < chunkSize; r++)
100                                 {
101                                         var line = new byte[info.BytesPerRow];
102                                         for (int chunk = 0; chunk < inGroup.Length; chunk++)
103                                         {
104                                                 var posX = inGroup[chunk].Key * chunkSize * info.BytesPixel;
105                                                 inGroup[chunk].Value?[r].CopyTo(line, posX);
106                                         }
107                                         snapWriter.WriteRowByte(line, row++);
108                                 }
109                         }
110                         snapWriter.ShouldCloseStream = true;
111                         try
112                         {
113                                 snapWriter.End();
114                         }
115                         catch (Exception)
116                         {
117                                 Debug.WriteLine("Snapshot exception!");
118                         }
119                         Debug.WriteLine($"snapshot finished in {t.ElapsedMilliseconds}");
120                 }
121
122                 private async Task<Dictionary<int, byte[][]>> ReadAllInGroup(IGrouping<int, ColumnMeta> group)
123                 {
124                         var taskGroup = new Dictionary<int, byte[][]>(group.Count());
125                         foreach (var shardMeta in group)
126                         {
127                                 var shardPath = Path.Combine(chunkPath, $"{shardMeta.Location.X}_{shardMeta.Location.Y}.png");
128                                 taskGroup.Add(shardMeta.Location.X - WestEdge, await ReadNoThrow(shardPath));
129                         }
130                         return taskGroup;
131                 }
132
133                 private async Task<byte[][]> ReadNoThrow(string readPath)
134                 {
135                         try
136                         {
137                                 return await Task.Run(() => FileHelper.CreatePngReader(readPath).ReadRowsByte().ScanlinesB);
138                         }
139                         catch (Exception e)
140                         {
141                                 Debug.WriteLine(e.Message);
142                                 return null;
143                         } // do nothing on error
144                 }
145         }
146 }