183 lines
7.7 KiB
C#
183 lines
7.7 KiB
C#
|
namespace System.IO.Compression {
|
||
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using System.Globalization;
|
||
|
|
||
|
internal class FastEncoder {
|
||
|
|
||
|
private FastEncoderWindow inputWindow; // input history window
|
||
|
private Match currentMatch; // current match in history window
|
||
|
private double lastCompressionRatio;
|
||
|
|
||
|
public FastEncoder() {
|
||
|
inputWindow = new FastEncoderWindow();
|
||
|
currentMatch = new Match();
|
||
|
}
|
||
|
|
||
|
internal int BytesInHistory {
|
||
|
get {
|
||
|
return inputWindow.BytesAvailable;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal DeflateInput UnprocessedInput {
|
||
|
get {
|
||
|
return inputWindow.UnprocessedInput;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void FlushInput() {
|
||
|
inputWindow.FlushWindow();
|
||
|
}
|
||
|
|
||
|
internal Double LastCompressionRatio {
|
||
|
get { return lastCompressionRatio; }
|
||
|
}
|
||
|
|
||
|
// Copy the compressed bytes to output buffer as a block. maxBytesToCopy limits the number of
|
||
|
// bytes we can copy from input. Set to any value < 1 if no limit
|
||
|
internal void GetBlock(DeflateInput input, OutputBuffer output, int maxBytesToCopy) {
|
||
|
Debug.Assert(InputAvailable(input), "call SetInput before trying to compress!");
|
||
|
|
||
|
WriteDeflatePreamble(output);
|
||
|
GetCompressedOutput(input, output, maxBytesToCopy);
|
||
|
WriteEndOfBlock(output);
|
||
|
}
|
||
|
|
||
|
// Compress data but don't format as block (doesn't have header and footer)
|
||
|
internal void GetCompressedData(DeflateInput input, OutputBuffer output) {
|
||
|
GetCompressedOutput(input, output, -1);
|
||
|
}
|
||
|
|
||
|
internal void GetBlockHeader(OutputBuffer output) {
|
||
|
WriteDeflatePreamble(output);
|
||
|
}
|
||
|
|
||
|
internal void GetBlockFooter(OutputBuffer output) {
|
||
|
WriteEndOfBlock(output);
|
||
|
}
|
||
|
|
||
|
// maxBytesToCopy limits the number of bytes we can copy from input. Set to any value < 1 if no limit
|
||
|
private void GetCompressedOutput(DeflateInput input, OutputBuffer output, int maxBytesToCopy) {
|
||
|
// snapshot for compression ratio stats
|
||
|
int bytesWrittenPre = output.BytesWritten;
|
||
|
int bytesConsumedFromInput = 0;
|
||
|
int inputBytesPre = BytesInHistory + input.Count;
|
||
|
|
||
|
do {
|
||
|
// read more input data into the window if there is space available
|
||
|
int bytesToCopy = (input.Count < inputWindow.FreeWindowSpace) ?
|
||
|
input.Count : inputWindow.FreeWindowSpace;
|
||
|
if (maxBytesToCopy >= 1) {
|
||
|
bytesToCopy = Math.Min(bytesToCopy, maxBytesToCopy - bytesConsumedFromInput);
|
||
|
}
|
||
|
if (bytesToCopy > 0) {
|
||
|
// copy data into history window
|
||
|
inputWindow.CopyBytes(input.Buffer, input.StartIndex, bytesToCopy);
|
||
|
input.ConsumeBytes(bytesToCopy);
|
||
|
bytesConsumedFromInput += bytesToCopy;
|
||
|
}
|
||
|
|
||
|
GetCompressedOutput(output);
|
||
|
|
||
|
} while (SafeToWriteTo(output) && InputAvailable(input) && (maxBytesToCopy < 1 || bytesConsumedFromInput < maxBytesToCopy));
|
||
|
|
||
|
// determine compression ratio, save
|
||
|
int bytesWrittenPost = output.BytesWritten;
|
||
|
int bytesWritten = bytesWrittenPost - bytesWrittenPre;
|
||
|
int inputBytesPost = BytesInHistory + input.Count;
|
||
|
int totalBytesConsumed = inputBytesPre - inputBytesPost;
|
||
|
if (bytesWritten != 0) {
|
||
|
lastCompressionRatio = (double)bytesWritten / (double)totalBytesConsumed;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
// compress the bytes in input history window
|
||
|
private void GetCompressedOutput(OutputBuffer output) {
|
||
|
|
||
|
while (inputWindow.BytesAvailable > 0 && SafeToWriteTo(output)) {
|
||
|
|
||
|
// Find next match. A match can be a symbol,
|
||
|
// a distance/length pair, a symbol followed by a distance/Length pair
|
||
|
inputWindow.GetNextSymbolOrMatch(currentMatch);
|
||
|
|
||
|
if (currentMatch.State == MatchState.HasSymbol) {
|
||
|
WriteChar(currentMatch.Symbol, output);
|
||
|
}
|
||
|
else if (currentMatch.State == MatchState.HasMatch) {
|
||
|
WriteMatch(currentMatch.Length, currentMatch.Position, output);
|
||
|
}
|
||
|
else {
|
||
|
WriteChar(currentMatch.Symbol, output);
|
||
|
WriteMatch(currentMatch.Length, currentMatch.Position, output);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private bool InputAvailable(DeflateInput input) {
|
||
|
return input.Count > 0 || BytesInHistory > 0;
|
||
|
}
|
||
|
|
||
|
private bool SafeToWriteTo(OutputBuffer output) { // can we safely continue writing to output buffer
|
||
|
return output.FreeBytes > FastEncoderStatics.MaxCodeLen;
|
||
|
}
|
||
|
|
||
|
private void WriteEndOfBlock(OutputBuffer output) {
|
||
|
// The fast encoder outputs one long block, so it just needs to terminate this block
|
||
|
const int EndOfBlockCode = 256;
|
||
|
uint code_info = FastEncoderStatics.FastEncoderLiteralCodeInfo[EndOfBlockCode];
|
||
|
int code_len = (int)(code_info & 31);
|
||
|
output.WriteBits(code_len, code_info >> 5);
|
||
|
}
|
||
|
|
||
|
static internal void WriteMatch(int matchLen, int matchPos, OutputBuffer output) {
|
||
|
Debug.Assert(matchLen >= FastEncoderWindow.MinMatch && matchLen <= FastEncoderWindow.MaxMatch, "Illegal currentMatch length!");
|
||
|
Debug.WriteLineIf(CompressionTracingSwitch.Verbose, String.Format(CultureInfo.InvariantCulture, "Match: {0}:{1}", matchLen, matchPos), "Compression");
|
||
|
|
||
|
// Get the code information for a match code
|
||
|
uint codeInfo = FastEncoderStatics.FastEncoderLiteralCodeInfo[(FastEncoderStatics.NumChars + 1 - FastEncoderWindow.MinMatch) + matchLen];
|
||
|
int codeLen = (int)codeInfo & 31;
|
||
|
Debug.Assert(codeLen != 0, "Invalid Match Length!");
|
||
|
if (codeLen <= 16) {
|
||
|
output.WriteBits(codeLen, codeInfo >> 5);
|
||
|
}
|
||
|
else {
|
||
|
output.WriteBits(16, (codeInfo >> 5) & 65535);
|
||
|
output.WriteBits(codeLen - 16, codeInfo >> (5 + 16));
|
||
|
}
|
||
|
|
||
|
// Get the code information for a distance code
|
||
|
codeInfo = FastEncoderStatics.FastEncoderDistanceCodeInfo[FastEncoderStatics.GetSlot(matchPos)];
|
||
|
output.WriteBits((int)(codeInfo & 15), codeInfo >> 8);
|
||
|
int extraBits = (int)(codeInfo >> 4) & 15;
|
||
|
if (extraBits != 0) {
|
||
|
output.WriteBits(extraBits, (uint)matchPos & FastEncoderStatics.BitMask[extraBits]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static internal void WriteChar(byte b, OutputBuffer output) {
|
||
|
Debug.WriteLineIf(CompressionTracingSwitch.Verbose, String.Format(CultureInfo.InvariantCulture, "Literal: {0}", b ), "Compression");
|
||
|
|
||
|
uint code = FastEncoderStatics.FastEncoderLiteralCodeInfo[b];
|
||
|
output.WriteBits((int)code & 31, code >> 5);
|
||
|
}
|
||
|
|
||
|
// Output the block type and tree structure for our hard-coded trees.
|
||
|
// Contains following data:
|
||
|
// "final" block flag 1 bit
|
||
|
// BLOCKTYPE_DYNAMIC 2 bits
|
||
|
// FastEncoderLiteralTreeLength
|
||
|
// FastEncoderDistanceTreeLength
|
||
|
//
|
||
|
static internal void WriteDeflatePreamble(OutputBuffer output) {
|
||
|
//Debug.Assert( bitCount == 0, "bitCount must be zero before writing tree bit!");
|
||
|
|
||
|
output.WriteBytes(FastEncoderStatics.FastEncoderTreeStructureData, 0, FastEncoderStatics.FastEncoderTreeStructureData.Length);
|
||
|
output.WriteBits(FastEncoderStatics.FastEncoderPostTreeBitCount, FastEncoderStatics.FastEncoderPostTreeBitBuf);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|