forked from eriscorp/dalib
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathSpfView.cs
More file actions
221 lines (187 loc) · 6.66 KB
/
SpfView.cs
File metadata and controls
221 lines (187 loc) · 6.66 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
#region
using System;
using System.IO;
using System.Text;
using DALib.Data;
using DALib.Definitions;
using DALib.Extensions;
using SkiaSharp;
#endregion
namespace DALib.Drawing.Virtualized;
/// <summary>
/// A lightweight view over an SPF file in a DataArchive. Parses the header, palettes, and frame table of contents on
/// construction; individual frame pixel data is read on demand from the underlying archive entry.
/// </summary>
public sealed class SpfView
{
private readonly long DataSectionOffset;
private readonly DataArchiveEntry Entry;
private readonly SpfTocEntry[] Toc;
/// <summary>
/// Indicates whether the images are colorized or palettized
/// </summary>
public SpfFormatType Format { get; }
/// <summary>
/// The primary palette used for palettized images
/// </summary>
public Palette? PrimaryColors { get; }
/// <summary>
/// The secondary palette used for palettized images
/// </summary>
public Palette? SecondaryColors { get; }
/// <summary>
/// The number of frames in the SPF file
/// </summary>
public int Count => Toc.Length;
private SpfView(
DataArchiveEntry entry,
long dataSectionOffset,
SpfTocEntry[] toc,
SpfFormatType format,
Palette? primaryColors,
Palette? secondaryColors)
{
Entry = entry;
DataSectionOffset = dataSectionOffset;
Toc = toc;
Format = format;
PrimaryColors = primaryColors;
SecondaryColors = secondaryColors;
}
/// <summary>
/// Creates an SpfView with the specified fileName from the specified archive
/// </summary>
/// <param name="fileName">
/// The name of the SPF file to extract from the archive.
/// </param>
/// <param name="archive">
/// The DataArchive from which to retrieve the SPF file.
/// </param>
/// <exception cref="FileNotFoundException">
/// Thrown if the SPF file with the specified name is not found in the archive.
/// </exception>
public static SpfView FromArchive(string fileName, DataArchive archive)
{
if (!archive.TryGetValue(fileName.WithExtension(".spf"), out var entry))
throw new FileNotFoundException($"SPF file with the name \"{fileName}\" was not found in the archive");
return FromEntry(entry);
}
/// <summary>
/// Creates an SpfView from the specified archive entry
/// </summary>
/// <param name="entry">
/// The DataArchiveEntry to load the SpfView from
/// </param>
public static SpfView FromEntry(DataArchiveEntry entry)
{
using var stream = entry.ToStreamSegment();
using var reader = new BinaryReader(stream, Encoding.Default, true);
_ = reader.ReadUInt32(); // Unknown1
_ = reader.ReadUInt32(); // Unknown2
var format = (SpfFormatType)reader.ReadUInt32();
Palette? primaryColors = null;
Palette? secondaryColors = null;
if (format == SpfFormatType.Palettized)
{
primaryColors = new Palette();
secondaryColors = new Palette();
for (var i = 0; i < 256; i++)
primaryColors[i] = reader.ReadRgb565Color();
for (var i = 0; i < 256; i++)
secondaryColors[i] = reader.ReadRgb555Color();
}
var frameCount = reader.ReadUInt32();
var tocEntries = new SpfTocEntry[frameCount];
for (var i = 0; i < frameCount; i++)
tocEntries[i] = new SpfTocEntry(
reader.ReadUInt16(),
reader.ReadUInt16(),
reader.ReadUInt16(),
reader.ReadUInt16(),
reader.ReadInt16(),
reader.ReadInt16(),
(reader.ReadUInt32() & 1) != 0,
reader.ReadUInt32(),
reader.ReadUInt32(),
reader.ReadUInt32(),
reader.ReadUInt32());
// totalByteCount followed by data section
_ = reader.ReadUInt32();
var dataSectionOffset = stream.Position;
return new SpfView(
entry,
dataSectionOffset,
tocEntries,
format,
primaryColors,
secondaryColors);
}
/// <summary>
/// Reads and returns the frame at the specified index. Pixel data is read from the archive on each access.
/// </summary>
public SpfFrame this[int index]
{
get
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Toc.Length);
var toc = Toc[index];
using var stream = Entry.ToStreamSegment();
using var reader = new BinaryReader(stream, Encoding.Default, true);
stream.Seek(DataSectionOffset + toc.StartAddress, SeekOrigin.Begin);
var frame = new SpfFrame
{
Left = toc.Left,
Top = toc.Top,
Right = toc.Right,
Bottom = toc.Bottom,
CenterX = toc.CenterX,
CenterY = toc.CenterY,
HasCenterPoint = toc.HasCenterPoint,
StartAddress = toc.StartAddress,
ByteWidth = toc.ByteWidth,
ByteCount = toc.ByteCount,
ImageByteCount = toc.ImageByteCount
};
switch (Format)
{
case SpfFormatType.Palettized:
frame.Data = reader.ReadBytes((int)toc.ByteCount);
break;
case SpfFormatType.Colorized:
frame.ColorData = new SKColor[toc.ImageByteCount];
var colorIndex = 0;
for (var y = 0; y < toc.Bottom; y++)
for (var x = 0; x < toc.Right; x++)
frame.ColorData[colorIndex++] = reader.ReadRgb565Color();
break;
}
return frame;
}
}
/// <summary>
/// Attempts to read the frame at the specified index. Returns false if the index is out of range.
/// </summary>
public bool TryGetValue(int index, out SpfFrame? frame)
{
if ((index < 0) || (index >= Toc.Length))
{
frame = null;
return false;
}
frame = this[index];
return true;
}
private readonly record struct SpfTocEntry(
ushort Left,
ushort Top,
ushort Right,
ushort Bottom,
short CenterX,
short CenterY,
bool HasCenterPoint,
uint StartAddress,
uint ByteWidth,
uint ByteCount,
uint ImageByteCount);
}