-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPNGHeader.cs
More file actions
304 lines (289 loc) · 9.29 KB
/
PNGHeader.cs
File metadata and controls
304 lines (289 loc) · 9.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace fcfh
{
/// <summary>
/// Represents a PNG Header
/// </summary>
public class PNGHeader
{
/// <summary>
/// Chunk Flags
/// </summary>
[Flags]
public enum ChunkFlags : int
{
/// <summary>
/// No Flags
/// </summary>
None = 0,
/// <summary>
/// This header can be safely stripped
/// </summary>
/// <remarks>Removing this header will not delete any information important for rendering.</remarks>
Ancillary = 1,
/// <summary>
/// This header is vendor defined
/// </summary>
/// <remarks>Header is not part of PNG standard</remarks>
Private = 2,
/// <summary>
/// Reserved Keyword
/// </summary>
/// <remarks>This should never be set</remarks>
Reserved = 4,
/// <summary>
/// This header is safe to copy to other formats and after editing
/// </summary>
/// <remarks>If this header is set it indicates that the chunk can be left in the image even if it is edited</remarks>
SafeToCopy = 8
}
/// <summary>
/// The first 8 bytes of a PNG file
/// </summary>
public const ulong PNG_MAGIC = 0x0a1a0a0d474e5089;
/// <summary>
/// Flags according to the Chunk name
/// </summary>
public ChunkFlags Flags
{
get
{
return
(char.IsLower(HeaderName[0]) ? ChunkFlags.Ancillary : ChunkFlags.None) |
(char.IsLower(HeaderName[1]) ? ChunkFlags.Private : ChunkFlags.None) |
(char.IsLower(HeaderName[2]) ? ChunkFlags.Reserved : ChunkFlags.None) |
(char.IsLower(HeaderName[3]) ? ChunkFlags.SafeToCopy : ChunkFlags.None);
}
}
/// <summary>
/// 4-byte name of the header
/// </summary>
public string HeaderName
{
get; private set;
}
/// <summary>
/// Raw binary data
/// </summary>
public byte[] Data
{ get; private set; }
/// <summary>
/// Checksum of <see cref="HeaderName">+<see cref="Data"/>
/// </summary>
public uint Checksum
{
get
{
return Tools.IntToNetwork(CalcChecksum(Data));
}
}
/// <summary>
/// Gets if this Header has encoded data
/// </summary>
public bool IsDataHeader
{
get
{
//Data must be at least 14 bytes (BMPENC+int+int)
return Data.Length > 14 && Encoding.ASCII.GetString(Data, 0, 6) == ImageWriter.MAGIC;
}
}
/// <summary>
/// Gets the File Name from a Header
/// </summary>
public string FileName
{
get
{
if (IsDataHeader)
{
return Encoding.UTF8.GetString(Data, 10, Tools.IntToHost(BitConverter.ToInt32(Data, 6)));
}
return null;
}
}
/// <summary>
/// Gets the File Data from a Data Header
/// </summary>
public byte[] FileData
{
get
{
if (IsDataHeader)
{
var StartOfData = 6 + 4 + 4 + Tools.IntToHost(BitConverter.ToInt32(Data, 6));
var LengthOfData = Tools.IntToHost(BitConverter.ToInt32(Data, StartOfData - 4));
return Data
.Skip(StartOfData)
.Take(LengthOfData)
.ToArray();
}
return null;
}
}
/// <summary>
/// Reads A Header from the given Source
/// </summary>
/// <param name="Source"></param>
public PNGHeader(Stream Source)
{
using (var BR = new BinaryReader(Source, Encoding.UTF8, true))
{
//Format: <length:i><headername:s(4)><data:b(length)><crc:i>
int DataLength = Tools.IntToHost(BR.ReadInt32());
HeaderName = Encoding.Default.GetString(BR.ReadBytes(4));
if (DataLength > 0)
{
Data = BR.ReadBytes(DataLength);
}
else
{
Data = new byte[0];
}
#if DEBUG
uint StoredChecksum = Tools.IntToHost(BR.ReadUInt32());
if (CalcChecksum() != StoredChecksum)
{
Console.Error.WriteLine(@"
Invalid Checksum!
=================
HEADER : {0}
CALCULATED: {1}
STORED : {2}
",
HeaderName,
string.Join("-", BitConverter.GetBytes(CalcChecksum()).Select(m => m.ToString("X2")).ToArray()),
string.Join("-", BitConverter.GetBytes(StoredChecksum).Select(m => m.ToString("X2")).ToArray())
);
}
else
{
Console.Error.WriteLine("Checksum OK for {0}", HeaderName);
}
#else
//Discard Checksum for release build
Tools.IntToHost(BR.ReadUInt32());
#endif
}
}
/// <summary>
/// Creates a new Header
/// </summary>
/// <param name="HeaderName">Header Name</param>
/// <param name="Data">Header Data</param>
public PNGHeader(string HeaderName, byte[] Data)
{
if (string.IsNullOrEmpty(HeaderName) || Encoding.UTF8.GetByteCount(HeaderName) != 4)
{
throw new ArgumentException("HeaderName must be 4 bytes in length");
}
this.HeaderName = HeaderName;
this.Data = Data == null ? new byte[0] : (byte[])Data.Clone();
}
/// <summary>
/// Writes the Header to an output stream
/// </summary>
/// <param name="Output">Output Stream</param>
public void WriteHeader(Stream Output)
{
using (var BW = new BinaryWriter(Output, Encoding.UTF8, true))
{
BW.Write(Tools.IntToNetwork(Data.Length));
BW.Write(Encoding.Default.GetBytes(HeaderName));
BW.Write(Data);
BW.Write(Tools.IntToNetwork(CalcChecksum()));
}
}
#region CRC
/// <summary>
/// Table of CRCs of all 8-bit messages.
/// </summary>
private static uint[] crc_table = null;
/// <summary>
/// Static initializer
/// </summary>
static PNGHeader()
{
make_crc_table();
}
/// <summary>
/// Make the table for a fast CRC.
/// </summary>
private static void make_crc_table()
{
uint c;
uint n, k;
if (crc_table == null)
{
crc_table = new uint[256];
for (n = 0; n < crc_table.Length; n++)
{
c = n;
for (k = 0; k < 8; k++)
{
if ((c & 1) != 0)
{
c = 0xedb88320 ^ (c >> 1);
}
else
{
c = c >> 1;
}
}
crc_table[n] = c;
}
}
}
/// <summary>
/// Update a running CRC with the bytes buf[0..len-1]--the CRC
/// should be initialized to all 1's, and the transmitted value
/// is the 1's complement of the final running CRC (see the
/// crc() routine below)).
/// </summary>
/// <param name="crc">CRC Start value</param>
/// <param name="buf">Bytes</param>
/// <returns>Updated CRC</returns>
private static uint update_crc(uint crc, byte[] buf)
{
uint c = crc;
int n;
for (n = 0; n < buf.Length; n++)
{
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}
/// <summary>
/// Return the CRC of the bytes buf[0..len-1].
/// </summary>
/// <param name="buf">Byte array</param>
/// <returns>Calculates PNG CRC of a byte array</returns>
private static uint crc(byte[] buf)
{
return update_crc(0xffffffff, buf) ^ 0xffffffff;
}
/// <summary>
/// Calculates CRC of a byte array
/// </summary>
/// <param name="Data">Bytes</param>
/// <returns>CRC</returns>
public static uint CalcChecksum(byte[] Data)
{
return crc(Data);
}
/// <summary>
/// Calculates the Checksum of the current header
/// </summary>
/// <returns>Checksum</returns>
public uint CalcChecksum()
{
return CalcChecksum(Encoding.Default.GetBytes(HeaderName).Concat(Data).ToArray());
}
#endregion
}
}