forked from eriscorp/dalib
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathEpfView.cs
More file actions
197 lines (168 loc) · 6.1 KB
/
EpfView.cs
File metadata and controls
197 lines (168 loc) · 6.1 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
#region
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using DALib.Data;
using DALib.Extensions;
#endregion
namespace DALib.Drawing.Virtualized;
/// <summary>
/// A lightweight view over an EPF file in a DataArchive. Parses only the header and table of contents on construction;
/// individual frame pixel data is read on demand from the underlying memory-mapped archive entry.
/// </summary>
public sealed class EpfView
{
private const int HEADER_LENGTH = 12;
private const int TOC_ENTRY_SIZE = 16;
private readonly DataArchiveEntry Entry;
private readonly TocEntry[] Toc;
private readonly int TocAddress;
/// <summary>
/// The pixel height of the image
/// </summary>
public short PixelHeight { get; }
/// <summary>
/// The pixel width of the image
/// </summary>
public short PixelWidth { get; }
/// <summary>
/// The number of frames in the EPF file
/// </summary>
public int Count => Toc.Length;
private EpfView(
DataArchiveEntry entry,
short pixelWidth,
short pixelHeight,
int tocAddress,
TocEntry[] toc)
{
Entry = entry;
PixelWidth = pixelWidth;
PixelHeight = pixelHeight;
TocAddress = tocAddress;
Toc = toc;
}
/// <summary>
/// Creates an EpfView with the specified fileName from the specified archive
/// </summary>
/// <param name="fileName">
/// The name of the EPF file to extract from the archive.
/// </param>
/// <param name="archive">
/// The DataArchive from which to retrieve the EPF file.
/// </param>
/// <exception cref="FileNotFoundException">
/// Thrown if the EPF file with the specified name is not found in the archive.
/// </exception>
public static EpfView FromArchive(string fileName, DataArchive archive)
{
if (!archive.TryGetValue(fileName.WithExtension(".epf"), out var entry))
throw new FileNotFoundException($"EPF file with the name \"{fileName}\" was not found in the archive");
return FromEntry(entry);
}
/// <summary>
/// Creates an EpfView from the specified archive entry
/// </summary>
/// <param name="entry">
/// The DataArchiveEntry to load the EpfView from
/// </param>
public static EpfView FromEntry(DataArchiveEntry entry)
{
using var stream = entry.ToStreamSegment();
using var reader = new BinaryReader(stream, Encoding.Default, true);
var frameCount = reader.ReadInt16();
var pixelWidth = reader.ReadInt16();
var pixelHeight = reader.ReadInt16();
stream.Seek(2, SeekOrigin.Current);
var tocAddress = reader.ReadInt32();
var tocEntries = new List<TocEntry>(frameCount);
for (var i = 0; i < frameCount; i++)
{
stream.Seek(HEADER_LENGTH + tocAddress + i * TOC_ENTRY_SIZE, SeekOrigin.Begin);
var top = reader.ReadInt16();
var left = reader.ReadInt16();
var bottom = reader.ReadInt16();
var right = reader.ReadInt16();
var startAddress = reader.ReadInt32();
var endAddress = reader.ReadInt32();
//empty frames (width==0 || height==0) are preserved in the TOC so that direct-index
//access by animation-frame index stays stable. Weapons/equipment use 0x0 frames as a
//"no visual on this pose" marker; dropping them would shift all subsequent indices and
//either mis-render or mask later frames.
tocEntries.Add(
new TocEntry(
top,
left,
bottom,
right,
startAddress,
endAddress));
}
return new EpfView(
entry,
pixelWidth,
pixelHeight,
tocAddress,
tocEntries.ToArray());
}
/// <summary>
/// Reads and returns the frame at the specified index. Pixel data is read from the archive on each access.
/// </summary>
public EpfFrame this[int index]
{
get
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Toc.Length);
var toc = Toc[index];
var width = toc.Right - toc.Left;
var height = toc.Bottom - toc.Top;
//empty-frame marker: preserve the TOC entry but return an empty Data array — callers
//should check PixelWidth/PixelHeight before rendering.
if ((width == 0) || (height == 0))
return new EpfFrame
{
Top = toc.Top,
Left = toc.Left,
Bottom = toc.Bottom,
Right = toc.Right,
Data = []
};
using var stream = Entry.ToStreamSegment();
using var reader = new BinaryReader(stream, Encoding.Default, true);
stream.Seek(HEADER_LENGTH + toc.StartAddress, SeekOrigin.Begin);
var data = (toc.EndAddress - toc.StartAddress) == (width * height)
? reader.ReadBytes(toc.EndAddress - toc.StartAddress)
: reader.ReadBytes(TocAddress - toc.StartAddress);
return new EpfFrame
{
Top = toc.Top,
Left = toc.Left,
Bottom = toc.Bottom,
Right = toc.Right,
Data = data
};
}
}
/// <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 EpfFrame? frame)
{
if ((index < 0) || (index >= Toc.Length))
{
frame = null;
return false;
}
frame = this[index];
return true;
}
private readonly record struct TocEntry(
short Top,
short Left,
short Bottom,
short Right,
int StartAddress,
int EndAddress);
}