-
Notifications
You must be signed in to change notification settings - Fork 9
/
LZ77.cs
146 lines (121 loc) · 4.52 KB
/
LZ77.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
using System;
using System.Collections.Generic;
namespace eAmuseCore.Compression
{
public static class LZ77
{
private const int minLength = 3;
public static byte[] Decompress(byte[] data)
{
List<byte> res = new List<byte>();
int pos = 0;
int state = 0;
while (pos < data.Length)
{
state >>= 1;
if (state <= 1)
state = data[pos++] | 0x100;
if ((state & 1) != 0)
{
res.Add(data[pos++]);
}
else
{
byte byte1 = data[pos++];
byte byte2 = data[pos++];
int length = (byte2 & 0xf) + minLength;
int distance = (byte1 << 4) | (byte2 >> 4);
if (distance == 0)
break;
int resPos = res.Count;
for (int i = 0; i < length; ++i)
{
int o = resPos - distance + i;
res.Add((o < 0) ? (byte)0 : res[o]);
}
}
}
return res.ToArray();
}
private struct Match
{
public int distance;
public int length;
}
private static Match FindLongestMatch(byte[] data, int offset, int windowSize, int lookAhead, int minLength)
{
Match res = new Match
{
distance = -1,
length = -1
};
int maxLength = Math.Min(lookAhead, data.Length - offset);
for (int matchLength = maxLength; matchLength >= minLength; --matchLength)
{
for (int distance = 1; distance <= windowSize; ++distance)
{
if (data.RepeatingSequencesEqual(offset, matchLength, offset - distance, distance))
{
res.distance = distance;
res.length = matchLength;
return res;
}
}
}
return res;
}
private static bool RepeatingSequencesEqual(this byte[] arr, int matchOffset, int matchLength, int compOffset, int compLength)
{
for (int i = 0; i < matchLength; ++i)
{
if (arr.GV(matchOffset + i) != arr.GV(compOffset + (i % compLength)))
return false;
}
return true;
}
private static byte GV(this byte[] arr, int i)
{
return (i < 0) ? (byte)0 : arr[i];
}
public static byte[] Compress(byte[] data, int windowSize = 256, int lookAhead = 0xf + minLength)
{
if (lookAhead < minLength || lookAhead > 0xf + minLength)
throw new ArgumentException("lookAhead out of range", "lookAhead");
if (windowSize < lookAhead)
throw new ArgumentException("windowSize needs to be larger than lookAhead", "windowSize");
byte[] result = new byte[data.Length + (data.Length / 8) + 4];
int resOffset = 1;
int resStateOffset = 0;
int resStateShift = 0;
int offset = 0;
while (offset < data.Length)
{
Match match = FindLongestMatch(data, offset, windowSize, lookAhead, minLength);
if (match.length >= minLength && match.distance > 0)
{
int binLength = match.length - minLength;
#if DEBUG
if (binLength > 0xf || match.distance > 0xfff || match.length > data.Length - offset)
throw new Exception("INTERNAL ERROR: found match is invalid!");
#endif
result[resOffset++] = (byte)(match.distance >> 4);
result[resOffset++] = (byte)(((match.distance & 0xf) << 4) | binLength);
resStateShift += 1;
offset += match.length;
}
else
{
result[resStateOffset] |= (byte)(1 << resStateShift++);
result[resOffset++] = data[offset++];
}
if (resStateShift >= 8)
{
resStateShift = 0;
resStateOffset = resOffset++;
}
}
Array.Resize(ref result, resOffset + 2);
return result;
}
}
}