diff --git a/src/ImageProcessor/IO/BigEndianBitConverter.cs b/src/ImageProcessor/IO/BigEndianBitConverter.cs
new file mode 100644
index 0000000000..bbaaf4cf53
--- /dev/null
+++ b/src/ImageProcessor/IO/BigEndianBitConverter.cs
@@ -0,0 +1,48 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.IO
+{
+ ///
+ /// Implementation of EndianBitConverter which converts to/from big-endian
+ /// byte arrays.
+ ///
+ /// Adapted from Miscellaneous Utility Library
+ /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see
+ /// .
+ ///
+ ///
+ internal sealed class BigEndianBitConverter : EndianBitConverter
+ {
+ ///
+ public override Endianness Endianness => Endianness.BigEndian;
+
+ ///
+ public override bool IsLittleEndian() => false;
+
+ ///
+ protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index)
+ {
+ int endOffset = index + bytes - 1;
+ for (int i = 0; i < bytes; i++)
+ {
+ buffer[endOffset - i] = unchecked((byte)(value & 0xff));
+ value = value >> 8;
+ }
+ }
+
+ ///
+ protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert)
+ {
+ long ret = 0;
+ for (int i = 0; i < bytesToConvert; i++)
+ {
+ ret = unchecked((ret << 8) | buffer[startIndex + i]);
+ }
+
+ return ret;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor/IO/EndianBinaryReader.cs b/src/ImageProcessor/IO/EndianBinaryReader.cs
new file mode 100644
index 0000000000..43cd50d836
--- /dev/null
+++ b/src/ImageProcessor/IO/EndianBinaryReader.cs
@@ -0,0 +1,616 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.IO
+{
+ using System;
+ using System.IO;
+ using System.Text;
+
+ ///
+ /// Equivalent of , but with either endianness, depending on
+ /// the EndianBitConverter it is constructed with. No data is buffered in the
+ /// reader; the client may seek within the stream at will.
+ ///
+ internal class EndianBinaryReader : IDisposable
+ {
+ ///
+ /// Decoder to use for string conversions.
+ ///
+ private readonly Decoder decoder;
+
+ ///
+ /// Buffer used for temporary storage before conversion into primitives
+ ///
+ private readonly byte[] buffer = new byte[16];
+
+ ///
+ /// Buffer used for temporary storage when reading a single character
+ ///
+ private readonly char[] charBuffer = new char[1];
+
+ ///
+ /// Minimum number of bytes used to encode a character
+ ///
+ private readonly int minBytesPerChar;
+
+ ///
+ /// Whether or not this reader has been disposed yet.
+ ///
+ private bool disposed;
+
+ ///
+ /// Equivalent of System.IO.BinaryWriter, but with either endianness, depending on
+ /// the EndianBitConverter it is constructed with.
+ ///
+ /// Converter to use when reading data
+ /// Stream to read data from
+ public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream)
+ : this(bitConverter, stream, Encoding.UTF8)
+ {
+ }
+
+ ///
+ /// Constructs a new binary reader with the given bit converter, reading
+ /// to the given stream, using the given encoding.
+ ///
+ /// Converter to use when reading data
+ /// Stream to read data from
+ /// Encoding to use when reading character data
+ public EndianBinaryReader(EndianBitConverter bitConverter, Stream stream, Encoding encoding)
+ {
+ // TODO: Use Guard
+ if (bitConverter == null)
+ {
+ throw new ArgumentNullException("bitConverter");
+ }
+
+ if (stream == null)
+ {
+ throw new ArgumentNullException("stream");
+ }
+
+ if (encoding == null)
+ {
+ throw new ArgumentNullException("encoding");
+ }
+
+ if (!stream.CanRead)
+ {
+ throw new ArgumentException("Stream isn't writable", "stream");
+ }
+
+ this.BaseStream = stream;
+ this.BitConverter = bitConverter;
+ this.Encoding = encoding;
+ this.decoder = encoding.GetDecoder();
+ this.minBytesPerChar = 1;
+
+ if (encoding is UnicodeEncoding)
+ {
+ this.minBytesPerChar = 2;
+ }
+ }
+
+ ///
+ /// Gets the bit converter used to read values from the stream.
+ ///
+ public EndianBitConverter BitConverter { get; }
+
+ ///
+ /// Gets the encoding used to read strings
+ ///
+ public Encoding Encoding { get; }
+
+ ///
+ /// Gets the underlying stream of the EndianBinaryReader.
+ ///
+ public Stream BaseStream { get; }
+
+ ///
+ /// Closes the reader, including the underlying stream.
+ ///
+ public void Close()
+ {
+ this.Dispose();
+ }
+
+ ///
+ /// Seeks within the stream.
+ ///
+ /// Offset to seek to.
+ /// Origin of seek operation.
+ public void Seek(int offset, SeekOrigin origin)
+ {
+ this.CheckDisposed();
+ this.BaseStream.Seek(offset, origin);
+ }
+
+ ///
+ /// Reads a single byte from the stream.
+ ///
+ /// The byte read
+ public byte ReadByte()
+ {
+ this.ReadInternal(this.buffer, 1);
+ return this.buffer[0];
+ }
+
+ ///
+ /// Reads a single signed byte from the stream.
+ ///
+ /// The byte read
+ public sbyte ReadSByte()
+ {
+ this.ReadInternal(this.buffer, 1);
+ return unchecked((sbyte)this.buffer[0]);
+ }
+
+ ///
+ /// Reads a boolean from the stream. 1 byte is read.
+ ///
+ /// The boolean read
+ public bool ReadBoolean()
+ {
+ this.ReadInternal(this.buffer, 1);
+ return this.BitConverter.ToBoolean(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 16-bit signed integer from the stream, using the bit converter
+ /// for this reader. 2 bytes are read.
+ ///
+ /// The 16-bit integer read
+ public short ReadInt16()
+ {
+ this.ReadInternal(this.buffer, 2);
+ return this.BitConverter.ToInt16(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 32-bit signed integer from the stream, using the bit converter
+ /// for this reader. 4 bytes are read.
+ ///
+ /// The 32-bit integer read
+ public int ReadInt32()
+ {
+ this.ReadInternal(this.buffer, 4);
+ return this.BitConverter.ToInt32(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 64-bit signed integer from the stream, using the bit converter
+ /// for this reader. 8 bytes are read.
+ ///
+ /// The 64-bit integer read
+ public long ReadInt64()
+ {
+ this.ReadInternal(this.buffer, 8);
+ return this.BitConverter.ToInt64(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 16-bit unsigned integer from the stream, using the bit converter
+ /// for this reader. 2 bytes are read.
+ ///
+ /// The 16-bit unsigned integer read
+ public ushort ReadUInt16()
+ {
+ this.ReadInternal(this.buffer, 2);
+ return this.BitConverter.ToUInt16(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 32-bit unsigned integer from the stream, using the bit converter
+ /// for this reader. 4 bytes are read.
+ ///
+ /// The 32-bit unsigned integer read
+ public uint ReadUInt32()
+ {
+ this.ReadInternal(this.buffer, 4);
+ return this.BitConverter.ToUInt32(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a 64-bit unsigned integer from the stream, using the bit converter
+ /// for this reader. 8 bytes are read.
+ ///
+ /// The 64-bit unsigned integer read
+ public ulong ReadUInt64()
+ {
+ this.ReadInternal(this.buffer, 8);
+ return this.BitConverter.ToUInt64(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a single-precision floating-point value from the stream, using the bit converter
+ /// for this reader. 4 bytes are read.
+ ///
+ /// The floating point value read
+ public float ReadSingle()
+ {
+ this.ReadInternal(this.buffer, 4);
+ return this.BitConverter.ToSingle(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a double-precision floating-point value from the stream, using the bit converter
+ /// for this reader. 8 bytes are read.
+ ///
+ /// The floating point value read
+ public double ReadDouble()
+ {
+ this.ReadInternal(this.buffer, 8);
+ return this.BitConverter.ToDouble(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a decimal value from the stream, using the bit converter
+ /// for this reader. 16 bytes are read.
+ ///
+ /// The decimal value read
+ public decimal ReadDecimal()
+ {
+ this.ReadInternal(this.buffer, 16);
+ return this.BitConverter.ToDecimal(this.buffer, 0);
+ }
+
+ ///
+ /// Reads a single character from the stream, using the character encoding for
+ /// this reader. If no characters have been fully read by the time the stream ends,
+ /// -1 is returned.
+ ///
+ /// The character read, or -1 for end of stream.
+ public int Read()
+ {
+ int charsRead = this.Read(this.charBuffer, 0, 1);
+ if (charsRead == 0)
+ {
+ return -1;
+ }
+ else
+ {
+ return this.charBuffer[0];
+ }
+ }
+
+ ///
+ /// Reads the specified number of characters into the given buffer, starting at
+ /// the given index.
+ ///
+ /// The buffer to copy data into
+ /// The first index to copy data into
+ /// The number of characters to read
+ /// The number of characters actually read. This will only be less than
+ /// the requested number of characters if the end of the stream is reached.
+ ///
+ public int Read(char[] data, int index, int count)
+ {
+ this.CheckDisposed();
+
+ // TODO: Use Guard
+ if (this.buffer == null)
+ {
+ throw new ArgumentNullException("buffer");
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+
+ if (count + index > data.Length)
+ {
+ throw new ArgumentException("Not enough space in buffer for specified number of characters starting at specified index");
+ }
+
+ int read = 0;
+ bool firstTime = true;
+
+ // Use the normal buffer if we're only reading a small amount, otherwise
+ // use at most 4K at a time.
+ byte[] byteBuffer = this.buffer;
+
+ if (byteBuffer.Length < count * this.minBytesPerChar)
+ {
+ byteBuffer = new byte[4096];
+ }
+
+ while (read < count)
+ {
+ int amountToRead;
+
+ // First time through we know we haven't previously read any data
+ if (firstTime)
+ {
+ amountToRead = count * this.minBytesPerChar;
+ firstTime = false;
+ }
+
+ // After that we can only assume we need to fully read 'chars left -1' characters
+ // and a single byte of the character we may be in the middle of
+ else
+ {
+ amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1;
+ }
+
+ if (amountToRead > byteBuffer.Length)
+ {
+ amountToRead = byteBuffer.Length;
+ }
+
+ int bytesRead = this.TryReadInternal(byteBuffer, amountToRead);
+ if (bytesRead == 0)
+ {
+ return read;
+ }
+
+ int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index);
+ read += decoded;
+ index += decoded;
+ }
+
+ return read;
+ }
+
+ ///
+ /// Reads the specified number of bytes into the given buffer, starting at
+ /// the given index.
+ ///
+ /// The buffer to copy data into
+ /// The first index to copy data into
+ /// The number of bytes to read
+ /// The number of bytes actually read. This will only be less than
+ /// the requested number of bytes if the end of the stream is reached.
+ ///
+ public int Read(byte[] buffer, int index, int count)
+ {
+ this.CheckDisposed();
+ if (buffer == null)
+ {
+ throw new ArgumentNullException("buffer");
+ }
+
+ if (index < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+
+ if (count + index > buffer.Length)
+ {
+ throw new ArgumentException("Not enough space in buffer for specified number of bytes starting at specified index");
+ }
+
+ int read = 0;
+ while (count > 0)
+ {
+ int block = this.BaseStream.Read(buffer, index, count);
+ if (block == 0)
+ {
+ return read;
+ }
+
+ index += block;
+ read += block;
+ count -= block;
+ }
+
+ return read;
+ }
+
+ ///
+ /// Reads the specified number of bytes, returning them in a new byte array.
+ /// If not enough bytes are available before the end of the stream, this
+ /// method will return what is available.
+ ///
+ /// The number of bytes to read
+ /// The bytes read
+ public byte[] ReadBytes(int count)
+ {
+ this.CheckDisposed();
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("count");
+ }
+
+ byte[] ret = new byte[count];
+ int index = 0;
+ while (index < count)
+ {
+ int read = this.BaseStream.Read(ret, index, count - index);
+
+ // Stream has finished half way through. That's fine, return what we've got.
+ if (read == 0)
+ {
+ byte[] copy = new byte[index];
+ Buffer.BlockCopy(ret, 0, copy, 0, index);
+ return copy;
+ }
+
+ index += read;
+ }
+
+ return ret;
+ }
+
+ ///
+ /// Reads the specified number of bytes, returning them in a new byte array.
+ /// If not enough bytes are available before the end of the stream, this
+ /// method will throw an IOException.
+ ///
+ /// The number of bytes to read
+ /// The bytes read
+ public byte[] ReadBytesOrThrow(int count)
+ {
+ byte[] ret = new byte[count];
+ this.ReadInternal(ret, count);
+ return ret;
+ }
+
+ ///
+ /// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
+ /// information first, with 7 bits of information per byte of value, and the top
+ /// bit as a continuation flag. This method is not affected by the endianness
+ /// of the bit converter.
+ ///
+ /// The 7-bit encoded integer read from the stream.
+ public int Read7BitEncodedInt()
+ {
+ this.CheckDisposed();
+
+ int ret = 0;
+ for (int shift = 0; shift < 35; shift += 7)
+ {
+ int b = this.BaseStream.ReadByte();
+ if (b == -1)
+ {
+ throw new EndOfStreamException();
+ }
+
+ ret = ret | ((b & 0x7f) << shift);
+ if ((b & 0x80) == 0)
+ {
+ return ret;
+ }
+ }
+
+ // Still haven't seen a byte with the high bit unset? Dodgy data.
+ throw new IOException("Invalid 7-bit encoded integer in stream.");
+ }
+
+ ///
+ /// Reads a 7-bit encoded integer from the stream. This is stored with the most significant
+ /// information first, with 7 bits of information per byte of value, and the top
+ /// bit as a continuation flag. This method is not affected by the endianness
+ /// of the bit converter.
+ ///
+ /// The 7-bit encoded integer read from the stream.
+ public int ReadBigEndian7BitEncodedInt()
+ {
+ this.CheckDisposed();
+
+ int ret = 0;
+ for (int i = 0; i < 5; i++)
+ {
+ int b = this.BaseStream.ReadByte();
+ if (b == -1)
+ {
+ throw new EndOfStreamException();
+ }
+
+ ret = (ret << 7) | (b & 0x7f);
+ if ((b & 0x80) == 0)
+ {
+ return ret;
+ }
+ }
+
+ // Still haven't seen a byte with the high bit unset? Dodgy data.
+ throw new IOException("Invalid 7-bit encoded integer in stream.");
+ }
+
+ ///
+ /// Reads a length-prefixed string from the stream, using the encoding for this reader.
+ /// A 7-bit encoded integer is first read, which specifies the number of bytes
+ /// to read from the stream. These bytes are then converted into a string with
+ /// the encoding for this reader.
+ ///
+ /// The string read from the stream.
+ public string ReadString()
+ {
+ int bytesToRead = this.Read7BitEncodedInt();
+
+ byte[] data = new byte[bytesToRead];
+ this.ReadInternal(data, bytesToRead);
+ return this.Encoding.GetString(data, 0, data.Length);
+ }
+
+ ///
+ /// Disposes of the underlying stream.
+ ///
+ public void Dispose()
+ {
+ if (!this.disposed)
+ {
+ this.disposed = true;
+ ((IDisposable)this.BaseStream).Dispose();
+ }
+ }
+
+ ///
+ /// Checks whether or not the reader has been disposed, throwing an exception if so.
+ ///
+ private void CheckDisposed()
+ {
+ if (this.disposed)
+ {
+ throw new ObjectDisposedException("EndianBinaryReader");
+ }
+ }
+
+ ///
+ /// Reads the given number of bytes from the stream, throwing an exception
+ /// if they can't all be read.
+ ///
+ /// Buffer to read into
+ /// Number of bytes to read
+ private void ReadInternal(byte[] data, int size)
+ {
+ this.CheckDisposed();
+ int index = 0;
+ while (index < size)
+ {
+ int read = this.BaseStream.Read(data, index, size - index);
+ if (read == 0)
+ {
+ throw new EndOfStreamException
+ (
+ string.Format(
+ "End of stream reached with {0} byte{1} left to read.",
+ size - index,
+ size - index == 1 ? "s" : string.Empty));
+ }
+
+ index += read;
+ }
+ }
+
+ ///
+ /// Reads the given number of bytes from the stream if possible, returning
+ /// the number of bytes actually read, which may be less than requested if
+ /// (and only if) the end of the stream is reached.
+ ///
+ /// Buffer to read into
+ /// Number of bytes to read
+ /// Number of bytes actually read
+ private int TryReadInternal(byte[] data, int size)
+ {
+ this.CheckDisposed();
+ int index = 0;
+ while (index < size)
+ {
+ int read = this.BaseStream.Read(data, index, size - index);
+ if (read == 0)
+ {
+ return index;
+ }
+
+ index += read;
+ }
+
+ return index;
+ }
+ }
+}
diff --git a/src/ImageProcessor/IO/EndianBitConverter.cs b/src/ImageProcessor/IO/EndianBitConverter.cs
new file mode 100644
index 0000000000..a683c09c0b
--- /dev/null
+++ b/src/ImageProcessor/IO/EndianBitConverter.cs
@@ -0,0 +1,724 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.IO
+{
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Runtime.InteropServices;
+
+ ///
+ /// Equivalent of , but with either endianness.
+ ///
+ /// Adapted from Miscellaneous Utility Library
+ /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see
+ /// .
+ ///
+ ///
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")]
+ [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")]
+ internal abstract class EndianBitConverter
+ {
+ #region Endianness of this converter
+ ///
+ /// Indicates the byte order ("endianness") in which data is converted using this class.
+ ///
+ ///
+ /// Different computer architectures store data using different byte orders. "Big-endian"
+ /// means the most significant byte is on the left end of a word. "Little-endian" means the
+ /// most significant byte is on the right end of a word.
+ ///
+ /// true if this converter is little-endian, false otherwise.
+ public abstract bool IsLittleEndian();
+
+ ///
+ /// Gets the byte order ("endianness") in which data is converted using this class.
+ ///
+ public abstract Endianness Endianness { get; }
+ #endregion
+
+ #region Factory properties
+ ///
+ /// The little-endian bit converter.
+ ///
+ private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter();
+
+ ///
+ /// Gets a little-endian bit converter instance. The same instance is
+ /// always returned.
+ ///
+ public static LittleEndianBitConverter Little => LittleConverter;
+
+ ///
+ /// The big-endian bit converter.
+ ///
+ private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter();
+
+ ///
+ /// Gets a big-endian bit converter instance. The same instance is
+ /// always returned.
+ ///
+ public static BigEndianBitConverter Big => BigConverter;
+ #endregion
+
+ #region Double/primitive conversions
+ ///
+ /// Converts the specified double-precision floating point number to a
+ /// 64-bit signed integer. Note: the endianness of this converter does not
+ /// affect the returned value.
+ ///
+ /// The number to convert.
+ /// A 64-bit signed integer whose value is equivalent to value.
+ public long DoubleToInt64Bits(double value)
+ {
+ return BitConverter.DoubleToInt64Bits(value);
+ }
+
+ ///
+ /// Converts the specified 64-bit signed integer to a double-precision
+ /// floating point number. Note: the endianness of this converter does not
+ /// affect the returned value.
+ ///
+ /// The number to convert.
+ /// A double-precision floating point number whose value is equivalent to value.
+ public double Int64BitsToDouble(long value)
+ {
+ return BitConverter.Int64BitsToDouble(value);
+ }
+
+ ///
+ /// Converts the specified single-precision floating point number to a
+ /// 32-bit signed integer. Note: the endianness of this converter does not
+ /// affect the returned value.
+ ///
+ /// The number to convert.
+ /// A 32-bit signed integer whose value is equivalent to value.
+ public int SingleToInt32Bits(float value)
+ {
+ return new Int32SingleUnion(value).AsInt32;
+ }
+
+ ///
+ /// Converts the specified 32-bit signed integer to a single-precision floating point
+ /// number. Note: the endianness of this converter does not
+ /// affect the returned value.
+ ///
+ /// The number to convert.
+ /// A single-precision floating point number whose value is equivalent to value.
+ public float Int32BitsToSingle(int value)
+ {
+ return new Int32SingleUnion(value).AsSingle;
+ }
+ #endregion
+
+ #region To(PrimitiveType) conversions
+ ///
+ /// Returns a Boolean value converted from one byte at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// true if the byte at startIndex in value is nonzero; otherwise, false.
+ public bool ToBoolean(byte[] value, int startIndex)
+ {
+ CheckByteArgument(value, startIndex, 1);
+ return BitConverter.ToBoolean(value, startIndex);
+ }
+
+ ///
+ /// Returns a Unicode character converted from two bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A character formed by two bytes beginning at startIndex.
+ public char ToChar(byte[] value, int startIndex)
+ {
+ return unchecked((char)this.CheckedFromBytes(value, startIndex, 2));
+ }
+
+ ///
+ /// Returns a double-precision floating point number converted from eight bytes
+ /// at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A double precision floating point number formed by eight bytes beginning at startIndex.
+ public double ToDouble(byte[] value, int startIndex)
+ {
+ return this.Int64BitsToDouble(this.ToInt64(value, startIndex));
+ }
+
+ ///
+ /// Returns a single-precision floating point number converted from four bytes
+ /// at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A single precision floating point number formed by four bytes beginning at startIndex.
+ public float ToSingle(byte[] value, int startIndex)
+ {
+ return this.Int32BitsToSingle(this.ToInt32(value, startIndex));
+ }
+
+ ///
+ /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 16-bit signed integer formed by two bytes beginning at startIndex.
+ public short ToInt16(byte[] value, int startIndex)
+ {
+ return unchecked((short)this.CheckedFromBytes(value, startIndex, 2));
+ }
+
+ ///
+ /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 32-bit signed integer formed by four bytes beginning at startIndex.
+ public int ToInt32(byte[] value, int startIndex)
+ {
+ return unchecked((int)this.CheckedFromBytes(value, startIndex, 4));
+ }
+
+ ///
+ /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 64-bit signed integer formed by eight bytes beginning at startIndex.
+ public long ToInt64(byte[] value, int startIndex)
+ {
+ return this.CheckedFromBytes(value, startIndex, 8);
+ }
+
+ ///
+ /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 16-bit unsigned integer formed by two bytes beginning at startIndex.
+ public ushort ToUInt16(byte[] value, int startIndex)
+ {
+ return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2));
+ }
+
+ ///
+ /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 32-bit unsigned integer formed by four bytes beginning at startIndex.
+ public uint ToUInt32(byte[] value, int startIndex)
+ {
+ return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4));
+ }
+
+ ///
+ /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex.
+ public ulong ToUInt64(byte[] value, int startIndex)
+ {
+ return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8));
+ }
+
+ ///
+ /// Convert the given number of bytes from the given array, from the given start
+ /// position, into a long, using the bytes as the least significant part of the long.
+ /// By the time this is called, the arguments have been checked for validity.
+ ///
+ /// The bytes to convert
+ /// The index of the first byte to convert
+ /// The number of bytes to use in the conversion
+ /// The converted number
+ protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert);
+
+ ///
+ /// Checks the given argument for validity.
+ ///
+ /// The byte array passed in
+ /// The start index passed in
+ /// The number of bytes required
+ /// value is a null reference
+ ///
+ /// startIndex is less than zero or greater than the length of value minus bytesRequired.
+ ///
+ [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")]
+ private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if (startIndex < 0 || startIndex > value.Length - bytesRequired)
+ {
+ throw new ArgumentOutOfRangeException(nameof(startIndex));
+ }
+ }
+
+ ///
+ /// Checks the arguments for validity before calling FromBytes
+ /// (which can therefore assume the arguments are valid).
+ ///
+ /// The bytes to convert after checking
+ /// The index of the first byte to convert
+ /// The number of bytes to convert
+ /// The
+ private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert)
+ {
+ CheckByteArgument(value, startIndex, bytesToConvert);
+ return this.FromBytes(value, startIndex, bytesToConvert);
+ }
+ #endregion
+
+ #region ToString conversions
+ ///
+ /// Returns a String converted from the elements of a byte array.
+ ///
+ /// An array of bytes.
+ /// All the elements of value are converted.
+ ///
+ /// A String of hexadecimal pairs separated by hyphens, where each pair
+ /// represents the corresponding element in value; for example, "7F-2C-4A".
+ ///
+ public static string ToString(byte[] value)
+ {
+ return BitConverter.ToString(value);
+ }
+
+ ///
+ /// Returns a String converted from the elements of a byte array starting at a specified array position.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// The elements from array position startIndex to the end of the array are converted.
+ ///
+ /// A String of hexadecimal pairs separated by hyphens, where each pair
+ /// represents the corresponding element in value; for example, "7F-2C-4A".
+ ///
+ public static string ToString(byte[] value, int startIndex)
+ {
+ return BitConverter.ToString(value, startIndex);
+ }
+
+ ///
+ /// Returns a String converted from a specified number of bytes at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// The number of bytes to convert.
+ /// The length elements from array position startIndex are converted.
+ ///
+ /// A String of hexadecimal pairs separated by hyphens, where each pair
+ /// represents the corresponding element in value; for example, "7F-2C-4A".
+ ///
+ public static string ToString(byte[] value, int startIndex, int length)
+ {
+ return BitConverter.ToString(value, startIndex, length);
+ }
+ #endregion
+
+ #region Decimal conversions
+ ///
+ /// Returns a decimal value converted from sixteen bytes
+ /// at a specified position in a byte array.
+ ///
+ /// An array of bytes.
+ /// The starting position within value.
+ /// A decimal formed by sixteen bytes beginning at startIndex.
+ public decimal ToDecimal(byte[] value, int startIndex)
+ {
+ // HACK: This always assumes four parts, each in their own endianness,
+ // starting with the first part at the start of the byte array.
+ // On the other hand, there's no real format specified...
+ int[] parts = new int[4];
+ for (int i = 0; i < 4; i++)
+ {
+ parts[i] = this.ToInt32(value, startIndex + (i * 4));
+ }
+
+ return new decimal(parts);
+ }
+
+ ///
+ /// Returns the specified decimal value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 16.
+ public byte[] GetBytes(decimal value)
+ {
+ byte[] bytes = new byte[16];
+ int[] parts = decimal.GetBits(value);
+ for (int i = 0; i < 4; i++)
+ {
+ this.CopyBytesImpl(parts[i], 4, bytes, i * 4);
+ }
+
+ return bytes;
+ }
+
+ ///
+ /// Copies the specified decimal value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// A character to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(decimal value, byte[] buffer, int index)
+ {
+ int[] parts = decimal.GetBits(value);
+ for (int i = 0; i < 4; i++)
+ {
+ this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index);
+ }
+ }
+ #endregion
+
+ #region GetBytes conversions
+ ///
+ /// Returns an array with the given number of bytes formed
+ /// from the least significant bytes of the specified value.
+ /// This is used to implement the other GetBytes methods.
+ ///
+ /// The value to get bytes for
+ /// The number of significant bytes to return
+ ///
+ /// The .
+ ///
+ private byte[] GetBytes(long value, int bytes)
+ {
+ byte[] buffer = new byte[bytes];
+ this.CopyBytes(value, bytes, buffer, 0);
+ return buffer;
+ }
+
+ ///
+ /// Returns the specified Boolean value as an array of bytes.
+ ///
+ /// A Boolean value.
+ /// An array of bytes with length 1.
+ ///
+ /// The .
+ ///
+ public byte[] GetBytes(bool value)
+ {
+ return BitConverter.GetBytes(value);
+ }
+
+ ///
+ /// Returns the specified Unicode character value as an array of bytes.
+ ///
+ /// A character to convert.
+ /// An array of bytes with length 2.
+ ///
+ /// The .
+ ///
+ public byte[] GetBytes(char value)
+ {
+ return this.GetBytes(value, 2);
+ }
+
+ ///
+ /// Returns the specified double-precision floating point value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 8.
+ public byte[] GetBytes(double value)
+ {
+ return this.GetBytes(this.DoubleToInt64Bits(value), 8);
+ }
+
+ ///
+ /// Returns the specified 16-bit signed integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 2.
+ public byte[] GetBytes(short value)
+ {
+ return this.GetBytes(value, 2);
+ }
+
+ ///
+ /// Returns the specified 32-bit signed integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 4.
+ public byte[] GetBytes(int value)
+ {
+ return this.GetBytes(value, 4);
+ }
+
+ ///
+ /// Returns the specified 64-bit signed integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 8.
+ public byte[] GetBytes(long value)
+ {
+ return this.GetBytes(value, 8);
+ }
+
+ ///
+ /// Returns the specified single-precision floating point value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 4.
+ public byte[] GetBytes(float value)
+ {
+ return this.GetBytes(this.SingleToInt32Bits(value), 4);
+ }
+
+ ///
+ /// Returns the specified 16-bit unsigned integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 2.
+ public byte[] GetBytes(ushort value)
+ {
+ return this.GetBytes(value, 2);
+ }
+
+ ///
+ /// Returns the specified 32-bit unsigned integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 4.
+ public byte[] GetBytes(uint value)
+ {
+ return this.GetBytes(value, 4);
+ }
+
+ ///
+ /// Returns the specified 64-bit unsigned integer value as an array of bytes.
+ ///
+ /// The number to convert.
+ /// An array of bytes with length 8.
+ public byte[] GetBytes(ulong value)
+ {
+ return this.GetBytes(unchecked((long)value), 8);
+ }
+
+ #endregion
+
+ #region CopyBytes conversions
+ ///
+ /// Copies the given number of bytes from the least-specific
+ /// end of the specified value into the specified byte array, beginning
+ /// at the specified index.
+ /// This is used to implement the other CopyBytes methods.
+ ///
+ /// The value to copy bytes for
+ /// The number of significant bytes to copy
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ private void CopyBytes(long value, int bytes, byte[] buffer, int index)
+ {
+ if (buffer == null)
+ {
+ throw new ArgumentNullException(nameof(buffer), "Byte array must not be null");
+ }
+
+ if (buffer.Length < index + bytes)
+ {
+ throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value");
+ }
+
+ this.CopyBytesImpl(value, bytes, buffer, index);
+ }
+
+ ///
+ /// Copies the given number of bytes from the least-specific
+ /// end of the specified value into the specified byte array, beginning
+ /// at the specified index.
+ /// This must be implemented in concrete derived classes, but the implementation
+ /// may assume that the value will fit into the buffer.
+ ///
+ /// The value to copy bytes for
+ /// The number of significant bytes to copy
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index);
+
+ ///
+ /// Copies the specified Boolean value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// A Boolean value.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(bool value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value ? 1 : 0, 1, buffer, index);
+ }
+
+ ///
+ /// Copies the specified Unicode character value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// A character to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(char value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 2, buffer, index);
+ }
+
+ ///
+ /// Copies the specified double-precision floating point value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(double value, byte[] buffer, int index)
+ {
+ this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 16-bit signed integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(short value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 2, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 32-bit signed integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(int value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 4, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 64-bit signed integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(long value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 8, buffer, index);
+ }
+
+ ///
+ /// Copies the specified single-precision floating point value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(float value, byte[] buffer, int index)
+ {
+ this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 16-bit unsigned integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(ushort value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 2, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 32-bit unsigned integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(uint value, byte[] buffer, int index)
+ {
+ this.CopyBytes(value, 4, buffer, index);
+ }
+
+ ///
+ /// Copies the specified 64-bit unsigned integer value into the specified byte array,
+ /// beginning at the specified index.
+ ///
+ /// The number to convert.
+ /// The byte array to copy the bytes into
+ /// The first index into the array to copy the bytes into
+ public void CopyBytes(ulong value, byte[] buffer, int index)
+ {
+ this.CopyBytes(unchecked((long)value), 8, buffer, index);
+ }
+
+ #endregion
+
+ #region Private struct used for Single/Int32 conversions
+ ///
+ /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa.
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ private struct Int32SingleUnion
+ {
+ ///
+ /// Int32 version of the value.
+ ///
+ [FieldOffset(0)]
+ private readonly int i;
+
+ ///
+ /// Single version of the value.
+ ///
+ [FieldOffset(0)]
+ private readonly float f;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The integer value of the new instance.
+ internal Int32SingleUnion(int i)
+ {
+ this.f = 0; // Just to keep the compiler happy
+ this.i = i;
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ ///
+ /// The floating point value of the new instance.
+ ///
+ internal Int32SingleUnion(float f)
+ {
+ this.i = 0; // Just to keep the compiler happy
+ this.f = f;
+ }
+
+ ///
+ /// Gets the value of the instance as an integer.
+ ///
+ internal int AsInt32 => this.i;
+
+ ///
+ /// Gets the value of the instance as a floating point number.
+ ///
+ internal float AsSingle => this.f;
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/ImageProcessor/IO/Endianness.cs b/src/ImageProcessor/IO/Endianness.cs
new file mode 100644
index 0000000000..4ac861af05
--- /dev/null
+++ b/src/ImageProcessor/IO/Endianness.cs
@@ -0,0 +1,23 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.IO
+{
+ ///
+ /// Endianness of a converter
+ ///
+ internal enum Endianness
+ {
+ ///
+ /// Little endian - least significant byte first
+ ///
+ LittleEndian,
+
+ ///
+ /// Big endian - most significant byte first
+ ///
+ BigEndian
+ }
+}
diff --git a/src/ImageProcessor/IO/LittleEndianBitConverter.cs b/src/ImageProcessor/IO/LittleEndianBitConverter.cs
new file mode 100644
index 0000000000..58e1088602
--- /dev/null
+++ b/src/ImageProcessor/IO/LittleEndianBitConverter.cs
@@ -0,0 +1,47 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor.IO
+{
+ ///
+ /// Implementation of EndianBitConverter which converts to/from little-endian
+ /// byte arrays.
+ ///
+ /// Adapted from Miscellaneous Utility Library
+ /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see
+ /// .
+ ///
+ ///
+ internal sealed class LittleEndianBitConverter : EndianBitConverter
+ {
+ ///
+ public override Endianness Endianness => Endianness.LittleEndian;
+
+ ///
+ public override bool IsLittleEndian() => true;
+
+ ///
+ protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index)
+ {
+ for (int i = 0; i < bytes; i++)
+ {
+ buffer[i + index] = unchecked((byte)(value & 0xff));
+ value = value >> 8;
+ }
+ }
+
+ ///
+ protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert)
+ {
+ long ret = 0;
+ for (int i = 0; i < bytesToConvert; i++)
+ {
+ ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]);
+ }
+
+ return ret;
+ }
+ }
+}
\ No newline at end of file