|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MS.Internal.Ink.InkSerializedFormat
{
internal class AlgoModule
{
/// <summary>
/// Ctor
/// </summary>
internal AlgoModule()
{
}
/// <summary>
/// Based on the given input, finds the best compression to use on it.
/// </summary>
/// <param name="input">assumed to be point data (x,x,x,x,x,x,x)</param>
/// <returns></returns>
internal byte GetBestDefHuff(int[] input)
{
if (input.Length < 3)
{
return NoCompression;
}
DeltaDelta xfDelDel = new DeltaDelta();
int xfData = 0;
int exData = 0;
// Perform delta delta 2 times to set up the internal state of
// delta delta transform
xfDelDel.Transform(input[0], ref xfData, ref exData);
xfDelDel.Transform(input[1], ref xfData, ref exData);
double sumSq = 0.0;
// Compute the variance of the delta delta
uint n = 2;
for(; n < input.Length; n++)
{
xfDelDel.Transform(input[n], ref xfData, ref exData);
if (0 == exData)
{
sumSq += ((double)xfData * (double)xfData);
}
}
sumSq *= (0.205625 / (n - 1.0));
int i = DefaultFirstSquareRoot.Length - 2;
for(; i > 1; i--)
{
if(sumSq > DefaultFirstSquareRoot[i])
{
break;
}
}
byte retVal = (byte)(IndexedHuffman | (byte)(i + 1));
return retVal;
}
/// <summary>
/// Compresses int[] packet data, returns it as a byte[]
/// </summary>
/// <param name="input">assumed to be point data (x,x,x,x,x,x,x)</param>
/// <param name="compression">magic byte specifying the compression to use</param>
/// <returns></returns>
internal byte[] CompressPacketData(int[] input, byte compression)
{
ArgumentNullException.ThrowIfNull(input);
List<byte> compressedData = new List<byte>();
//leave room at the beginning of
//compressedData for the compression header byte
//which we will add at the end
compressedData.Add((byte)0);
if (DefaultCompression == (DefaultCompression & compression))
{
compression = GetBestDefHuff(input);
}
if (IndexedHuffman == (DefaultCompression & compression))
{
DataXform dtxf = this.HuffModule.FindDtXf(compression);
HuffCodec huffCodec = this.HuffModule.FindCodec(compression);
huffCodec.Compress(dtxf, input, compressedData);
if (((compressedData.Count - 1/*for the algo byte we just made room for*/) >> 2) > input.Length)
{
//recompress with no compression (gorilla)
compression = NoCompression;
//reset
compressedData.Clear();
compressedData.Add((byte)0);
}
}
if (NoCompression == (DefaultCompression & compression))
{
bool testDelDel = ((compression & 0x20) != 0);
compression =
this.GorillaCodec.FindPacketAlgoByte(input, testDelDel);
DeltaDelta dtxf = null;
if ((compression & 0x20) != 0)
{
dtxf = this.DeltaDelta;
}
int inputIndex = 0;
if (null != dtxf)
{
//multibyteencode the first two values
int xfData = 0;
int xfExtra = 0;
dtxf.ResetState();
dtxf.Transform(input[0], ref xfData, ref xfExtra);
this.MultiByteCodec.SignEncode(xfData, compressedData);
dtxf.Transform(input[1], ref xfData, ref xfExtra);
this.MultiByteCodec.SignEncode(xfData, compressedData);
//advance to the third member, we've already read the first two
inputIndex = 2;
}
//Gorllia time
int bitCount = (compression & 0x1F);
this.GorillaCodec.Compress( bitCount, //the max count of bits required for each int
input, //the input array to compress
inputIndex, //the index to start compressing at
dtxf, //data transform to use when compressing, can be null
compressedData); //a ref to the compressed data that will be written to
}
// compression / algo data always goes in index 0
compressedData[0] = compression;
return compressedData.ToArray();
}
/// <summary>
/// DecompressPacketData - given a compressed byte[], uncompress it to the outputBuffer
/// </summary>
/// <param name="input">compressed byte from the ISF stream</param>
/// <param name="outputBuffer">prealloc'd buffer to write to</param>
/// <returns></returns>
internal uint DecompressPacketData(byte[] input, int[] outputBuffer)
{
ArgumentNullException.ThrowIfNull(input);
if (input.Length < 2)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Input buffer passed was shorter than expected"));
}
ArgumentNullException.ThrowIfNull(outputBuffer);
if (outputBuffer.Length == 0)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("output buffer length was zero"));
}
byte compression = input[0];
uint totalBytesRead = 1; //we just read one
int inputIndex = 1;
switch (compression & 0xC0)
{
case 0x80://IndexedHuffman
{
DataXform dtxf = this.HuffModule.FindDtXf(compression);
HuffCodec huffCodec = this.HuffModule.FindCodec(compression);
totalBytesRead += huffCodec.Uncompress(dtxf, input, inputIndex, outputBuffer);
return totalBytesRead;
}
case 0x00: //NoCompression
{
int outputBufferIndex = 0;
DeltaDelta dtxf = null;
if ((compression & 0x20) != 0)
{
dtxf = this.DeltaDelta;
}
int bitCount = 0;
if ((compression & 0x1F) == 0)
{
bitCount = Native.BitsPerInt;//32
}
else
{
bitCount = (compression & 0x1F);
}
if (null != dtxf)
{
//must have at least two more bytes besides the
//initial algo byte
if (input.Length < 3)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Input buffer was too short (must be at least 3 bytes)"));
}
//multibyteencode the first two values
int xfData = 0;
int xfExtra = 0;
dtxf.ResetState();
uint bytesRead =
this.MultiByteCodec.SignDecode(input, inputIndex, ref xfData);
//advance our index
inputIndex += (int)bytesRead;
totalBytesRead += bytesRead;
int result = dtxf.InverseTransform(xfData, xfExtra);
Debug.Assert(outputBufferIndex < outputBuffer.Length);
outputBuffer[outputBufferIndex++] = result;
bytesRead =
this.MultiByteCodec.SignDecode(input, inputIndex, ref xfData);
//advance our index
inputIndex += (int)bytesRead;
totalBytesRead += bytesRead;
result = dtxf.InverseTransform(xfData, xfExtra);
Debug.Assert(outputBufferIndex < outputBuffer.Length);
outputBuffer[outputBufferIndex++] = result;
}
totalBytesRead +=
this.GorillaCodec.Uncompress( bitCount, //the max count of bits required for each int
input, //the input array to uncompress
inputIndex, //the index to start uncompressing at
dtxf, //data transform to use when compressing, can be null
outputBuffer,//a ref to the output buffer to write to
outputBufferIndex); //the index of the output buffer to write to
return totalBytesRead;
}
default:
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid decompression algo byte"));
}
}
}
/// <summary>
/// Compresses property data which is already in the form of a byte[]
/// into a compressed byte[]
/// </summary>
/// <param name="input">byte[] data ready to be compressed</param>
/// <param name="compression">the compression to use</param>
/// <returns></returns>
internal byte[] CompressPropertyData(byte[] input, byte compression)
{
List<byte> compressedData = new List<byte>(input.Length + 1); //reasonable default based on profiling.
//leave room at the beginning of
//compressedData for the compression header byte
compressedData.Add((byte)0);
if (DefaultCompression == (DefaultCompression & compression))
{
compression = this.GorillaCodec.FindPropAlgoByte(input);
}
//validate that we never lzencode
if (LempelZiv == (compression & LempelZiv))
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("Invalid compression specified or computed by FindPropAlgoByte"));
}
//determine what the optimal way to compress the data is. Should we treat
//the byte[] as a series of Int's, Short's or Byte's?
int countPerItem = 0, bitCount = 0, padCount = 0;
this.GorillaCodec.GetPropertyBitCount(compression, ref countPerItem, ref bitCount, ref padCount);
Debug.Assert(countPerItem == 4 || countPerItem == 2 || countPerItem == 1);
GorillaEncodingType type = GorillaEncodingType.Byte;
int unitCount = input.Length;
if (countPerItem == 4)
{
type = GorillaEncodingType.Int;
unitCount >>= 2;
}
else if (countPerItem == 2)
{
type = GorillaEncodingType.Short;
unitCount >>= 1;
}
BitStreamReader reader = new BitStreamReader(input);
//encode, gorilla style
this.GorillaCodec.Compress(bitCount, //the max count of bits required for each int
reader, //the reader, which can read int, byte, short
type, //informs how the reader reads
unitCount, //just how many items do we need to compress?
compressedData); //a ref to the compressed data that will be written to
compressedData[0] = compression;
return compressedData.ToArray();
}
/// <summary>
/// Decompresses property data (from a compressed byte[] to an uncompressed byte[])
/// </summary>
/// <param name="input">The byte[] to decompress</param>
/// <returns></returns>
internal byte[] DecompressPropertyData(byte[] input)
{
ArgumentNullException.ThrowIfNull(input);
if (input.Length < 2)
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("input.Length must be at least 2"));
}
byte compression = input[0];
int inputIndex = 1;
if (LempelZiv == (compression & LempelZiv))
{
if (0 != (compression & (~LempelZiv)))
{
throw new ArgumentException(StrokeCollectionSerializer.ISFDebugMessage("bogus isf, we don't decompress property data with lz"));
}
return this.LZCodec.Uncompress(input, inputIndex);
}
else
{
//gorilla
//determine what the way to uncompress the data. Should we treat
//the byte[] as a series of Int's, Short's or Byte's?
int countPerItem = 0, bitCount = 0, padCount = 0;
this.GorillaCodec.GetPropertyBitCount(compression, ref countPerItem, ref bitCount, ref padCount);
Debug.Assert(countPerItem == 4 || countPerItem == 2 || countPerItem == 1);
GorillaEncodingType type = GorillaEncodingType.Byte;
if (countPerItem == 4)
{
type = GorillaEncodingType.Int;
}
else if (countPerItem == 2)
{
type = GorillaEncodingType.Short;
}
//determine how many units (of int, short or byte) that there are to decompress
int unitsToDecode = ((input.Length - inputIndex << 3) / bitCount) - padCount;
BitStreamReader reader = new BitStreamReader(input, inputIndex);
return this.GorillaCodec.Uncompress(bitCount, reader, type, unitsToDecode);
}
}
/// <summary>
/// Private lazy init'd member
/// </summary>
private HuffModule HuffModule
{
get
{
if (_huffModule == null)
{
_huffModule = new HuffModule();
}
return _huffModule;
}
}
/// <summary>
/// Private lazy init'd member
/// </summary>
private MultiByteCodec MultiByteCodec
{
get
{
if (_multiByteCodec == null)
{
_multiByteCodec = new MultiByteCodec();
}
return _multiByteCodec;
}
}
/// <summary>
/// Private lazy init'd member
/// </summary>
private DeltaDelta DeltaDelta
{
get
{
if (_deltaDelta == null)
{
_deltaDelta = new DeltaDelta();
}
return _deltaDelta;
}
}
/// <summary>
/// Private lazy init'd member
/// </summary>
private GorillaCodec GorillaCodec
{
get
{
if (_gorillaCodec == null)
{
_gorillaCodec = new GorillaCodec();
}
return _gorillaCodec;
}
}
/// <summary>
/// Private lazy init'd member
/// </summary>
private LZCodec LZCodec
{
get
{
if (_lzCodec == null)
{
_lzCodec = new LZCodec();
}
return _lzCodec;
}
}
/// <summary>
/// Privates, lazy initialized, do not reference directly
/// </summary>
private HuffModule _huffModule;
private MultiByteCodec _multiByteCodec;
private DeltaDelta _deltaDelta;
private GorillaCodec _gorillaCodec;
private LZCodec _lzCodec;
/// <summary>
/// Static members defined in Penimc code
/// </summary>
internal const byte NoCompression = 0x00;
internal const byte DefaultCompression = 0xC0;
internal const byte IndexedHuffman = 0x80;
internal const byte LempelZiv = 0x80;
internal const byte DefaultBAACount = 8;
internal const byte MaxBAACount = 10;
private static ReadOnlySpan<double> DefaultFirstSquareRoot => [1, 1, 1, 4, 9, 16, 36, 49];
}
}
|