-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFrame.cs
285 lines (257 loc) · 12.1 KB
/
Frame.cs
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
/*
* Idmr.ImageFormat.Act, Allows editing capability of LucasArts *.ACT files.
* Copyright (C) 2009-2023 Michael Gaisser (mjgaisser@gmail.com)
* Licensed under the MPL v2.0 or later
*
* Full notice in ActImage.cs
* VERSION: 2.2
*/
/* CHANGE LOG
* v2.2, 230708
* [UPD] using jumps properly now
* [NEW] UseFrameColors, support for Global colors
* v2.1, 141214
* [UPD] switch to MPL
* v2.0, 121014
* - Release
*/
using System;
using System.Drawing;
using System.Drawing.Imaging;
using Idmr.Common;
namespace Idmr.ImageFormat.Act
{
/// <summary>Object for individual Frame information</summary>
public class Frame
{
internal ActImage _parent;
internal byte[] _header = new byte[_headerLength];
internal Color[] _colors;
Point _location = new Point(621, 621);
internal byte[] _rows;
internal Bitmap _image; // Format8bppIndexed
bool _useFrameColors = true;
internal const int _headerLength = 0x2C;
#region constructors
/// <summary>Populates the Frame with information from raw data</summary>
/// <param name="parent">The <see cref="ActImage"/> the Frame belongs to</param>
/// <param name="raw">Complete raw byte data of a Frame</param>
/// <exception cref="ArgumentException">No colors defined in the file.</exception>
internal Frame(ActImage parent, byte[] raw)
{
_parent = parent;
ArrayFunctions.TrimArray(raw, 0, _header);
var colorsJump = BitConverter.ToInt32(_header, 4);
var imageJump = BitConverter.ToInt32(_header, 8);
_useFrameColors = BitConverter.ToInt32(_header, 0x24) == 0x18;
if (!_useFrameColors && !_parent.UseGlobalColors) throw new ArgumentException("No global or frame colors defined!", "file");
if (_useFrameColors) // I've always seen this as true
{
_colors = new Color[BitConverter.ToInt32(_header, 0x28)];
System.Diagnostics.Debug.WriteLine("Colors: " + NumberOfColors);
for (int c = 0, pos = colorsJump; c < _colors.Length; c++, pos += 4)
_colors[c] = Color.FromArgb(raw[pos], raw[pos + 1], raw[pos + 2]); // Red, Green, Blue, Alpha unused
}
_location = new Point(BitConverter.ToInt32(raw, imageJump), BitConverter.ToInt32(raw, imageJump + 4)); // FrameLeft, FrameTop
System.Diagnostics.Debug.WriteLine("Location: " + X + "," + Y + "\r\nSize: " + Width + "," + Height);
_rows = new byte[raw.Length - imageJump - 0x10];
ArrayFunctions.TrimArray(raw, imageJump + 0x10, _rows);
if (_useFrameColors) _image = ActImage.DecodeImage(_rows, Width, Height, _colors, _lengthBitCount);
else _image = ActImage.DecodeImage(_rows, Width, Height, _parent.GlobalColors, _lengthBitCount);
}
/// <summary>Creates a new Frame with the given <see cref="PixelFormat.Format8bppIndexed"/> image</summary>
/// <param name="parent">The <see cref="ActImage"/> the Frame belongs to</param>
/// <param name="image">Image to be used for the Frame, must be <see cref="PixelFormat.Format8bppIndexed"/></param>
/// <exception cref="FormatException"><paramref name="image"/> is not <see cref="PixelFormat.Format8bppIndexed"/></exception>
/// <exception cref="BoundaryException"><paramref name="image"/> exceeds maximum allowable dimensions</exception>
internal Frame(ActImage parent, Bitmap image)
{
if (image.PixelFormat != PixelFormat.Format8bppIndexed)
throw new FormatException("Image must be 8bppIndexed (256 color)");
_parent = parent;
initializeHeader();
Image = image;
}
/// <summary>Creates an empty Frame</summary>
/// <param name="parent">The <see cref="ActImage"/> the Frame belongs to</param>
internal Frame(ActImage parent)
{
_parent = parent;
initializeHeader();
}
#endregion constructors
#region public methods
/// <summary>Modifies the given palette entry</summary>
/// <param name="index">Color index</param>
/// <param name="color">Color to be used</param>
/// <exception cref="IndexOutOfRangeException">Invalid <paramref name="index"/> value</exception>
/// <exception cref="InvalidOperationException"><see cref="UseFrameColors"/> is <b>false</b>.</exception>
/// <exception cref="NullReferenceException"><see cref="Image"/> has not been initialized</exception>
public void SetColor(int index, Color color)
{
if (!_useFrameColors) throw new InvalidOperationException();
_colors[index] = color;
ColorPalette pal = _image.Palette;
pal.Entries[index] = color;
_image.Palette = pal;
}
/// <summary>Modifies the given palette entry</summary>
/// <param name="index">Color index</param>
/// <param name="red">R value of color to be used</param>
/// <param name="green">G value of color to be used</param>
/// <param name="blue">B value of color to be used</param>
/// <exception cref="IndexOutOfRangeException">Invalid <paramref name="index"/> value</exception>
/// <exception cref="InvalidOperationException"><see cref="UseFrameColors"/> is <b>false</b>.</exception>
/// <exception cref="NullReferenceException"><see cref="Image"/> has not been initialized</exception>
public void SetColor(int index, byte red, byte green, byte blue) { SetColor(index, Color.FromArgb(red, green, blue)); }
#endregion public methods
#region public properties
/// <summary>Gets a copy of the frame color array.</summary>
/// <exception cref="NullReferenceException">Frame colors are not used.</exception>
public Color[] Colors => (Color[])_colors.Clone();
/// <summary>Gets the height of the Frame</summary>
public int Height
{
get => BitConverter.ToInt32(_header, 0x14);
internal set => ArrayFunctions.WriteToArray(value, _header, 0x14);
}
/// <summary>Gets or sets the <see cref="PixelFormat.Format8bppIndexed"/> frame image</summary>
/// <exception cref="FormatException"><i>Image</i> is not <see cref="PixelFormat.Format8bppIndexed"/></exception>
/// <exception cref="BoundaryException"><i>Image.Size</i> exceeds maximum allowable dimensions</exception>
/// <remarks><see cref="Colors"/> is updated with the trimmed color array, <i>Image</i> is also updated with new indexes as necessary.<br/>
/// If <see cref="Location"/> is its default value, it will initialize to center the Frame around <see cref="ActImage.Center"/>.<br/>
/// Parent <see cref="ActImage.Size"/> will adjust as necessary.</remarks>
public Bitmap Image
{
get => _image;
set
{
if (value.PixelFormat != PixelFormat.Format8bppIndexed)
throw new FormatException("Image must be 8bppIndexed (256 color)");
if (value.Width > MaximumWidth || value.Height > MaximumHeight)
throw new BoundaryException("Image.Size", MaximumWidth + "x" + MaximumHeight);
if (_parent.NumberOfFrames == 1) _parent.Size = value.Size;
_image = value;
_colors = GraphicsFunctions.GetTrimmedColors(_image);
setBitCount();
_rows = ActImage.EncodeImage(_image, _lengthBitCount);
updateHeader();
if (_location.X == 621 && _location.Y == 621) Location = new Point(-Width / 2, -Height / 2);
_parent.recalculateSize();
}
}
/// <summary>Gets or sets the frame origin position relative to <see cref="ActImage.Center"/></summary>
/// <exception cref="BoundaryException">Value causes a portion of the frame to extend beyond <see cref="ActImage"/> limits</exception>
/// <remarks>If <see cref="Image"/> is uninitialized, value is <b>(621, 621)</b>.<br/>
/// Parent <see cref="ActImage.Size"/> will adjust as necessary.</remarks>
public Point Location
{
get => _location;
set
{
_location.X = value.X;
_location.Y = value.Y;
}
}
/// <summary>Gets or sets the X component of <see cref="Location"/></summary>
/// <exception cref="BoundaryException">Value causes a portion of the frame to extend beyond <see cref="ActImage"/> limits</exception>
/// <remarks>If <see cref="Image"/> is uninitialized, value is <b>621</b>.<br/>
/// Parent <see cref="ActImage.Size"/> will adjust as necessary.</remarks>
public int X
{
get => _location.X;
set
{
int upper = MaximumWidth - Width - _parent.Center.X;
if (value < _parent.Center.X * -1 && value > upper)
throw new BoundaryException("X", (_parent.Center.X * -1) + "-" + upper);
_location.X = value;
_parent.recalculateSize();
}
}
/// <summary>Gets or sets the Y component of <see cref="Location"/></summary>
/// <exception cref="BoundaryException">Value causes a portion of the frame to extend beyond <see cref="ActImage"/> limits</exception>
/// <remarks>If <see cref="Image"/> is uninitialized, value is <b>621</b>.<br/>
/// Parent <see cref="ActImage.Size"/> will adjust as necessary.</remarks>
public int Y
{
get => _location.Y;
set
{
int upper = MaximumHeight - Height - _parent.Center.Y;
if (value < _parent.Center.Y * -1 && value > upper)
throw new BoundaryException("Y", (_parent.Center.Y * -1) + "-" + upper);
_location.Y = value;
_parent.recalculateSize();
}
}
/// <summary>Maximum height allowed within a single Frame</summary>
/// <remarks>Value is <b>256</b></remarks>
public const int MaximumHeight = 256;
/// <summary>Maximum width allowed within a single Frame</summary>
/// <remarks>Value is <b>256</b></remarks>
public const int MaximumWidth = 256;
/// <summary>Gets the number of colors defined by the Frame</summary>
public int NumberOfColors
{
get
{
if (_useFrameColors) return _colors.Length;
else return 0;
}
}
/// <summary>Gets or sets if the frame's color array is used.</summary>
/// <remarks>If setting to <b>true</b>, initializes a 256 color array.</remarks>
public bool UseFrameColors
{
get => _useFrameColors;
set
{
if (value) _colors = new Color[256];
else _colors = null;
_useFrameColors = value;
}
}
/// <summary>Gets the width of the Frame</summary>
public int Width
{
get => BitConverter.ToInt32(_header, 0x10);
internal set => ArrayFunctions.WriteToArray(value, _header, 0x10);
}
#endregion public properties
#region private methods
void setBitCount()
{
int bitCount = 3; // Shift=3, allows 8px lines, 00-1F ColorIndex
if (_image.Width <= 16) bitCount = 4; // Shift=4 allows 16px lines, 00-0F ColorIndex
if (_colors.Length <= 8) bitCount = 5; // Shift=5 allows 32px, 00-08 ColorIndex
else if (_colors.Length <= 16) bitCount = 4;
_lengthBitCount = bitCount;
}
void updateHeader()
{
ArrayFunctions.WriteToArray(_imageDataOffset + _rows.Length + 0x10, _header, 0);
ArrayFunctions.WriteToArray(_imageDataOffset, _header, 8);
ArrayFunctions.WriteToArray(_imageDataOffset + _rows.Length + 0x10, _header, 0xC); // this should be Reserved and pointless
Width = _image.Width;
Height = _image.Height;
ArrayFunctions.WriteToArray(_colors.Length, _header, 0x28);
}
void initializeHeader()
{
ArrayFunctions.WriteToArray(_headerLength, _header, 4);
ArrayFunctions.WriteToArray(3, _header, 0x20); // temp shift value
ArrayFunctions.WriteToArray(0x18, _header, 0x24);
}
#endregion private methods
#region private properties
int _lengthBitCount
{
get => BitConverter.ToInt32(_header, 0x20);
set => ArrayFunctions.WriteToArray(value, _header, 0x20);
}
int _imageDataOffset => _headerLength + NumberOfColors * 4;
internal int _length => _imageDataOffset + 0x10 + _rows.Length;
#endregion private properties
}
}