diff --git a/BitStream.cs b/BitStream.cs new file mode 100644 index 0000000..5243acc --- /dev/null +++ b/BitStream.cs @@ -0,0 +1,133 @@ +using System; +using System.IO; +using SabreTools.IO; + +namespace SabreTools.Compression +{ + /// + /// Wrapper to allow reading bits from a source stream + /// + public class BitStream + { + /// + /// Original stream source + /// + private Stream _source; + + /// + /// Last read byte value from the stream + /// + private byte? _lastRead; + + /// + /// Index in the byte of the current bit + /// + private int _bitIndex; + + /// + /// Create a new BitStream from a source Stream + /// + public BitStream(Stream source) + { + if (!source.CanRead || !source.CanSeek) + throw new ArgumentException(nameof(source)); + + _source = source; + _lastRead = null; + _bitIndex = 0; + } + + /// + /// Discard the current cached byte + /// + public void Discard() + { + _lastRead = null; + _bitIndex = 0; + } + + /// + /// Read a single bit, if possible + /// + /// The next bit encoded in a byte, null on error or end of stream + public byte? ReadBit() + { + // If we reached the end of the stream + if (_source.Position >= _source.Length) + return null; + + // If we don't have a value cached + if (_lastRead == null) + { + // Read the next byte, if possible + _lastRead = ReadSourceByte(); + if (_lastRead == null) + return null; + + // Reset the bit index + _bitIndex = 0; + } + + // Get the value by bit-shifting + int value = (_lastRead.Value >> _bitIndex++) & 0x01; + + // Reset the byte if we're at the end + if (_bitIndex >= 8) + Discard(); + + return (byte)value; + } + + /// + /// Read a full byte, if possible + /// + /// The next byte, null on error or end of stream + public byte? ReadByte() + { + throw new NotImplementedException(); + } + + /// + /// Read a full UInt16, if possible + /// + /// The next UInt16, null on error or end of stream + public byte? ReadUInt16() + { + throw new NotImplementedException(); + } + + /// + /// Read a full UInt32, if possible + /// + /// The next UInt32, null on error or end of stream + public byte? ReadUInt32() + { + throw new NotImplementedException(); + } + + /// + /// Read a full UInt64, if possible + /// + /// The next UInt64, null on error or end of stream + public byte? ReadUInt64() + { + throw new NotImplementedException(); + } + + /// + /// Read a single byte from the underlying stream, if possible + /// + /// The next full byte from the stream, null on error or end of stream + private byte? ReadSourceByte() + { + try + { + return _source.ReadByteValue(); + } + catch + { + return null; + } + } + } +} \ No newline at end of file