-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDistorter.cs
331 lines (279 loc) · 11.1 KB
/
Distorter.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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
using System;
using System.Drawing;
using System.Drawing.Imaging;
namespace PKHack
{
public class DistortionEffect
{
public enum Type
{
Invalid,
Horizontal,
HorizontalInterlaced,
Vertical
}
private Type type;
private ushort ampl;
private ushort s_freq;
private short ampl_accel;
private short s_freq_accel;
private byte start;
private byte speed;
private short compr;
private short compr_accel;
private const double C1 = 1 / 512.0;
private const double C2 = 8.0 * Math.PI / (1024 * 256);
private const double C3 = Math.PI / 60.0;
/// <summary>
/// Evaluates the distortion effect at the given destination line and
/// time value and returns the computed offset value.
///
/// If the distortion mode is horizontal, this offset should be interpreted
/// as the number of pixels to offset the given line's starting x position.
///
/// If the distortion mode is vertical, this offset should be interpreted as
/// the y-coordinate of the line from the source bitmap to draw at the given
/// y-coordinate in the destination bitmap.
/// </summary>
/// <param name="y">The y-coordinate of the destination line to evaluate for</param>
/// <param name="t">The number of ticks since beginning animation</param>
/// <returns>The distortion offset for the given (y,t) coordinates</returns>
public int GetAppliedOffset(int y, int t)
{
// Compute "current" values of amplitude, frequency, and compression
ushort amplitude = (ushort)(ampl + ampl_accel * t * 2);
ushort frequency = (ushort)(s_freq + s_freq_accel * t * 2);
short compression = (short)(compr + compr_accel * t * 2);
// Compute the value of the sinusoidal line offset function
int S = (int)(C1 * amplitude * Math.Sin(C2 * frequency * y + C3 * speed * t));
if (type == Type.Horizontal)
{
return S;
}
if (type == Type.HorizontalInterlaced)
{
return (y % 2) == 0 ? -S : S;
}
if (type == Type.Vertical)
{
int L = (int)(y * (1 + compression / 256.0) + S) % 256;
if (L < 0) L = 256 + L;
if (L > 255) L = 256 - L;
return L;
}
return 0;
}
/// <summary>
/// Gets or sets the type of distortion effect to use.
/// </summary>
public Type Effect
{
get { return type; }
set { type = value; }
}
/// <summary>
/// Gets or sets the amplitude of the distortion effect
/// </summary>
public ushort Amplitude
{
get { return ampl; }
set { ampl = value; }
}
/// <summary>
/// Gets or sets the spatial frequency of the distortion effect
/// </summary>
public ushort Frequency
{
get { return s_freq; }
set { s_freq = value; }
}
/// <summary>
/// The amount to add to the amplitude value every iteration.
/// </summary>
public short AmplitudeAcceleration
{
get { return ampl_accel; }
set { ampl_accel = value; }
}
/// <summary>
/// The amount to add to the frequency value each iteration.
/// </summary>
public short FrequencyAcceleration
{
get { return s_freq_accel; }
set { s_freq_accel = value; }
}
/// <summary>
/// Compression factor
/// </summary>
public short Compression
{
get { return compr; }
set { compr = value; }
}
/// <summary>
/// Change in the compression value every iteration
/// </summary>
public short CompressionAcceleration
{
get { return compr_accel; }
set { compr_accel = value; }
}
/// <summary>
/// Offset for starting time.
/// </summary>
public byte StartTime
{
get { return start; }
set { start = value; }
}
/// <summary>
/// Gets or sets the "speed" of the distortion.
/// 0 = no animation, 127 = very fast, 255 = very slow for some reason
/// </summary>
public byte Speed
{
get { return speed; }
set { speed = value; }
}
}
public class Distorter
{
private Bitmap src;
// There is some redundancy here: 'effect' is currently what is used
// in computing frames, although really there should be a list of
// four different effects ('dist') which are used in sequence.
//
// 'dist' is currently unused, but ComputeFrame should be changed to
// make use of it as soon as the precise nature of effect sequencing
// can be determined.
//
// The goal is to make Distorter a general-purpose BG effect class that
// can be used to show either a single distortion effect, or to show the
// entire sequence of effects associated with a background entry (including
// scrolling and palette animation, which still need to be implemented).
//
// Also note that "current_dist" should not be used. Distorter should be
// a "temporally stateless" class, meaning that all temporal effects should
// be computed at once, per request, rather than maintaining an internal
// tick count. (The idea being that it should be fast to compute any individual
// frame. Since it is certainly possible to do this, there is no sense
// requiring that all previous frames be computed before any given desired
// frame.)
private DistortionEffect effect = new DistortionEffect();
private DistortionEffect[] dist = new DistortionEffect[4];
private int current_dist = 1;
public DistortionEffect[] Distortions
{
get { return dist; }
}
public DistortionEffect CurrentDistortion
{
get { return dist[current_dist]; }
}
public DistortionEffect Effect
{
get { return effect; }
set { effect = value; }
}
public Bitmap Original
{
get { return src; }
set
{
src = new Bitmap(value);
}
}
public void OverlayFrame(Bitmap dst, int letterbox, int ticks, float alpha, bool erase)
{
ComputeFrame(dst, letterbox, ticks, alpha, erase);
}
public void RenderFrame(Bitmap dst, int ticks)
{
ComputeFrame(dst, 0, ticks, 1.0f, true);
}
// Computes a distortion of the source and overlays it on a destination bitmap
// with specified alpha
private unsafe void ComputeFrame(Bitmap dst, int letterbox, int ticks, float alpha, bool erase)
{
// Lock source and destination bitmap bits
BitmapData dstData = dst.LockBits(new Rectangle(0, 0, dst.Width, dst.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData srcData = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int dstStride = dstData.Stride;
int srcStride = srcData.Stride;
byte* bdst = (byte*)dstData.Scan0;
byte* bsrc = (byte*)srcData.Scan0;
// Given the list of 4 distortions and the tick count, decide which
// effect to use:
// Basically, we have 4 effects, each possibly with a duration.
//
// Evaluation order is: 1, 2, 3, 0
//
// If the first effect is null, control transitions to the second effect.
// If the first and second effects are null, no effect occurs.
// If any other effect is null, the sequence is truncated.
// If a non-null effect has a zero duration, it will not be switched
// away from.
//
// Essentially, this configuration sets up a precise and repeating
// sequence of between 0 and 4 different distortion effects. Once we
// compute the sequence, computing the particular frame of which distortion
// to use becomes easy; simply mod the tick count by the total duration
// of the effects that are used in the sequence, then check the remainder
// against the cumulative durations of each effect.
//
// I guess the trick is to be sure that my description above is correct.
//
// Heh.
// First, count the total duration of the effects:
/*int total = 0;
for (int i = 0; i < 4; i++)
{
}*/
for (int y = 0; y < 224; y++)
{
int S = effect.GetAppliedOffset(y, ticks);
int L = y;
if (Effect.Effect == DistortionEffect.Type.Vertical)
L = S;
for (int x = 0; x < 256; x++)
{
if (y < letterbox || y > 224 - letterbox)
{
bdst[x * 3 + y * dstStride + 2] = 0;
bdst[x * 3 + y * dstStride + 1] = 0;
bdst[x * 3 + y * dstStride + 0] = 0;
continue;
}
int dx = x;
if (Effect.Effect == DistortionEffect.Type.Horizontal
|| Effect.Effect == DistortionEffect.Type.HorizontalInterlaced)
{
dx = (x + S) % 256;
if (dx < 0) dx = 256 + dx;
if (dx > 255) dx = 256 - dx;
}
/*
* Either copy or add to the destination bitmap
*/
if (erase)
{
bdst[x * 3 + y * dstStride + 2] = (byte)(alpha * bsrc[dx * 3 + L * srcStride + 2]);
bdst[x * 3 + y * dstStride + 1] = (byte)(alpha * bsrc[dx * 3 + L * srcStride + 1]);
bdst[x * 3 + y * dstStride + 0] = (byte)(alpha * bsrc[dx * 3 + L * srcStride + 0]);
}
else
{
bdst[x * 3 + y * dstStride + 2] += (byte)(alpha * bsrc[dx * 3 + L * srcStride + 2]);
bdst[x * 3 + y * dstStride + 1] += (byte)(alpha * bsrc[dx * 3 + L * srcStride + 1]);
bdst[x * 3 + y * dstStride + 0] += (byte)(alpha * bsrc[dx * 3 + L * srcStride + 0]);
}
}
}
dst.UnlockBits(dstData);
src.UnlockBits(srcData);
}
}
}