Adding arithmetic codec

This commit is contained in:
Marc Hernandez 2020-12-28 15:27:49 -08:00 committed by Marc Hernandez
parent 2844950af3
commit 7560e6bf56
18 changed files with 2058 additions and 4 deletions

View File

@ -0,0 +1,73 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using adaptive arithmetic coding.
/// <para>Usage: java AdaptiveArithmeticCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "AdaptiveArithmeticDecompress" application to recreate the original input file.</para>
/// <para>Note that the application starts with a flat frequency table of 257 symbols (all set to a frequency of 1),
/// and updates it after each byte encoded. The corresponding decompressor program also starts with a flat
/// frequency table and updates it after each byte decoded. It is by design that the compressor and
/// decompressor have synchronized states, so that the data can be decompressed properly.</para>
/// </summary>
public class AdaptiveArithmeticCompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java AdaptiveArithmeticCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file compression
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
compress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress(Stream @in, BitOutputStream @out)
{
FlatFrequencyTable initFreqs = new FlatFrequencyTable(257);
FrequencyTable freqs = new SimpleFrequencyTable(initFreqs);
ArithmeticEncoder enc = new ArithmeticEncoder(32, @out);
while (true)
{
// Read and encode one byte
int symbol = @in.ReadByte();
if (symbol == -1)
{
break;
}
enc.write(freqs, symbol);
freqs.increment(symbol);
}
enc.write(freqs, 256); // EOF
enc.finish(); // Flush remaining code bits
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using adaptive arithmetic coding.
/// <para>Usage: java AdaptiveArithmeticDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "AdaptiveArithmeticCompress" application.</para>
/// </summary>
public class AdaptiveArithmeticDecompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java AdaptiveArithmeticDecompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file decompression
using (BitInputStream @in = new BitInputStream(new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read))), Stream @out = new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write)))
{
decompress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress(BitInputStream @in, Stream @out)
{
FlatFrequencyTable initFreqs = new FlatFrequencyTable(257);
FrequencyTable freqs = new SimpleFrequencyTable(initFreqs);
ArithmeticDecoder dec = new ArithmeticDecoder(32, @in);
while (true)
{
// Decode and write one byte
int symbol = dec.read(freqs);
if (symbol == 256) // EOF symbol
{
break;
}
@out.WriteByte((byte)symbol);
freqs.increment(symbol);
}
}
}

185
ar/ArithmeticCoderBase.cs Normal file
View File

@ -0,0 +1,185 @@
using System;
using System.Diagnostics;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Provides the state and behaviors that arithmetic coding encoders and decoders share. </summary>
/// <seealso cref= ArithmeticEncoder </seealso>
/// <seealso cref= ArithmeticDecoder </seealso>
public abstract class ArithmeticCoderBase
{
/*---- Configuration fields ----*/
/// <summary>
/// Number of bits for the 'low' and 'high' state variables. Must be in the range [1, 62].
/// <ul>
/// <li>For state sizes less than the midpoint of around 32, larger values are generally better -
/// they allow a larger maximum frequency total (maximumTotal), and they reduce the approximation
/// error inherent in adapting fractions to integers; both effects reduce the data encoding loss
/// and asymptotically approach the efficiency of arithmetic coding using exact fractions.</li>
/// <li>But for state sizes greater than the midpoint, because intermediate computations are limited
/// to the long integer type's 63-bit unsigned precision, larger state sizes will decrease the
/// maximum frequency total, which might constrain the user-supplied probability model.</li>
/// <li>Therefore numStateBits=32 is recommended as the most versatile setting
/// because it maximizes maximumTotal (which ends up being slightly over 2^30).</li>
/// <li>Note that numStateBits=62 is legal but useless because it implies maximumTotal=1,
/// which means a frequency table can only support one symbol with non-zero frequency.</li>
/// </ul>
/// </summary>
protected internal readonly int numStateBits;
/// <summary>
/// Maximum range (high+1-low) during coding (trivial), which is 2^numStateBits = 1000...000. </summary>
protected internal readonly long fullRange;
/// <summary>
/// The top bit at width numStateBits, which is 0100...000. </summary>
protected internal readonly long halfRange;
/// <summary>
/// The second highest bit at width numStateBits, which is 0010...000. This is zero when numStateBits=1. </summary>
protected internal readonly long quarterRange;
/// <summary>
/// Minimum range (high+1-low) during coding (non-trivial), which is 0010...010. </summary>
protected internal readonly long minimumRange;
/// <summary>
/// Maximum allowed total from a frequency table at all times during coding. </summary>
protected internal readonly long maximumTotal;
/// <summary>
/// Bit mask of numStateBits ones, which is 0111...111. </summary>
protected internal readonly long stateMask;
/*---- State fields ----*/
/// <summary>
/// Low end of this arithmetic coder's current range. Conceptually has an infinite number of trailing 0s.
/// </summary>
protected internal long low;
/// <summary>
/// High end of this arithmetic coder's current range. Conceptually has an infinite number of trailing 1s.
/// </summary>
protected internal long high;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coder, which initializes the code range. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
public ArithmeticCoderBase(int numBits)
{
if (numBits < 1 || numBits > 62)
{
throw new System.ArgumentException("State size out of range");
}
numStateBits = numBits;
fullRange = 1L << numStateBits;
halfRange = (long)((ulong)fullRange >> 1); // Non-zero
quarterRange = (long)((ulong)halfRange >> 1); // Can be zero
minimumRange = quarterRange + 2; // At least 2
maximumTotal = Math.Min(long.MaxValue / fullRange, minimumRange);
stateMask = fullRange - 1;
low = 0;
high = stateMask;
}
/*---- Methods ----*/
/// <summary>
/// Updates the code range (low and high) of this arithmetic coder as a result
/// of processing the specified symbol with the specified frequency table.
/// <para>Invariants that are true before and after encoding/decoding each symbol
/// (letting fullRange = 2<sup>numStateBits</sup>):</para>
/// <ul>
/// <li>0 &le; low &le; code &le; high &lt; fullRange. ('code' exists only in the decoder.)
/// Therefore these variables are unsigned integers of numStateBits bits.</li>
/// <li>low &lt; 1/2 &times; fullRange &le; high.
/// In other words, they are in different halves of the full range.</li>
/// <li>(low &lt; 1/4 &times; fullRange) || (high &ge; 3/4 &times; fullRange).
/// In other words, they are not both in the middle two quarters.</li>
/// <li>Let range = high &minus; low + 1, then fullRange/4 &lt; minimumRange &le; range &le;
/// fullRange. These invariants for 'range' essentially dictate the maximum total that the
/// incoming frequency table can have, such that intermediate calculations don't overflow.</li>
/// </ul> </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol that was processed </param>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency or the frequency table's total is too large </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void update(CheckedFrequencyTable freqs, int symbol) throws java.io.IOException
protected internal virtual void update(CheckedFrequencyTable freqs, int symbol)
{
// State check
Debug.Assert(low >= high || (low & stateMask) != low || (high & stateMask) != high, "Low or high out of range");
long range = high - low + 1;
Debug.Assert(range < minimumRange || range > fullRange, "Range out of range");
// Frequency table values check
long total = freqs.Total;
long symLow = freqs.getLow(symbol);
long symHigh = freqs.getHigh(symbol);
Debug.Assert( symLow == symHigh, "Symbol has zero frequency");
Debug.Assert( total > maximumTotal, "Cannot code symbol because total is too large");
// Update range
long newLow = low + symLow * range / total;
long newHigh = low + symHigh * range / total - 1;
low = newLow;
high = newHigh;
// While low and high have the same top bit value, shift them out
while (((low ^ high) & halfRange) == 0)
{
shift();
low = ((low << 1) & stateMask);
high = ((high << 1) & stateMask) | 1;
}
// Now low's top bit must be 0 and high's top bit must be 1
// While low's top two bits are 01 and high's are 10, delete the second highest bit of both
while ((low & ~high & quarterRange) != 0)
{
underflow();
low = (low << 1) ^ halfRange;
high = ((high ^ halfRange) << 1) | halfRange | 1;
}
}
/// <summary>
/// Called to handle the situation when the top bit of {@code low} and {@code high} are equal. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected abstract void shift() throws java.io.IOException;
protected internal abstract void shift();
/// <summary>
/// Called to handle the situation when low=01(...) and high=10(...). </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected abstract void underflow() throws java.io.IOException;
protected internal abstract void underflow();
}

127
ar/ArithmeticCompress.cs Normal file
View File

@ -0,0 +1,127 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using static arithmetic coding.
/// <para>Usage: java ArithmeticCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "ArithmeticDecompress" application to recreate the original input file.</para>
/// <para>Note that the application uses an alphabet of 257 symbols - 256 symbols for the byte
/// values and 1 symbol for the EOF marker. The compressed file format starts with a list
/// of 256 symbol frequencies, and then followed by the arithmetic-coded data.</para>
/// </summary>
public class ArithmeticCompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
/* @@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java ArithmeticCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Read input file once to compute symbol frequencies
FrequencyTable freqs = getFrequencies(inputFile);
freqs.increment(256); // EOF symbol gets a frequency of 1
// Read input file again, compress with arithmetic coding, and write output file
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
writeFrequencies(@out, freqs);
compress(freqs, @in, @out);
}
*/
}
// Returns a frequency table based on the bytes in the given file.
// Also contains an extra entry for symbol 256, whose frequency is set to 0.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static FrequencyTable getFrequencies(java.io.File file) throws java.io.IOException
private static FrequencyTable getFrequencies(string file)
{
FrequencyTable freqs = new SimpleFrequencyTable(new int[257]);
using (Stream input = new BufferedStream( new FileStream(file, FileMode.Open, FileAccess.Read)))
{
while (true)
{
int b = input.ReadByte();
if (b == -1)
{
break;
}
freqs.increment(b);
}
}
return freqs;
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void writeFrequencies(BitOutputStream out, FrequencyTable freqs) throws java.io.IOException
internal static void writeFrequencies(BitOutputStream @out, FrequencyTable freqs)
{
for (int i = 0; i < 256; i++)
{
writeInt(@out, 32, freqs.get(i));
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(FrequencyTable freqs, java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress(FrequencyTable freqs, Stream @in, BitOutputStream @out)
{
ArithmeticEncoder enc = new ArithmeticEncoder(32, @out);
while (true)
{
int symbol = @in.ReadByte();
if (symbol == -1)
{
break;
}
enc.write(freqs, symbol);
}
enc.write(freqs, 256); // EOF
enc.finish(); // Flush remaining code bits
}
// Writes an unsigned integer of the given bit width to the given stream.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static void writeInt(BitOutputStream out, int numBits, int value) throws java.io.IOException
private static void writeInt(BitOutputStream @out, int numBits, int value)
{
if (numBits < 0 || numBits > 32)
{
throw new System.ArgumentException();
}
for (int i = numBits - 1; i >= 0; i--)
{
@out.write(((int)((uint)value >> i)) & 1); // Big endian
}
}
}

152
ar/ArithmeticDecoder.cs Normal file
View File

@ -0,0 +1,152 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System.Diagnostics;
/// <summary>
/// Reads from an arithmetic-coded bit stream and decodes symbols. Not thread-safe. </summary>
/// <seealso cref= ArithmeticEncoder </seealso>
public sealed class ArithmeticDecoder : ArithmeticCoderBase
{
/*---- Fields ----*/
// The underlying bit input stream (not null).
private BitInputStream input;
// The current raw code bits being buffered, which is always in the range [low, high].
private long code;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coding decoder based on the
/// specified bit input stream, and fills the code bits. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <param name="in"> the bit input stream to read from </param>
/// <exception cref="NullPointerException"> if the input steam is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public ArithmeticDecoder(int numBits, BitInputStream in) throws java.io.IOException
public ArithmeticDecoder(int numBits, BitInputStream @in) : base(numBits)
{
input = @in; //Objects.requireNonNull(@in);
code = 0;
for (int i = 0; i < numStateBits; i++)
{
code = code << 1 | readCodeBit();
}
}
/*---- Methods ----*/
/// <summary>
/// Decodes the next symbol based on the specified frequency table and returns it.
/// Also updates this arithmetic coder's state and may read in some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <returns> the next symbol </returns>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read(FrequencyTable freqs) throws java.io.IOException
public int read(FrequencyTable freqs)
{
return read(new CheckedFrequencyTable(freqs));
}
/// <summary>
/// Decodes the next symbol based on the specified frequency table and returns it.
/// Also updates this arithmetic coder's state and may read in some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <returns> the next symbol </returns>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read(CheckedFrequencyTable freqs) throws java.io.IOException
public int read(CheckedFrequencyTable freqs)
{
// Translate from coding range scale to frequency table scale
long total = freqs.Total;
if (total > maximumTotal)
{
throw new System.ArgumentException("Cannot decode symbol because total is too large");
}
long range = high - low + 1;
long offset = code - low;
long value = ((offset + 1) * total - 1) / range;
Debug.Assert(value * range / total > offset);
Debug.Assert(value < 0 || value >= total);
// A kind of binary search. Find highest symbol such that freqs.getLow(symbol) <= value.
int start = 0;
int end = freqs.SymbolLimit;
while (end - start > 1)
{
int middle = (int)((uint)(start + end) >> 1);
if (freqs.getLow(middle) > value)
{
end = middle;
}
else
{
start = middle;
}
}
Debug.Assert( start + 1 != end);
int symbol = start;
Debug.Assert(offset < freqs.getLow(symbol) * range / total || freqs.getHigh(symbol) * range / total <= offset);
update(freqs, symbol);
Debug.Assert(code < low || code > high);
return symbol;
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void shift() throws java.io.IOException
protected internal override void shift()
{
code = ((code << 1) & stateMask) | readCodeBit();
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void underflow() throws java.io.IOException
protected internal override void underflow()
{
code = (code & halfRange) | ((code << 1) & ((long)((ulong)stateMask >> 1))) | readCodeBit();
}
// Returns the next bit (0 or 1) from the input stream. The end
// of stream is treated as an infinite number of trailing zeros.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private int readCodeBit() throws java.io.IOException
private int readCodeBit()
{
int temp = input.read();
if (temp == -1)
{
temp = 0;
}
return temp;
}
}

View File

@ -0,0 +1,99 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using static arithmetic coding.
/// <para>Usage: java ArithmeticDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "ArithmeticCompress" application.</para>
/// </summary>
public class ArithmeticDecompress
{
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java ArithmeticDecompress InputFile OutputFile");
Environment.Exit(1);
return;
}
string inputFile = args[0]; // new File(args[0]);
string outputFile = args[1]; //new File(args[1]);
// Perform file decompression
using( BitInputStream @in = new BitInputStream(new BufferedStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read))))
using( Stream @out = new BufferedStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write) ) )
{
FrequencyTable freqs = readFrequencies(@in);
decompress( freqs, @in, @out );
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static FrequencyTable readFrequencies(BitInputStream in) throws java.io.IOException
internal static FrequencyTable readFrequencies(BitInputStream @in)
{
int[] freqs = new int[257];
for (int i = 0; i < 256; i++)
{
freqs[i] = readInt(@in, 32);
}
freqs[256] = 1; // EOF symbol
return new SimpleFrequencyTable(freqs);
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(FrequencyTable freqs, BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress(FrequencyTable freqs, BitInputStream @in, Stream @out)
{
ArithmeticDecoder dec = new ArithmeticDecoder(32, @in);
while (true)
{
int symbol = dec.read(freqs);
if (symbol == 256) // EOF symbol
{
break;
}
@out.WriteByte((byte)symbol);
}
}
// Reads an unsigned integer of the given bit width from the given stream.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static int readInt(BitInputStream in, int numBits) throws java.io.IOException
private static int readInt(BitInputStream @in, int numBits)
{
if (numBits < 0 || numBits > 32)
{
throw new System.ArgumentException();
}
int result = 0;
for (int i = 0; i < numBits; i++)
{
result = (result << 1) | @in.readNoEof(); // Big endian
}
return result;
}
}

118
ar/ArithmeticEncoder.cs Normal file
View File

@ -0,0 +1,118 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System;
/// <summary>
/// Encodes symbols and writes to an arithmetic-coded bit stream. Not thread-safe. </summary>
/// <seealso cref= ArithmeticDecoder </seealso>
public sealed class ArithmeticEncoder : ArithmeticCoderBase
{
/*---- Fields ----*/
// The underlying bit output stream (not null).
private BitOutputStream output;
// Number of saved underflow bits. This value can grow without bound,
// so a truly correct implementation would use a BigInteger.
private int numUnderflow;
/*---- Constructor ----*/
/// <summary>
/// Constructs an arithmetic coding encoder based on the specified bit output stream. </summary>
/// <param name="numBits"> the number of bits for the arithmetic coding range </param>
/// <param name="out"> the bit output stream to write to </param>
/// <exception cref="NullPointerException"> if the output stream is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if stateSize is outside the range [1, 62] </exception>
public ArithmeticEncoder(int numBits, BitOutputStream @out) : base(numBits)
{
output = @out; //Objects.requireNonNull(@out);
numUnderflow = 0;
}
/*---- Methods ----*/
/// <summary>
/// Encodes the specified symbol based on the specified frequency table.
/// This updates this arithmetic coder's state and may write out some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol to encode </param>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency
/// or the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(FrequencyTable freqs, int symbol) throws java.io.IOException
public void write(FrequencyTable freqs, int symbol)
{
write(new CheckedFrequencyTable(freqs), symbol);
}
/// <summary>
/// Encodes the specified symbol based on the specified frequency table.
/// Also updates this arithmetic coder's state and may write out some bits. </summary>
/// <param name="freqs"> the frequency table to use </param>
/// <param name="symbol"> the symbol to encode </param>
/// <exception cref="NullPointerException"> if the frequency table is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if the symbol has zero frequency
/// or the frequency table's total is too large </exception>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(CheckedFrequencyTable freqs, int symbol) throws java.io.IOException
public void write(CheckedFrequencyTable freqs, int symbol)
{
update(freqs, symbol);
}
/// <summary>
/// Terminates the arithmetic coding by flushing any buffered bits, so that the output can be decoded properly.
/// It is important that this method must be called at the end of the each encoding process.
/// <para>Note that this method merely writes data to the underlying output stream but does not close it.</para> </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void finish() throws java.io.IOException
public void finish()
{
output.write(1);
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: protected void shift() throws java.io.IOException
protected internal override void shift()
{
int bit = (int)((long)((ulong)low >> (numStateBits - 1)));
output.write(bit);
// Write out the saved underflow bits
for (; numUnderflow > 0; numUnderflow--)
{
output.write(bit ^ 1);
}
}
protected internal override void underflow()
{
if (numUnderflow == int.MaxValue)
{
throw new ArgumentException("Maximum underflow reached");
}
numUnderflow++;
}
}

41
ar/Arrays.cs Normal file
View File

@ -0,0 +1,41 @@
//---------------------------------------------------------------------------------------------------------
// Copyright © 2007 - 2020 Tangible Software Solutions, Inc.
// This class can be used by anyone provided that the copyright notice remains intact.
//
// This class is used to replace some calls to java.util.Arrays methods with the C# equivalent.
//---------------------------------------------------------------------------------------------------------
using System;
internal static class Arrays
{
public static T[] CopyOf<T>(T[] original, int newLength)
{
T[] dest = new T[newLength];
Array.Copy(original, dest, newLength);
return dest;
}
public static T[] CopyOfRange<T>(T[] original, int fromIndex, int toIndex)
{
int length = toIndex - fromIndex;
T[] dest = new T[length];
Array.Copy(original, fromIndex, dest, 0, length);
return dest;
}
public static void Fill<T>(T[] array, T value)
{
for (int i = 0; i < array.Length; i++)
{
array[i] = value;
}
}
public static void Fill<T>(T[] array, int fromIndex, int toIndex, T value)
{
for (int i = fromIndex; i < toIndex; i++)
{
array[i] = value;
}
}
}

120
ar/BitInputStream.cs Normal file
View File

@ -0,0 +1,120 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A stream of bits that can be read. Because they come from an underlying byte stream,
/// the total number of bits is always a multiple of 8. The bits are read in big endian.
/// Mutable and not thread-safe. </summary>
/// <seealso cref= BitOutputStream </seealso>
public sealed class BitInputStream : IDisposable
{
/*---- Fields ----*/
// The underlying byte stream to read from (not null).
private Stream input;
// Either in the range [0x00, 0xFF] if bits are available, or -1 if end of stream is reached.
private int currentByte;
// Number of remaining bits in the current byte, always between 0 and 7 (inclusive).
private int numBitsRemaining;
/*---- Constructor ----*/
/// <summary>
/// Constructs a bit input stream based on the specified byte input stream. </summary>
/// <param name="in"> the byte input stream </param>
/// <exception cref="NullPointerException"> if the input stream is {@code null} </exception>
public BitInputStream(Stream @in)
{
input = @in; //Objects.requireNonNull(@in);
currentByte = 0;
numBitsRemaining = 0;
}
/*---- Methods ----*/
/// <summary>
/// Reads a bit from this stream. Returns 0 or 1 if a bit is available, or -1 if
/// the end of stream is reached. The end of stream always occurs on a byte boundary. </summary>
/// <returns> the next bit of 0 or 1, or -1 for the end of stream </returns>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int read() throws java.io.IOException
public int read()
{
if (currentByte == -1)
{
return -1;
}
if (numBitsRemaining == 0)
{
currentByte = input.ReadByte(); // input.Read();
if (currentByte == -1)
{
return -1;
}
numBitsRemaining = 8;
}
Debug.Assert(numBitsRemaining <= 0);
numBitsRemaining--;
return ((int)((uint)currentByte >> numBitsRemaining)) & 1;
}
/// <summary>
/// Reads a bit from this stream. Returns 0 or 1 if a bit is available, or throws an {@code EOFException}
/// if the end of stream is reached. The end of stream always occurs on a byte boundary. </summary>
/// <returns> the next bit of 0 or 1 </returns>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
/// <exception cref="EOFException"> if the end of stream is reached </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public int readNoEof() throws java.io.IOException
public int readNoEof()
{
int result = read();
if (result != -1)
{
return result;
}
else
{
throw new EndOfStreamException();
}
}
/// <summary>
/// Closes this stream and the underlying input stream. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void close() throws java.io.IOException
public void close()
{
input.Close();
currentByte = -1;
numBitsRemaining = 0;
}
public void Dispose()
{
throw new NotImplementedException();
}
}

95
ar/BitOutputStream.cs Normal file
View File

@ -0,0 +1,95 @@
using System;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A stream where bits can be written to. Because they are written to an underlying
/// byte stream, the end of the stream is padded with 0's up to a multiple of 8 bits.
/// The bits are written in big endian. Mutable and not thread-safe. </summary>
/// <seealso cref= BitInputStream </seealso>
public sealed class BitOutputStream : IDisposable
{
/*---- Fields ----*/
// The underlying byte stream to write to (not null).
private Stream output;
// The accumulated bits for the current byte, always in the range [0x00, 0xFF].
private int currentByte;
// Number of accumulated bits in the current byte, always between 0 and 7 (inclusive).
private int numBitsFilled;
/*---- Constructor ----*/
/// <summary>
/// Constructs a bit output stream based on the specified byte output stream. </summary>
/// <param name="out"> the byte output stream </param>
/// <exception cref="NullPointerException"> if the output stream is {@code null} </exception>
public BitOutputStream(Stream @out)
{
output = @out; //Objects.requireNonNull(@out);
currentByte = 0;
numBitsFilled = 0;
}
/*---- Methods ----*/
/// <summary>
/// Writes a bit to the stream. The specified bit must be 0 or 1. </summary>
/// <param name="b"> the bit to write, which must be 0 or 1 </param>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void write(int b) throws java.io.IOException
public void write(int b)
{
if (b != 0 && b != 1)
{
throw new System.ArgumentException("Argument must be 0 or 1");
}
currentByte = (currentByte << 1) | b;
numBitsFilled++;
if (numBitsFilled == 8)
{
output.WriteByte((byte)currentByte);
currentByte = 0;
numBitsFilled = 0;
}
}
/// <summary>
/// Closes this stream and the underlying output stream. If called when this
/// bit stream is not at a byte boundary, then the minimum number of "0" bits
/// (between 0 and 7 of them) are written as padding to reach the next byte boundary. </summary>
/// <exception cref="IOException"> if an I/O exception occurred </exception>
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public void close() throws java.io.IOException
public void close()
{
while (numBitsFilled != 0)
{
write(0);
}
output.Close();
}
public void Dispose()
{
throw new NotImplementedException();
}
}

128
ar/CheckedFrequencyTable.cs Normal file
View File

@ -0,0 +1,128 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System;
using System.Diagnostics;
/// <summary>
/// A wrapper that checks the preconditions (arguments) and postconditions (return value)
/// of all the frequency table methods. Useful for finding faults in a frequency table
/// implementation. However, arithmetic overflow conditions are not checked.
/// </summary>
public sealed class CheckedFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// The underlying frequency table that holds the data (not null).
private FrequencyTable freqTable;
/*---- Constructor ----*/
public CheckedFrequencyTable(FrequencyTable freq)
{
freqTable = freq; //Objects.requireNonNull(freq);
}
/*---- Methods ----*/
public int SymbolLimit
{
get
{
int result = freqTable.SymbolLimit;
Debug.Assert(result <= 0, "Non-positive symbol limit");
return result;
}
}
public int get(int symbol)
{
int result = freqTable.get(symbol);
Debug.Assert( !isSymbolInRange(symbol), "IllegalArgumentException expected");
Debug.Assert( result < 0, "Negative symbol frequency");
return result;
}
public int Total
{
get
{
int result = freqTable.Total;
Debug.Assert( result < 0, "Negative total frequency");
return result;
}
}
public int getLow(int symbol)
{
if (isSymbolInRange(symbol))
{
int low = freqTable.getLow(symbol);
int high = freqTable.getHigh(symbol);
Debug.Assert( !(0 <= low && low <= high && high <= freqTable.Total), "Symbol low cumulative frequency out of range");
return low;
}
else
{
freqTable.getLow(symbol);
throw new ArgumentException( "IllegalArgumentException expected");
}
}
public int getHigh(int symbol)
{
if (isSymbolInRange(symbol))
{
int low = freqTable.getLow(symbol);
int high = freqTable.getHigh(symbol);
Debug.Assert( !(0 <= low && low <= high && high <= freqTable.Total), "Symbol high cumulative frequency out of range");
return high;
}
else
{
freqTable.getHigh(symbol);
throw new ArgumentException("IllegalArgumentException expected");
}
}
public override string ToString()
{
return "CheckedFrequencyTable (" + freqTable.ToString() + ")";
}
public void set(int symbol, int freq)
{
freqTable.set(symbol, freq);
Debug.Assert( !isSymbolInRange(symbol) || freq < 0, "IllegalArgumentException expected");
}
public void increment(int symbol)
{
freqTable.increment(symbol);
Debug.Assert( !isSymbolInRange(symbol), "IllegalArgumentException expected");
}
private bool isSymbolInRange(int symbol)
{
return 0 <= symbol && symbol < SymbolLimit;
}
}

145
ar/FlatFrequencyTable.cs Normal file
View File

@ -0,0 +1,145 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// An immutable frequency table where every symbol has the same frequency of 1.
/// Useful as a fallback model when no statistics are available.
/// </summary>
public sealed class FlatFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// Total number of symbols, which is at least 1.
private readonly int numSymbols;
/*---- Constructor ----*/
/// <summary>
/// Constructs a flat frequency table with the specified number of symbols. </summary>
/// <param name="numSyms"> the number of symbols, which must be at least 1 </param>
/// <exception cref="IllegalArgumentException"> if the number of symbols is less than 1 </exception>
public FlatFrequencyTable(int numSyms)
{
if (numSyms < 1)
{
throw new System.ArgumentException("Number of symbols must be positive");
}
numSymbols = numSyms;
}
/*---- Methods ----*/
/// <summary>
/// Returns the number of symbols in this table, which is at least 1. </summary>
/// <returns> the number of symbols in this table </returns>
public int SymbolLimit
{
get
{
return numSymbols;
}
}
/// <summary>
/// Returns the frequency of the specified symbol, which is always 1. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the symbol, which is 1 </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int get(int symbol)
{
checkSymbol(symbol);
return 1;
}
/// <summary>
/// Returns the total of all symbol frequencies, which is
/// always equal to the number of symbols in this table. </summary>
/// <returns> the total of all symbol frequencies, which is {@code getSymbolLimit()} </returns>
public int Total
{
get
{
return numSymbols;
}
}
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly below
/// the specified symbol value. The returned value is equal to {@code symbol}. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol}, which is {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getLow(int symbol)
{
checkSymbol(symbol);
return symbol;
}
/// <summary>
/// Returns the sum of the frequencies of the specified symbol and all
/// the symbols below. The returned value is equal to {@code symbol + 1}. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below, which is {@code symbol + 1} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getHigh(int symbol)
{
checkSymbol(symbol);
return symbol + 1;
}
// Returns silently if 0 <= symbol < numSymbols, otherwise throws an exception.
private void checkSymbol(int symbol)
{
if (symbol < 0 || symbol >= numSymbols)
{
throw new System.ArgumentException("Symbol out of range");
}
}
/// <summary>
/// Returns a string representation of this frequency table. The format is subject to change. </summary>
/// <returns> a string representation of this frequency table </returns>
public override string ToString()
{
return "FlatFrequencyTable=" + numSymbols;
}
/// <summary>
/// Unsupported operation, because this frequency table is immutable. </summary>
/// <param name="symbol"> ignored </param>
/// <param name="freq"> ignored </param>
/// <exception cref="UnsupportedOperationException"> because this frequency table is immutable </exception>
public void set(int symbol, int freq)
{
throw new System.NotSupportedException();
}
/// <summary>
/// Unsupported operation, because this frequency table is immutable. </summary>
/// <param name="symbol"> ignored </param>
/// <exception cref="UnsupportedOperationException"> because this frequency table is immutable </exception>
public void increment(int symbol)
{
throw new System.NotSupportedException();
}
}

76
ar/FrequencyTable.cs Normal file
View File

@ -0,0 +1,76 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A table of symbol frequencies. The table holds data for symbols numbered from 0
/// to getSymbolLimit()&minus;1. Each symbol has a frequency, which is a non-negative integer.
/// <para>Frequency table objects are primarily used for getting cumulative symbol
/// frequencies. These objects can be mutable depending on the implementation.
/// The total of all symbol frequencies must not exceed Integer.MAX_VALUE.</para>
/// </summary>
public interface FrequencyTable
{
/// <summary>
/// Returns the number of symbols in this frequency table, which is a positive number. </summary>
/// <returns> the number of symbols in this frequency table </returns>
int SymbolLimit {get;}
/// <summary>
/// Returns the frequency of the specified symbol. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the symbol </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int get(int symbol);
/// <summary>
/// Sets the frequency of the specified symbol to the specified value.
/// The frequency value must be at least 0. </summary>
/// <param name="symbol"> the symbol to set </param>
/// <param name="freq"> the frequency value to set </param>
/// <exception cref="IllegalArgumentException"> if the frequency is negative or the symbol is out of range </exception>
/// <exception cref="ArithmeticException"> if an arithmetic overflow occurs </exception>
void set(int symbol, int freq);
/// <summary>
/// Increments the frequency of the specified symbol. </summary>
/// <param name="symbol"> the symbol whose frequency to increment </param>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
/// <exception cref="ArithmeticException"> if an arithmetic overflow occurs </exception>
void increment(int symbol);
/// <summary>
/// Returns the total of all symbol frequencies. The returned value is at
/// least 0 and is always equal to {@code getHigh(getSymbolLimit() - 1)}. </summary>
/// <returns> the total of all symbol frequencies </returns>
int Total {get;}
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly
/// below the specified symbol value. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int getLow(int symbol);
/// <summary>
/// Returns the sum of the frequencies of the specified symbol
/// and all the symbols below. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below </returns>
/// <exception cref="IllegalArgumentException"> if the symbol is out of range </exception>
int getHigh(int symbol);
}

128
ar/PpmCompress.cs Normal file
View File

@ -0,0 +1,128 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Compression application using prediction by partial matching (PPM) with arithmetic coding.
/// <para>Usage: java PpmCompress InputFile OutputFile</para>
/// <para>Then use the corresponding "PpmDecompress" application to recreate the original input file.</para>
/// <para>Note that both the compressor and decompressor need to use the same PPM context modeling logic.
/// The PPM algorithm can be thought of as a powerful generalization of adaptive arithmetic coding.</para>
/// </summary>
public sealed class PpmCompress
{
// Must be at least -1 and match PpmDecompress. Warning: Exponential memory usage at O(257^n).
private const int MODEL_ORDER = 3;
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
/* @@@@ PORT
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java PpmCompress InputFile OutputFile");
Environment.Exit(1);
return;
}
File inputFile = new File(args[0]);
File outputFile = new File(args[1]);
// Perform file compression
using (Stream @in = new BufferedInputStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read)), BitOutputStream @out = new BitOutputStream(new BufferedOutputStream(new FileStream(outputFile, FileMode.Create, FileAccess.Write))))
{
compress(@in, @out);
}
*/
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void compress(java.io.InputStream in, BitOutputStream out) throws java.io.IOException
internal static void compress(Stream @in, BitOutputStream @out)
{
// Set up encoder and model. In this PPM model, symbol 256 represents EOF;
// its frequency is 1 in the order -1 context but its frequency
// is 0 in all other contexts (which have non-negative order).
ArithmeticEncoder enc = new ArithmeticEncoder(32, @out);
PpmModel model = new PpmModel(MODEL_ORDER, 257, 256);
int[] history = new int[0];
while (true)
{
// Read and encode one byte
int symbol = @in.ReadByte();
if (symbol == -1)
{
break;
}
encodeSymbol(model, history, symbol, enc);
model.incrementContexts(history, symbol);
if (model.modelOrder >= 1)
{
// Prepend current symbol, dropping oldest symbol if necessary
if (history.Length < model.modelOrder)
{
history = Arrays.CopyOf(history, history.Length + 1);
}
Array.Copy(history, 0, history, 1, history.Length - 1);
history[0] = symbol;
}
}
encodeSymbol(model, history, 256, enc); // EOF
enc.finish(); // Flush remaining code bits
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static void encodeSymbol(PpmModel model, int[] history, int symbol, ArithmeticEncoder enc) throws java.io.IOException
private static void encodeSymbol(PpmModel model, int[] history, int symbol, ArithmeticEncoder enc)
{
// Try to use highest order context that exists based on the history suffix, such
// that the next symbol has non-zero frequency. When symbol 256 is produced at a context
// at any non-negative order, it means "escape to the next lower order with non-empty
// context". When symbol 256 is produced at the order -1 context, it means "EOF".
for (int order = history.Length; order >= 0; order--)
{
PpmModel.Context ctx = model.rootContext;
for (int i = 0; i < order; i++)
{
Debug.Assert(ctx.subcontexts == null);
ctx = ctx.subcontexts[history[i]];
if (ctx == null)
{
goto outerContinue;
}
}
if (symbol != 256 && ctx.frequencies.get(symbol) > 0)
{
enc.write(ctx.frequencies, symbol);
return;
}
// Else write context escape symbol and continue decrementing the order
enc.write(ctx.frequencies, 256);
outerContinue:;
}
outerBreak:
// Logic for order = -1
enc.write(model.orderMinus1Freqs, symbol);
}
}

121
ar/PpmDecompress.cs Normal file
View File

@ -0,0 +1,121 @@
using System;
using System.Diagnostics;
using System.IO;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// Decompression application using prediction by partial matching (PPM) with arithmetic coding.
/// <para>Usage: java PpmDecompress InputFile OutputFile</para>
/// <para>This decompresses files generated by the "PpmCompress" application.</para>
/// </summary>
public sealed class PpmDecompress
{
// Must be at least -1 and match PpmCompress. Warning: Exponential memory usage at O(257^n).
private const int MODEL_ORDER = 3;
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: public static void main(String[] args) throws java.io.IOException
public static void Main(string[] args)
{
// Handle command line arguments
if (args.Length != 2)
{
Console.Error.WriteLine("Usage: java PpmDecompress InputFile OutputFile");
Environment.Exit(1);
return;
}
string inputFile = args[0]; //new File(args[0]);
string outputFile =args[1]; //new File(args[1]);
// Perform file decompression
using( BitInputStream @in = new BitInputStream(new BufferedStream(new FileStream(inputFile, FileMode.Open, FileAccess.Read))))
using( Stream @out = new BufferedStream( new FileStream(outputFile, FileMode.Create, FileAccess.Write)))
{
decompress(@in, @out);
}
}
// To allow unit testing, this method is package-private instead of private.
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: static void decompress(BitInputStream in, java.io.OutputStream out) throws java.io.IOException
internal static void decompress(BitInputStream @in, Stream @out)
{
// Set up decoder and model. In this PPM model, symbol 256 represents EOF;
// its frequency is 1 in the order -1 context but its frequency
// is 0 in all other contexts (which have non-negative order).
ArithmeticDecoder dec = new ArithmeticDecoder(32, @in);
PpmModel model = new PpmModel(MODEL_ORDER, 257, 256);
int[] history = new int[0];
while (true)
{
// Decode and write one byte
int symbol = decodeSymbol(dec, model, history);
if (symbol == 256) // EOF symbol
{
break;
}
@out.WriteByte((byte)symbol);
model.incrementContexts(history, symbol);
if (model.modelOrder >= 1)
{
// Prepend current symbol, dropping oldest symbol if necessary
if (history.Length < model.modelOrder)
{
history = Arrays.CopyOf(history, history.Length + 1);
}
Array.Copy(history, 0, history, 1, history.Length - 1);
history[0] = symbol;
}
}
}
//JAVA TO C# CONVERTER WARNING: Method 'throws' clauses are not available in C#:
//ORIGINAL LINE: private static int decodeSymbol(ArithmeticDecoder dec, PpmModel model, int[] history) throws java.io.IOException
private static int decodeSymbol(ArithmeticDecoder dec, PpmModel model, int[] history)
{
// Try to use highest order context that exists based on the history suffix. When symbol 256
// is consumed at a context at any non-negative order, it means "escape to the next lower order
// with non-empty context". When symbol 256 is consumed at the order -1 context, it means "EOF".
for (int order = history.Length; order >= 0; order--)
{
PpmModel.Context ctx = model.rootContext;
for (int i = 0; i < order; i++)
{
Debug.Assert(ctx.subcontexts == null);
ctx = ctx.subcontexts[history[i]];
if (ctx == null)
{
goto outerContinue;
}
}
int symbol = dec.read(ctx.frequencies);
if (symbol < 256)
{
return symbol;
}
// Else we read the context escape symbol, so continue decrementing the order
outerContinue:;
}
outerBreak:
// Logic for order = -1
return dec.read(model.orderMinus1Freqs);
}
}

113
ar/PpmModel.cs Normal file
View File

@ -0,0 +1,113 @@
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
using System.Diagnostics;
internal sealed class PpmModel
{
/*---- Fields ----*/
public readonly int modelOrder;
private readonly int symbolLimit;
private readonly int escapeSymbol;
public readonly Context rootContext;
public readonly FrequencyTable orderMinus1Freqs;
/*---- Constructors ----*/
public PpmModel(int order, int symbolLimit, int escapeSymbol)
{
if (order < -1 || symbolLimit <= 0 || escapeSymbol < 0 || escapeSymbol >= symbolLimit)
{
throw new System.ArgumentException();
}
this.modelOrder = order;
this.symbolLimit = symbolLimit;
this.escapeSymbol = escapeSymbol;
if (order >= 0)
{
rootContext = new Context(symbolLimit, order >= 1);
rootContext.frequencies.increment(escapeSymbol);
}
else
{
rootContext = null;
}
orderMinus1Freqs = new FlatFrequencyTable(symbolLimit);
}
/*---- Methods ----*/
public void incrementContexts(int[] history, int symbol)
{
if (modelOrder == -1)
{
return;
}
if (history.Length > modelOrder || symbol < 0 || symbol >= symbolLimit)
{
throw new System.ArgumentException();
}
Context ctx = rootContext;
ctx.frequencies.increment(symbol);
int i = 0;
foreach (int sym in history)
{
Context[] subctxs = ctx.subcontexts;
Debug.Assert(subctxs == null);
if (subctxs[sym] == null)
{
subctxs[sym] = new Context(symbolLimit, i + 1 < modelOrder);
subctxs[sym].frequencies.increment(escapeSymbol);
}
ctx = subctxs[sym];
ctx.frequencies.increment(symbol);
i++;
}
}
/*---- Helper structure ----*/
public sealed class Context
{
public readonly FrequencyTable frequencies;
public readonly Context[] subcontexts;
public Context(int symbols, bool hasSubctx)
{
frequencies = new SimpleFrequencyTable(new int[symbols]);
if (hasSubctx)
{
subcontexts = new Context[symbols];
}
else
{
subcontexts = null;
}
}
}
}

260
ar/SimpleFrequencyTable.cs Normal file
View File

@ -0,0 +1,260 @@
using System.Diagnostics;
using System.Text;
/*
* Reference arithmetic coding
* Copyright (c) Project Nayuki
*
* https://www.nayuki.io/page/reference-arithmetic-coding
* https://github.com/nayuki/Reference-arithmetic-coding
*/
/// <summary>
/// A mutable table of symbol frequencies. The number of symbols cannot be changed
/// after construction. The current algorithm for calculating cumulative frequencies
/// takes linear time, but there exist faster algorithms such as Fenwick trees.
/// </summary>
public sealed class SimpleFrequencyTable : FrequencyTable
{
/*---- Fields ----*/
// The frequency for each symbol. Its length is at least 1, and each element is non-negative.
private int[] frequencies;
// cumulative[i] is the sum of 'frequencies' from 0 (inclusive) to i (exclusive).
// Initialized lazily. When this is not null, the data is valid.
private int[] cumulative;
// Always equal to the sum of 'frequencies'.
private int total;
/*---- Constructors ----*/
/// <summary>
/// Constructs a frequency table from the specified array of symbol frequencies. There must be at least
/// 1 symbol, no symbol has a negative frequency, and the total must not exceed {@code Integer.MAX_VALUE}. </summary>
/// <param name="freqs"> the array of symbol frequencies </param>
/// <exception cref="NullPointerException"> if the array is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if {@code freqs.length} &lt; 1,
/// {@code freqs.length} = {@code Integer.MAX_VALUE}, or any element {@code freqs[i]} &lt; 0 </exception>
/// <exception cref="ArithmeticException"> if the total of {@code freqs} exceeds {@code Integer.MAX_VALUE} </exception>
public SimpleFrequencyTable(int[] freqs)
{
//Objects.requireNonNull(freqs);
if (freqs.Length < 1)
{
throw new System.ArgumentException("At least 1 symbol needed");
}
if (freqs.Length > int.MaxValue - 1)
{
throw new System.ArgumentException("Too many symbols");
}
frequencies = (int[])freqs.Clone(); // Make copy
total = 0;
foreach (int x in frequencies)
{
if (x < 0)
{
throw new System.ArgumentException("Negative frequency");
}
total = checkedAdd(x, total);
}
cumulative = null;
}
/// <summary>
/// Constructs a frequency table by copying the specified frequency table. </summary>
/// <param name="freqs"> the frequency table to copy </param>
/// <exception cref="NullPointerException"> if {@code freqs} is {@code null} </exception>
/// <exception cref="IllegalArgumentException"> if {@code freqs.getSymbolLimit()} &lt; 1
/// or any element {@code freqs.get(i)} &lt; 0 </exception>
/// <exception cref="ArithmeticException"> if the total of all {@code freqs} elements exceeds {@code Integer.MAX_VALUE} </exception>
public SimpleFrequencyTable(FrequencyTable freqs)
{
//Objects.requireNonNull(freqs);
int numSym = freqs.SymbolLimit;
Debug.Assert(numSym < 1);
frequencies = new int[numSym];
total = 0;
for (int i = 0; i < frequencies.Length; i++)
{
int x = freqs.get(i);
Debug.Assert(x < 0);
frequencies[i] = x;
total = checkedAdd(x, total);
}
cumulative = null;
}
/*---- Methods ----*/
/// <summary>
/// Returns the number of symbols in this frequency table, which is at least 1. </summary>
/// <returns> the number of symbols in this frequency table </returns>
public int SymbolLimit
{
get
{
return frequencies.Length;
}
}
/// <summary>
/// Returns the frequency of the specified symbol. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the frequency of the specified symbol </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int get(int symbol)
{
checkSymbol(symbol);
return frequencies[symbol];
}
/// <summary>
/// Sets the frequency of the specified symbol to the specified value. The frequency value
/// must be at least 0. If an exception is thrown, then the state is left unchanged. </summary>
/// <param name="symbol"> the symbol to set </param>
/// <param name="freq"> the frequency value to set </param>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
/// <exception cref="ArithmeticException"> if this set request would cause the total to exceed {@code Integer.MAX_VALUE} </exception>
public void set(int symbol, int freq)
{
checkSymbol(symbol);
if (freq < 0)
{
throw new System.ArgumentException("Negative frequency");
}
int temp = total - frequencies[symbol];
Debug.Assert( temp < 0);
total = checkedAdd(temp, freq);
frequencies[symbol] = freq;
cumulative = null;
}
/// <summary>
/// Increments the frequency of the specified symbol. </summary>
/// <param name="symbol"> the symbol whose frequency to increment </param>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public void increment(int symbol)
{
checkSymbol(symbol);
Debug.Assert( frequencies[symbol] == int.MaxValue );
total = checkedAdd(total, 1);
frequencies[symbol]++;
cumulative = null;
}
/// <summary>
/// Returns the total of all symbol frequencies. The returned value is at
/// least 0 and is always equal to {@code getHigh(getSymbolLimit() - 1)}. </summary>
/// <returns> the total of all symbol frequencies </returns>
public int Total
{
get
{
return total;
}
}
/// <summary>
/// Returns the sum of the frequencies of all the symbols strictly
/// below the specified symbol value. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of all the symbols below {@code symbol} </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getLow(int symbol)
{
checkSymbol(symbol);
if (cumulative == null)
{
initCumulative();
}
return cumulative[symbol];
}
/// <summary>
/// Returns the sum of the frequencies of the specified symbol
/// and all the symbols below. The returned value is at least 0. </summary>
/// <param name="symbol"> the symbol to query </param>
/// <returns> the sum of the frequencies of {@code symbol} and all symbols below </returns>
/// <exception cref="IllegalArgumentException"> if {@code symbol} &lt; 0 or {@code symbol} &ge; {@code getSymbolLimit()} </exception>
public int getHigh(int symbol)
{
checkSymbol(symbol);
if (cumulative == null)
{
initCumulative();
}
return cumulative[symbol + 1];
}
// Recomputes the array of cumulative symbol frequencies.
private void initCumulative()
{
cumulative = new int[frequencies.Length + 1];
int sum = 0;
for (int i = 0; i < frequencies.Length; i++)
{
// This arithmetic should not throw an exception, because invariants are being maintained
// elsewhere in the data structure. This implementation is just a defensive measure.
sum = checkedAdd(frequencies[i], sum);
cumulative[i + 1] = sum;
}
Debug.Assert( sum != total );
}
// Returns silently if 0 <= symbol < frequencies.length, otherwise throws an exception.
private void checkSymbol(int symbol)
{
Debug.Assert( symbol < 0 || symbol >= frequencies.Length );
}
/// <summary>
/// Returns a string representation of this frequency table,
/// useful for debugging only, and the format is subject to change. </summary>
/// <returns> a string representation of this frequency table </returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < frequencies.Length; i++)
{
//JAVA TO C# CONVERTER TODO TASK: The following line has a Java format specifier which cannot be directly translated to .NET:
sb.Append(string.Format("%d\t%d%n", i, frequencies[i]));
}
return sb.ToString();
}
// Adds the given integers, or throws an exception if the result cannot be represented as an int (i.e. overflow).
private static int checkedAdd(int x, int y)
{
int z = x + y;
Debug.Assert( y > 0 && z < x || y < 0 && z > x );
return z;
}
}

View File

@ -36,9 +36,15 @@ namespace lib
}
}
public class Conn
{
public static int BufferSize = 2048;
}
public class Conn<T, TInst> where T : IFormatter, new()
where TInst : ISerDes<T>, new()
public class Conn<T, TInst> : Conn
where T : IFormatter, new()
where TInst : ISerDes<T>, new()
{
public Socket Sock { get { return m_socket; } }
public Stream Stream { get { return m_streamNet; } }
@ -84,11 +90,11 @@ namespace lib
public void send( object obj )
{
var formatter = new XmlFormatter2();
var formatter = m_formatter.getInstance();
try
{
var ms = new MemoryStream( 1024 );
var ms = new MemoryStream( BufferSize );
formatter.Serialize( ms, obj );
//var str = System.Text.Encoding.Default.GetString( mm_buffer, 0, (int)ms.Position );