From ff1b32053f9b8cd82825c94984797163f08ba7da Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 27 Jan 2016 21:08:14 +1100 Subject: [PATCH] Add endian tools for prep Former-commit-id: 0dad22cb5bc18a9a053c77c189950d711eaf8f02 Former-commit-id: afac0a345b441a0a9ee35818e4b479b89610bce8 Former-commit-id: 5390bc5686e8ed22e86399181e0b7bd857b12aaf --- .../IO/BigEndianBitConverter.cs | 48 ++ src/ImageProcessor/IO/EndianBinaryReader.cs | 616 +++++++++++++++ src/ImageProcessor/IO/EndianBitConverter.cs | 724 ++++++++++++++++++ src/ImageProcessor/IO/Endianness.cs | 23 + .../IO/LittleEndianBitConverter.cs | 47 ++ 5 files changed, 1458 insertions(+) create mode 100644 src/ImageProcessor/IO/BigEndianBitConverter.cs create mode 100644 src/ImageProcessor/IO/EndianBinaryReader.cs create mode 100644 src/ImageProcessor/IO/EndianBitConverter.cs create mode 100644 src/ImageProcessor/IO/Endianness.cs create mode 100644 src/ImageProcessor/IO/LittleEndianBitConverter.cs 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