OSDN Git Service

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