using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using QiHe.CodeLib.Compress.DeflateFormat;
namespace QiHe.CodeLib.Compress
{
public partial class Deflate
{
public static byte[] Compress(byte[] data)
{
MemoryStream input = new MemoryStream(data);
MemoryStream output = new MemoryStream();
Compress(input, output);
return output.ToArray();
}
public static void Compress(Stream input, Stream output)
{
Compress(input, output, CompressionOption.Smaller);
}
public static void Compress(Stream input, Stream output, CompressionOption option)
{
BitStream outputStream = new BitStream(output);
CompressedItemGroup group = new CompressedItemGroup();
MultiSet<int> alphabetSet = new MultiSet<int>();
MultiSet<int> distanceSet = new MultiSet<int>();
float ratio = 8;
bool decrease = false;
bool first = true;
foreach (CompressedItem item in Compressor.AnalyzeItems(input, option))
{
group.Items.Add(item);
if (item is LiteralByte)
{
alphabetSet.Add(((LiteralByte)item).Value);
}
else if (item is LengthDistancePair)
{
LengthDistancePair pair = item as LengthDistancePair;
alphabetSet.Add(DeflateCoding.Length[pair.Length].Code);
distanceSet.Add(DeflateCoding.Distance[pair.Distance].Code);
}
if (group.Items.Count % 200 == 0)
{
float count = (float)(group.Items.Count);
float new_ratio = EstimateCompressRatio(alphabetSet, count);
if (new_ratio > ratio && decrease)
{
if (!first)
{
//TraceTool.MultiColPage.Debug.Send(String.Format("{0}\t{1}", count, ratio));
alphabetSet.Add(DeflateCoding.EndOfBlock);
Encode(group, alphabetSet, distanceSet, outputStream);
group.Items.Clear();
alphabetSet.Clear();
distanceSet.Clear();
new_ratio = 8;
decrease = false;
first = true;
}
else
{
decrease = false;
first = false;
}
}
if (new_ratio < ratio && !decrease)
{
decrease = true;
}
ratio = new_ratio;
}
}
alphabetSet.Add(DeflateCoding.EndOfBlock);
group.IsFinal = true;
Encode(group, alphabetSet, distanceSet, outputStream);
outputStream.Flush();
}
private static void Encode(CompressedItemGroup group, MultiSet<int> alphabetSet, MultiSet<int> distanceSet, BitStream output)
{
HuffmanCodes huffmanCodes = new HuffmanCodes(alphabetSet, distanceSet);
int dynamicSize = huffmanCodes.CalculateBlockSize();
int defaultSize = huffmanCodes.CalculateDefaultBlockSize();
if (dynamicSize >= defaultSize)
{
WriteDefaultCodedBlock(group, output);
}
else
{
WriteDynamicCodedBlock(group, output, huffmanCodes);
}
}
private static float EstimateCompressRatio(MultiSet<int> alphabetSet, float count)
{
HuffmanTree alphabetHuffmanCode = HuffmanTree.FromSymbolWeights(alphabetSet, 15);
int wpl = alphabetHuffmanCode.WeightedPathLength;
float new_ratio = (float)Math.Round((wpl + alphabetSet.Count * 7) / count, 2);
return new_ratio;
}
private static void Encode(CompressedItemGroup group, BitStream output)
{
MultiSet<int> alphabetSet = new MultiSet<int>();
MultiSet<int> distanceSet = new MultiSet<int>();
foreach (CompressedItem item in group.Items)
{
if (item is LiteralByte)
{
alphabetSet.Add(((LiteralByte)item).Value);
}
else if (item is LengthDistancePair)
{
LengthDistancePair pair = item as LengthDistancePair;
alphabetSet.Add(DeflateCoding.Length[pair.Length].Code);
distanceSet.Add(DeflateCoding.Distance[pair.Distance].Code);
}
}
alphabetSet.Add(DeflateCoding.EndOfBlock);
if (distanceSet.Count == 0)
{
WriteDefaultCodedBlock(group, output);
}
else
{
HuffmanCodes huffmanCodes = new HuffmanCodes(alphabetSet, distanceSet);
WriteDynamicCodedBlock(group, output, huffmanCodes);
}
}
private static void WriteCopiedBlock(CompressedItemGroup group, BitStream output)
{
throw new Exception("The method or operation is not implemented.");
}
private static void WriteDefaultCodedBlock(CompressedItemGroup group, BitStream output)
{
output.WriteBit(group.IsFinal);
output.WriteBits((int)BlockType.FixedHuffmanCodes, 2);
WriteBlockContent(group, output, HuffmanCodes.Default);
}
private static void WriteDynamicCodedBlock(CompressedItemGroup group, BitStream output, HuffmanCodes huffmanCodes)
{
output.WriteBit(group.IsFinal);
output.WriteBits((int)BlockType.DynamicHuffmanCodes, 2);
huffmanCodes.Encode(output);
WriteBlockContent(group, output, huffmanCodes);
}
private static void WriteBlockContent(CompressedItemGroup group, BitStream output, HuffmanCodes huffmanCodes)
{
foreach (CompressedItem item in group.Items)
{
if (item is LiteralByte)
{
LiteralByte literal = item as LiteralByte;
huffmanCodes.WriteLiteral(output, literal.Value);
}
else if (item is LengthDistancePair)
{
LengthDistancePair pair = item as LengthDistancePair;
huffmanCodes.WriteLength(output, pair.Length);
huffmanCodes.WriteDistance(output, pair.Distance);
}
}
huffmanCodes.WriteLiteral(output, DeflateCoding.EndOfBlock);
}
}
}