/// <summary> /// Stores meta data (i.e. data needed to decompress and test the file) for the compressed <paramref name="filePart" /> /// . /// </summary> /// <param name="filePart">The compressed file which meta data should be stored.</param> /// <param name="frequencyTable">The frequency table needed to restore the Huffman tree (for file decoding). </param> /// <param name="uncompressedHashSum">The hashsum needed to test file correctness after unpacking.</param> /// <param name="compressedHashSum">The hashsum needed to test compressed file correctness before unpacking.</param> /// <param name="ratio">The compression ratio shown to the user.</param> /// <param name="uncompressed">Size of the file before compression.</param> /// <param name="compressed">Size of the compressed file.</param> private void StoreMetaData(PackagePart filePart, FrequencyTable frequencyTable, byte[] uncompressedHashSum, byte[] compressedHashSum, double ratio, long uncompressed, long compressed, string name) { /* collect metadate into a class */ var meta = new FileMetaData { frequencyTable = frequencyTable, compressedHash = compressedHashSum, uncompressedHash = uncompressedHashSum, compressionRatio = ratio, uncompressedSize = uncompressed, compressedSize = compressed, filename = name }; /* create path (relating to the archive root) where the meta data will be stored */ var metaUri = PackUriHelper.CreatePartUri(new Uri(filePart.Uri + "META", UriKind.Relative)); /* create a package part in the path */ var metaPart = _archive.CreatePart(metaUri, "", CompressionOption.NotCompressed); filePart.CreateRelationship(metaUri, TargetMode.Internal, ".\\META"); using (var metaStream = new BufferedStream(metaPart.GetStream(), BufferSize)) { var formatter = new BinaryFormatter(); formatter.Serialize(metaStream, meta); } }
/// <summary> /// Compresses the file stored in the <paramref name="path" /> and stores compressed file it the /// <paramref name="filePart" />. /// </summary> /// <param name="path">The path to the file to be compressed.</param> /// <param name="filePart">The archive part to store the compressed file in.</param> /// <param name="frequencyTable">The frequency table to be saved to the archive.</param> private void CompressFile(string path, PackagePart filePart, out FrequencyTable frequencyTable) { /* open the file */ var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize); /* find frequencies of bytes in the file */ OnStateChange("counting up frequencies of bytes"); frequencyTable = new FrequencyTable(fileStream); frequencyTable.SubscribeToUpdates(OnProgressChange); frequencyTable.CountFrequencies(); /* rewind the position of the stream to use this stream once again */ fileStream.Position = 0; /* build the Huffman tree based on the counted frequencies */ OnStateChange("building the Huffman tree"); var huffmanTree = new HuffmanTree(frequencyTable); /* get the Huffman code from the tree */ OnStateChange("retrieving the Huffman code"); var encodingTable = new EncodingTable(huffmanTree); /* get the file part's stream */ var filePartStream = new BufferedStream(filePart.GetStream(), BufferSize); /* encode the file */ OnStateChange("compressing the file"); var encoder = new Encoder(fileStream, filePartStream, encodingTable); encoder.SubscribeToUpdates(OnProgressChange); encoder.Encode(); /* dispose unused streams */ fileStream.Close(); filePartStream.Close(); }
/// <summary> /// Initializes a new instance of the decoder. /// </summary> /// <param name="frequencyTable">The frequency table to build the Huffman tree from.</param> /// <param name="encoodedBytesCount">The number of bytes that should be decoded and written.</param> public Decoder(Stream input, Stream output, FrequencyTable frequencyTable, long encoodedBytesCount) { InputStream = input; OutputStream = output; Tree = new HuffmanTree(frequencyTable); BytesCount = encoodedBytesCount; }
/// <summary> /// Packs non-zero frequency bytes to Huffman tree leaves and joins it to corresponding bytes' frequencies. /// </summary> /// <param name="freq">The frequency table to get nodes from.</param> /// <returns>Weighted nodes containing Huffman tree leaves and their weights.</returns> private static WeightedNode[] PackNodes(FrequencyTable freq) { var weightedNodes = new List <WeightedNode>(); for (int @byte = byte.MinValue; @byte <= byte.MaxValue; @byte++) { /* for each byte in the frequency table */ if (freq[@byte] != 0) { /* if the byte occurs in the stream at least once */ weightedNodes.Add(new WeightedNode { node = new Leaf(@byte), weight = freq[@byte] }); } } return(weightedNodes.ToArray()); }
public static Node GenerateHuffmanTree(FrequencyTable frequencyTable, out int height) { //TODO enhance this code (assuming that both arrays have the same lenght) /* check frequency table */ if (frequencyTable == null) { throw new ArgumentNullException(nameof(frequencyTable), "The provided frequency table is null."); } /* set initial height */ height = 0; /* the array contaning leaves of a tree */ var leaves = PackNodes(frequencyTable); Array.Sort(leaves); if (leaves.Length == 1) { return(leaves[0].node); height = 1; } if (leaves.Length == 0) { return(null); } /* the array containing internal nodes of a tree */ var internalNodes = new WeightedNode[leaves.Length - 1]; // (because Huffman tree is a full binary tree the array containg leaves-1 elements) /* fill internailNodes with null values (null means node of infinite weight) */ FillWithNull(ref internalNodes); /* the index of the current element of the leaves array */ var leavesIndex = 0; /* the index of the current element of the internalNodes array */ var internalNodesIndex = 0; var internalNodesEnd = 0; /* current sum value */ long currentSum; /* current (withing a loop iteration) min sum value */ long minSum; /* special marker variable for determining the exact two elements of the min sum */ var status = ""; for (var i = 0; i <= internalNodes.Length - 1; i++) /* while */ { height += 1; minSum = long.MaxValue; if (leavesIndex + 1 < leaves.Length) /* if two sequential elements (relating to leavesIndex) of leaves array exist */ { /* check if their sum is less than the min sum (and set it as the new min sum if so) */ currentSum = leaves[leavesIndex].weight + leaves[leavesIndex + 1].weight; if (currentSum < minSum) { minSum = currentSum; status = "leaves + leaves"; } } if (leavesIndex < leaves.Length && internalNodes[internalNodesIndex] != null) /* if current internal node's weight is not equal to infinity */ { /* check if sum of current leaf and internal node is less than the min sum (and set it as the new min sum if so) */ currentSum = leaves[leavesIndex].weight + internalNodes[internalNodesIndex].weight; if (currentSum < minSum) { minSum = currentSum; status = "leaves + internalNodes"; } } if (internalNodes[internalNodesIndex] != null && internalNodesIndex + 1 < internalNodes.Length) { /* if two sequential elements (relating to internalNodesIndex) of internalNodes array exist */ if (internalNodes[internalNodesIndex + 1] != null) { /* check if sum of current leaf and internal node is less than the min sum (and set it as the new min sum if so) */ currentSum = internalNodes[internalNodesIndex].weight + internalNodes[internalNodesIndex + 1].weight; if (currentSum < minSum) { minSum = currentSum; status = "internalNodes + internalNodes"; } } } WeightedNode w1, w2; switch (status) { case "leaves + leaves": w1 = leaves[leavesIndex]; w2 = leaves[leavesIndex + 1]; internalNodes[internalNodesEnd++] = new WeightedNode { node = new InternalNode(w1.node, w2.node), weight = w1.weight + w2.weight }; leavesIndex += 2; break; case "leaves + internalNodes": w1 = leaves[leavesIndex]; w2 = internalNodes[internalNodesIndex]; internalNodes[internalNodesEnd++] = new WeightedNode { node = new InternalNode(w1.node, w2.node), weight = w1.weight + w2.weight }; leavesIndex += 1; internalNodesIndex += 1; break; case "internalNodes + internalNodes": w1 = internalNodes[internalNodesIndex]; w2 = internalNodes[internalNodesIndex + 1]; internalNodes[internalNodesEnd++] = new WeightedNode { node = new InternalNode(w1.node, w2.node), weight = w1.weight + w2.weight }; internalNodesIndex += 2; break; default: throw new NotImplementedException(); } } return(internalNodes[internalNodes.Length - 1].node); }