//******************************************************************************************************
// CompressionExtensions.cs - Gbtc
//
// Copyright © 2012, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may
// not use this file except in compliance with the License. You may obtain a copy of the License at:
//
// http://www.opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 09/26/2008 - J. Ritchie Carroll
// Generated original version of source code.
// 08/05/2009 - Josh L. Patterson
// Edited Comments.
// 09/14/2009 - Stephen C. Wills
// Added new header and license agreement.
// 12/14/2012 - Starlynn Danyelle Gilliam
// Modified Header.
//
//******************************************************************************************************
using System;
using System.IO;
using System.IO.Compression;
namespace GSF.IO.Compression
{
#region [ Enumerations ]
///
/// Level of compression enumeration.
///
///
/// Compression strengths represent tradeoffs on speed of compression vs. effectiveness of compression.
///
public enum CompressionStrength
{
/// Disables compression.
NoCompression = 0,
/// Enables standard compression.
Standard = 1,
/// Enables multi-pass compression to continue recompressing buffer as long as size continues to shrink.
MultiPass = 2
}
#endregion
///
/// Defines extension functions related to compression.
///
public static class CompressionExtensions
{
///
/// Default compression buffer size.
///
public const int BufferSize = 262144;
///
/// Needed version of this library to uncompress stream (1.0.0 stored as byte 100).
///
public const byte CompressionVersion = 100;
///
/// Compress a byte array using standard compression method.
///
/// The array to compress.
/// A compressed version of the source array.
public static byte[] Compress(this byte[] source)
{
return source.Compress(CompressionStrength.Standard);
}
///
/// Compress a byte array using specified compression method.
///
/// The array to compress.
/// The specified .
/// A compressed version of the source array.
public static byte[] Compress(this byte[] source, CompressionStrength strength)
{
return source.Compress(0, source.Length, strength, 0);
}
///
/// Compress a byte array using specified compression method.
///
/// The array to compress.
/// The number of bytes to read into the byte array for compression.
/// An representing the start index of the byte array.
/// The specified .
/// A compressed version of the source array.
public static byte[] Compress(this byte[] source, int startIndex, int length, CompressionStrength strength)
{
return source.Compress(startIndex, length, strength, 0);
}
// When user requests multi-pass compression, we allow multiple compression passes on a buffer because
// this can often produce better compression results
private static byte[] Compress(this byte[] source, int startIndex, int length, CompressionStrength strength, int compressionDepth)
{
if (strength == CompressionStrength.NoCompression)
{
// No compression requested, return specified portion of the buffer
byte[] outBuffer = new byte[++length];
outBuffer[0] = (byte)strength;
for (int x = 1; x < length; x++)
outBuffer[x] = source[startIndex + x - 1];
return outBuffer;
}
// Create a new compression deflater
using (BlockAllocatedMemoryStream compressedData = new BlockAllocatedMemoryStream())
{
using (DeflateStream deflater = new DeflateStream(compressedData, CompressionMode.Compress, true))
{
// Provide data for compression
deflater.Write(source, startIndex, length);
}
byte[] destination = compressedData.ToArray();
int destinationLength = destination.Length;
// Prepend compression depth and extract only used part of compressed buffer
byte[] outBuffer = new byte[++destinationLength];
// First two bits are reserved for compression strength - this leaves 6 bits for a maximum of 64 compressions
outBuffer[0] = (byte)((compressionDepth << 2) | (int)strength);
for (int x = 1; x < destinationLength; x++)
outBuffer[x] = destination[x - 1];
if (strength == CompressionStrength.MultiPass && destinationLength < length && compressionDepth < 64)
{
// See if another pass would help the compression...
byte[] testBuffer = outBuffer.Compress(0, outBuffer.Length, strength, compressionDepth + 1);
if (testBuffer.Length < outBuffer.Length)
return testBuffer;
return outBuffer;
}
return outBuffer;
}
}
///
/// Compress a stream using specified compression strength.
///
///
/// This returns a memory stream of the compressed results, if the incoming stream is
/// very large this will consume a large amount memory. In this case use the overload
/// that takes the destination stream as a parameter instead.
///
/// The to compress.
/// The of the compression.
/// Returns a of the compressed .
public static MemoryStream Compress(this Stream source, CompressionStrength strength)
{
MemoryStream destination = new MemoryStream();
source.Compress(destination, strength, null);
return destination;
}
///
/// Compress a stream onto given output stream using specified compression strength.
///
/// The to compress.
/// The of the compression.
/// The destination.
/// The progress handler to check progress.
public static void Compress(this Stream source, Stream destination, CompressionStrength strength, Action> progressHandler)
{
ProcessProgressHandler progress = null;
byte[] inBuffer = new byte[BufferSize];
byte[] outBuffer;
byte[] lengthBuffer;
int read;
long total = 0, length = -1;
// Send initial progress event
if ((object)progressHandler != null)
{
try
{
if (source.CanSeek)
length = source.Length;
}
catch
{
length = -1;
}
// Create a new progress handler to track compression progress
progress = new ProcessProgressHandler(progressHandler, "Compress", length);
progress.Complete = 0;
}
// Read initial buffer
read = source.Read(inBuffer, 0, BufferSize);
// Write compression version into stream
byte[] version = new byte[1];
version[0] = CompressionVersion;
destination.Write(version, 0, 1);
while (read > 0)
{
// Compress buffer - note that we are only going to compress used part of buffer,
// we don't want any left over garbage to end up in compressed stream...
outBuffer = inBuffer.Compress(0, read, strength);
// The output stream is hopefully smaller than the input stream, so we prepend the final size of
// each compressed buffer into the destination output stream so that we can safely uncompress
// the stream in a "chunked" fashion later...
lengthBuffer = BitConverter.GetBytes(outBuffer.Length);
destination.Write(lengthBuffer, 0, lengthBuffer.Length);
destination.Write(outBuffer, 0, outBuffer.Length);
// Update compression progress
if ((object)progressHandler != null)
{
total += read;
progress.Complete = total;
}
// Read next buffer
read = source.Read(inBuffer, 0, BufferSize);
}
}
///
/// Decompress a byte array.
///
/// The array to decompress.
/// A decompressed version of the source array.
public static byte[] Decompress(this byte[] source)
{
return source.Decompress(0, source.Length);
}
///
/// Decompress a byte array.
///
/// The array to decompress.
/// The number of bytes to read into the byte array for compression.
/// An representing the start index of the byte array.
/// A decompressed array.
public static byte[] Decompress(this byte[] source, int startIndex, int length)
{
const byte CompressionStrengthMask = (byte)(Bits.Bit00 | Bits.Bit01);
// Unmask compression strength from first buffer byte
CompressionStrength strength = (CompressionStrength)(source[startIndex] & CompressionStrengthMask);
if (strength == CompressionStrength.NoCompression)
{
// No compression was applied to original buffer, return specified portion of the buffer
return source.BlockCopy(startIndex + 1, length - 1);
}
// Create a new decompression deflater
using (BlockAllocatedMemoryStream compressedData = new BlockAllocatedMemoryStream(source, startIndex + 1, length - 1))
{
byte[] destination;
int compressionDepth = (source[startIndex] & ~CompressionStrengthMask) >> 2;
using (DeflateStream inflater = new DeflateStream(compressedData, CompressionMode.Decompress))
{
// Read uncompressed data
destination = inflater.ReadStream();
}
// When user requests multi-pass compression, there may be multiple compression passes on a buffer,
// so we cycle through the needed uncompressions to get back to the original data
if (strength == CompressionStrength.MultiPass && compressionDepth > 0)
return destination.Decompress();
return destination;
}
}
///
/// Decompress a stream.
///
///
/// This returns a memory stream of the uncompressed results, if the incoming stream is
/// very large this will consume a large amount memory. In this case use the overload
/// that takes the destination stream as a parameter instead.
///
/// A source to decompress.
/// A representing the decompressed source.
public static MemoryStream Decompress(this Stream source)
{
MemoryStream destination = new MemoryStream();
source.Decompress(destination, null);
return destination;
}
///
/// Decompress a stream onto given output stream.
///
/// A source to decompress.
/// The destination to decompress to.
/// A handler to monitor the action's progress.
public static void Decompress(this Stream source, Stream destination, Action> progressHandler)
{
ProcessProgressHandler progress = null;
byte[] inBuffer;
byte[] outBuffer;
byte[] lengthBuffer = BitConverter.GetBytes((int)0);
int read, size;
long total = 0, length = -1;
// Send initial progress event
if ((object)progressHandler != null)
{
try
{
if (source.CanSeek)
length = source.Length;
}
catch
{
length = -1;
}
// Create a new progress handler to track compression progress
progress = new ProcessProgressHandler(progressHandler, "Uncompress", length);
progress.Complete = 0;
}
// Read compression version from stream
byte[] versionBuffer = new byte[1];
if (source.Read(versionBuffer, 0, 1) > 0)
{
if (versionBuffer[0] != CompressionVersion)
throw new InvalidOperationException("Invalid compression version encountered in compressed stream - decompression aborted");
// Read initial buffer
read = source.Read(lengthBuffer, 0, lengthBuffer.Length);
while (read > 0)
{
// Convert the byte array containing the buffer size into an integer
size = BitConverter.ToInt32(lengthBuffer, 0);
if (size > 0)
{
// Create and read the next buffer
inBuffer = new byte[size];
read = source.Read(inBuffer, 0, size);
if (read > 0)
{
// Uncompress buffer
outBuffer = inBuffer.Decompress();
destination.Write(outBuffer, 0, outBuffer.Length);
}
// Update decompression progress
if ((object)progressHandler != null)
{
total += (read + lengthBuffer.Length);
progress.Complete = total;
}
}
// Read the size of the next buffer from the stream
read = source.Read(lengthBuffer, 0, lengthBuffer.Length);
}
}
}
}
}