mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
29 changed files with 439 additions and 1333 deletions
@ -1,177 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Diagnostics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Gif |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a byte of data in a GIF data stream which contains a number
|
|||
/// of data items.
|
|||
/// </summary>
|
|||
internal readonly struct PackedField : IEquatable<PackedField> |
|||
{ |
|||
/// <summary>
|
|||
/// The individual bits representing the packed byte.
|
|||
/// </summary>
|
|||
private static readonly bool[] Bits = new bool[8]; |
|||
|
|||
/// <summary>
|
|||
/// Gets the byte which represents the data items held in this instance.
|
|||
/// </summary>
|
|||
public byte Byte |
|||
{ |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
get |
|||
{ |
|||
int returnValue = 0; |
|||
int bitShift = 7; |
|||
foreach (bool bit in Bits) |
|||
{ |
|||
int bitValue; |
|||
if (bit) |
|||
{ |
|||
bitValue = 1 << bitShift; |
|||
} |
|||
else |
|||
{ |
|||
bitValue = 0; |
|||
} |
|||
|
|||
returnValue |= bitValue; |
|||
bitShift--; |
|||
} |
|||
|
|||
return Convert.ToByte(returnValue & 0xFF); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a new <see cref="PackedField"/> with the bits in the packed fields to
|
|||
/// the corresponding bits from the supplied byte.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to pack.</param>
|
|||
/// <returns>The <see cref="PackedField"/></returns>
|
|||
public static PackedField FromInt(byte value) |
|||
{ |
|||
PackedField packed = default; |
|||
packed.SetBits(0, 8, value); |
|||
return packed; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the specified bit within the packed fields to the supplied
|
|||
/// value.
|
|||
/// </summary>
|
|||
/// <param name="index">
|
|||
/// The zero-based index within the packed fields of the bit to set.
|
|||
/// </param>
|
|||
/// <param name="valueToSet">
|
|||
/// The value to set the bit to.
|
|||
/// </param>
|
|||
public void SetBit(int index, bool valueToSet) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(index, 0, 7, nameof(index)); |
|||
Bits[index] = valueToSet; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the specified bits within the packed fields to the supplied
|
|||
/// value.
|
|||
/// </summary>
|
|||
/// <param name="startIndex">The zero-based index within the packed fields of the first bit to set.</param>
|
|||
/// <param name="length">The number of bits to set.</param>
|
|||
/// <param name="valueToSet">The value to set the bits to.</param>
|
|||
public void SetBits(int startIndex, int length, int valueToSet) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(startIndex, 0, 7, nameof(startIndex)); |
|||
DebugCheckLength(startIndex, length); |
|||
|
|||
int bitShift = length - 1; |
|||
for (int i = startIndex; i < startIndex + length; i++) |
|||
{ |
|||
int bitValueIfSet = 1 << bitShift; |
|||
int bitValue = valueToSet & bitValueIfSet; |
|||
int bitIsSet = bitValue >> bitShift; |
|||
Bits[i] = bitIsSet == 1; |
|||
bitShift--; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the specified bit within the byte.
|
|||
/// </summary>
|
|||
/// <param name="index">The zero-based index of the bit to get.</param>
|
|||
/// <returns>
|
|||
/// The value of the specified bit within the byte.
|
|||
/// </returns>
|
|||
public bool GetBit(int index) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(index, 0, 7, nameof(index)); |
|||
return Bits[index]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the value of the specified bits within the byte.
|
|||
/// </summary>
|
|||
/// <param name="startIndex">The zero-based index of the first bit to get.</param>
|
|||
/// <param name="length">The number of bits to get.</param>
|
|||
/// <returns>
|
|||
/// The value of the specified bits within the byte.
|
|||
/// </returns>
|
|||
public int GetBits(int startIndex, int length) |
|||
{ |
|||
DebugGuard.MustBeBetweenOrEqualTo(startIndex, 1, 8, nameof(startIndex)); |
|||
DebugCheckLength(startIndex, length); |
|||
|
|||
int returnValue = 0; |
|||
int bitShift = length - 1; |
|||
for (int i = startIndex; i < startIndex + length; i++) |
|||
{ |
|||
int bitValue = (Bits[i] ? 1 : 0) << bitShift; |
|||
returnValue += bitValue; |
|||
bitShift--; |
|||
} |
|||
|
|||
return returnValue; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override bool Equals(object obj) |
|||
{ |
|||
return obj is PackedField other && this.Equals(other); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public bool Equals(PackedField other) |
|||
{ |
|||
return this.Byte.Equals(other.Byte); |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override string ToString() |
|||
{ |
|||
return $"PackedField [ Byte={this.Byte} ]"; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public override int GetHashCode() |
|||
{ |
|||
return this.Byte.GetHashCode(); |
|||
} |
|||
|
|||
[Conditional("DEBUG")] |
|||
private static void DebugCheckLength(int startIndex, int length) |
|||
{ |
|||
if (length < 1 || startIndex + length > 8) |
|||
{ |
|||
string message = "Length must be greater than zero and the sum of length and start index must be less than 8. " |
|||
+ $"Supplied length: {length}. Supplied start index: {startIndex}"; |
|||
|
|||
throw new ArgumentOutOfRangeException(nameof(length), message); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Gif |
|||
{ |
|||
/// <summary>
|
|||
/// The Graphic Control Extension contains parameters used when
|
|||
/// processing a graphic rendering block.
|
|||
/// </summary>
|
|||
[StructLayout(LayoutKind.Sequential, Pack = 1)] |
|||
internal readonly struct GifGraphicControlExtension : IGifExtension |
|||
{ |
|||
public GifGraphicControlExtension( |
|||
byte packed, |
|||
ushort delayTime, |
|||
byte transparencyIndex) |
|||
{ |
|||
this.BlockSize = 4; |
|||
this.Packed = packed; |
|||
this.DelayTime = delayTime; |
|||
this.TransparencyIndex = transparencyIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the size of the block.
|
|||
/// </summary>
|
|||
public byte BlockSize { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the packed disposalMethod and transparencyFlag value.
|
|||
/// </summary>
|
|||
public byte Packed { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the delay time in of hundredths (1/100) of a second
|
|||
/// to wait before continuing with the processing of the Data Stream.
|
|||
/// The clock starts ticking immediately after the graphic is rendered.
|
|||
/// </summary>
|
|||
public ushort DelayTime { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transparency index.
|
|||
/// The Transparency Index is such that when encountered, the corresponding pixel
|
|||
/// of the display device is not modified and processing goes on to the next pixel.
|
|||
/// </summary>
|
|||
public byte TransparencyIndex { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the disposal method which indicates the way in which the
|
|||
/// graphic is to be treated after being displayed.
|
|||
/// </summary>
|
|||
public DisposalMethod DisposalMethod => (DisposalMethod)((this.Packed & 0x1C) >> 2); |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether transparency flag is to be set.
|
|||
/// This indicates whether a transparency index is given in the Transparent Index field.
|
|||
/// </summary>
|
|||
public bool TransparencyFlag => (this.Packed & 0x01) == 1; |
|||
|
|||
byte IGifExtension.Label => GifConstants.GraphicControlLabel; |
|||
|
|||
public int WriteTo(Span<byte> buffer) |
|||
{ |
|||
ref GifGraphicControlExtension dest = ref Unsafe.As<byte, GifGraphicControlExtension>(ref MemoryMarshal.GetReference(buffer)); |
|||
|
|||
dest = this; |
|||
|
|||
return 5; |
|||
} |
|||
|
|||
public static GifGraphicControlExtension Parse(ReadOnlySpan<byte> buffer) |
|||
{ |
|||
return MemoryMarshal.Cast<byte, GifGraphicControlExtension>(buffer)[0]; |
|||
} |
|||
|
|||
public static byte GetPackedValue(DisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false) |
|||
{ |
|||
/* |
|||
Reserved | 3 Bits |
|||
Disposal Method | 3 Bits |
|||
User Input Flag | 1 Bit |
|||
Transparent Color Flag | 1 Bit |
|||
*/ |
|||
|
|||
byte value = 0; |
|||
|
|||
value |= (byte)((int)disposalMethod << 2); |
|||
|
|||
if (userInputFlag) |
|||
{ |
|||
value |= 1 << 1; |
|||
} |
|||
|
|||
if (transparencyFlag) |
|||
{ |
|||
value |= 1; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
} |
|||
} |
|||
@ -1,94 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Gif |
|||
{ |
|||
/// <summary>
|
|||
/// The Graphic Control Extension contains parameters used when
|
|||
/// processing a graphic rendering block.
|
|||
/// </summary>
|
|||
internal readonly struct GifGraphicsControlExtension |
|||
{ |
|||
public const int Size = 8; |
|||
|
|||
public GifGraphicsControlExtension( |
|||
DisposalMethod disposalMethod, |
|||
bool transparencyFlag, |
|||
ushort delayTime, |
|||
byte transparencyIndex) |
|||
{ |
|||
this.DisposalMethod = disposalMethod; |
|||
this.TransparencyFlag = transparencyFlag; |
|||
this.DelayTime = delayTime; |
|||
this.TransparencyIndex = transparencyIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the disposal method which indicates the way in which the
|
|||
/// graphic is to be treated after being displayed.
|
|||
/// </summary>
|
|||
public DisposalMethod DisposalMethod { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether transparency flag is to be set.
|
|||
/// This indicates whether a transparency index is given in the Transparent Index field.
|
|||
/// (This field is the least significant bit of the byte.)
|
|||
/// </summary>
|
|||
public bool TransparencyFlag { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the delay time.
|
|||
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
|
|||
/// wait before continuing with the processing of the Data Stream.
|
|||
/// The clock starts ticking immediately after the graphic is rendered.
|
|||
/// </summary>
|
|||
public ushort DelayTime { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the transparency index.
|
|||
/// The Transparency Index is such that when encountered, the corresponding pixel
|
|||
/// of the display device is not modified and processing goes on to the next pixel.
|
|||
/// </summary>
|
|||
public byte TransparencyIndex { get; } |
|||
|
|||
public byte PackField() |
|||
{ |
|||
PackedField field = default; |
|||
|
|||
field.SetBits(3, 3, (int)this.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal
|
|||
|
|||
// TODO: Allow this as an option.
|
|||
field.SetBit(6, false); // 7 : User input - 0 = none
|
|||
field.SetBit(7, this.TransparencyFlag); // 8: Has transparent.
|
|||
|
|||
return field.Byte; |
|||
} |
|||
|
|||
public void WriteTo(Span<byte> buffer) |
|||
{ |
|||
buffer[0] = GifConstants.ExtensionIntroducer; |
|||
buffer[1] = GifConstants.GraphicControlLabel; |
|||
buffer[2] = 4; // Block Size
|
|||
buffer[3] = this.PackField(); // Packed Field
|
|||
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(4, 2), this.DelayTime); // Delay Time
|
|||
buffer[6] = this.TransparencyIndex; |
|||
buffer[7] = GifConstants.Terminator; |
|||
} |
|||
|
|||
public static GifGraphicsControlExtension Parse(ReadOnlySpan<byte> buffer) |
|||
{ |
|||
// We've already read the Extension Introducer introducer & Graphic Control Label
|
|||
// Start from the block size (0)
|
|||
byte packed = buffer[1]; |
|||
|
|||
return new GifGraphicsControlExtension( |
|||
disposalMethod: (DisposalMethod)((packed & 0x1C) >> 2), |
|||
delayTime: BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2, 2)), |
|||
transparencyIndex: buffer[4], |
|||
transparencyFlag: (packed & 0x01) == 1); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
|
|||
namespace SixLabors.ImageSharp.Formats.Gif |
|||
{ |
|||
/// <summary>
|
|||
/// A base interface for GIF extensions.
|
|||
/// </summary>
|
|||
public interface IGifExtension |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the label identifying the extensions.
|
|||
/// </summary>
|
|||
byte Label { get; } |
|||
|
|||
/// <summary>
|
|||
/// Writes the extension data to the buffer.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to write the extension to.</param>
|
|||
/// <returns>The number of bytes written to the buffer.</returns>
|
|||
int WriteTo(Span<byte> buffer); |
|||
} |
|||
} |
|||
@ -1,364 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace SixLabors.ImageSharp.IO |
|||
{ |
|||
/// <summary>
|
|||
/// Equivalent of <see cref="BinaryReader"/>, but with either endianness.
|
|||
/// No data is buffered in the reader; the client may seek within the stream at will.
|
|||
/// </summary>
|
|||
internal class EndianBinaryReader : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Buffer used for temporary storage before conversion into primitives
|
|||
/// </summary>
|
|||
private readonly byte[] storageBuffer = new byte[16]; |
|||
|
|||
/// <summary>
|
|||
/// Whether or not this reader has been disposed yet.
|
|||
/// </summary>
|
|||
private bool disposed; |
|||
|
|||
/// <summary>
|
|||
/// The endianness used to read data
|
|||
/// </summary>
|
|||
private readonly Endianness endianness; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EndianBinaryReader"/> class.
|
|||
/// Constructs a new binary reader with the given bit converter, reading
|
|||
/// to the given stream, using the given encoding.
|
|||
/// </summary>
|
|||
/// <param name="endianness">Endianness to use when reading data</param>
|
|||
/// <param name="stream">Stream to read data from</param>
|
|||
public EndianBinaryReader(Endianness endianness, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
Guard.IsTrue(stream.CanRead, nameof(stream), "Stream isn't readable"); |
|||
|
|||
this.BaseStream = stream; |
|||
this.endianness = endianness; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying stream of the EndianBinaryReader.
|
|||
/// </summary>
|
|||
public Stream BaseStream { get; } |
|||
|
|||
/// <summary>
|
|||
/// Closes the reader, including the underlying stream.
|
|||
/// </summary>
|
|||
public void Close() |
|||
{ |
|||
this.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Seeks within the stream.
|
|||
/// </summary>
|
|||
/// <param name="offset">Offset to seek to.</param>
|
|||
/// <param name="origin">Origin of seek operation.</param>
|
|||
public void Seek(int offset, SeekOrigin origin) |
|||
{ |
|||
this.CheckDisposed(); |
|||
this.BaseStream.Seek(offset, origin); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a single byte from the stream.
|
|||
/// </summary>
|
|||
/// <returns>The byte read</returns>
|
|||
public byte ReadByte() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 1); |
|||
return this.storageBuffer[0]; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a single signed byte from the stream.
|
|||
/// </summary>
|
|||
/// <returns>The byte read</returns>
|
|||
public sbyte ReadSByte() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 1); |
|||
return unchecked((sbyte)this.storageBuffer[0]); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a boolean from the stream. 1 byte is read.
|
|||
/// </summary>
|
|||
/// <returns>The boolean read</returns>
|
|||
public bool ReadBoolean() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 1); |
|||
|
|||
return this.storageBuffer[0] != 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 16-bit signed integer from the stream, using the bit converter
|
|||
/// for this reader. 2 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 16-bit integer read</returns>
|
|||
public short ReadInt16() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 2); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadInt16BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadInt16LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 32-bit signed integer from the stream, using the bit converter
|
|||
/// for this reader. 4 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 32-bit integer read</returns>
|
|||
public int ReadInt32() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 4); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadInt32BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadInt32LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 64-bit signed integer from the stream, using the bit converter
|
|||
/// for this reader. 8 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 64-bit integer read</returns>
|
|||
public long ReadInt64() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 8); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadInt64BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadInt64LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 16-bit unsigned integer from the stream, using the bit converter
|
|||
/// for this reader. 2 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 16-bit unsigned integer read</returns>
|
|||
public ushort ReadUInt16() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 2); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadUInt16BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadUInt16LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 32-bit unsigned integer from the stream, using the bit converter
|
|||
/// for this reader. 4 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 32-bit unsigned integer read</returns>
|
|||
public uint ReadUInt32() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 4); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadUInt32BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadUInt32LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a 64-bit unsigned integer from the stream, using the bit converter
|
|||
/// for this reader. 8 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The 64-bit unsigned integer read</returns>
|
|||
public ulong ReadUInt64() |
|||
{ |
|||
this.ReadInternal(this.storageBuffer, 8); |
|||
|
|||
return (this.endianness == Endianness.BigEndian) |
|||
? BinaryPrimitives.ReadUInt64BigEndian(this.storageBuffer) |
|||
: BinaryPrimitives.ReadUInt64LittleEndian(this.storageBuffer); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a single-precision floating-point value from the stream, using the bit converter
|
|||
/// for this reader. 4 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The floating point value read</returns>
|
|||
public unsafe float ReadSingle() |
|||
{ |
|||
int intValue = this.ReadInt32(); |
|||
|
|||
return *((float*)&intValue); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads a double-precision floating-point value from the stream, using the bit converter
|
|||
/// for this reader. 8 bytes are read.
|
|||
/// </summary>
|
|||
/// <returns>The floating point value read</returns>
|
|||
public unsafe double ReadDouble() |
|||
{ |
|||
long value = this.ReadInt64(); |
|||
|
|||
return *((double*)&value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the specified number of bytes into the given buffer, starting at
|
|||
/// the given index.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer to copy data into</param>
|
|||
/// <param name="index">The first index to copy data into</param>
|
|||
/// <param name="count">The number of bytes to read</param>
|
|||
/// <returns>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.
|
|||
/// </returns>
|
|||
public int Read(byte[] buffer, int index, int count) |
|||
{ |
|||
this.CheckDisposed(); |
|||
|
|||
Guard.NotNull(this.storageBuffer, nameof(this.storageBuffer)); |
|||
Guard.MustBeGreaterThanOrEqualTo(index, 0, nameof(index)); |
|||
Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count)); |
|||
Guard.IsFalse(count + index > buffer.Length, nameof(buffer.Length), "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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bytes to read</param>
|
|||
/// <returns>The bytes read</returns>
|
|||
public byte[] ReadBytes(int count) |
|||
{ |
|||
this.CheckDisposed(); |
|||
Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(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; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="count">The number of bytes to read</param>
|
|||
/// <returns>The bytes read</returns>
|
|||
public byte[] ReadBytesOrThrow(int count) |
|||
{ |
|||
byte[] ret = new byte[count]; |
|||
this.ReadInternal(ret, count); |
|||
return ret; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes of the underlying stream.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
if (!this.disposed) |
|||
{ |
|||
this.disposed = true; |
|||
((IDisposable)this.BaseStream).Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks whether or not the reader has been disposed, throwing an exception if so.
|
|||
/// </summary>
|
|||
private void CheckDisposed() |
|||
{ |
|||
if (this.disposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(EndianBinaryReader)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reads the given number of bytes from the stream, throwing an exception
|
|||
/// if they can't all be read.
|
|||
/// </summary>
|
|||
/// <param name="data">Buffer to read into</param>
|
|||
/// <param name="size">Number of bytes to read</param>
|
|||
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($"End of stream reached with {size - index} byte{(size - index == 1 ? "s" : string.Empty)} left to read."); |
|||
} |
|||
|
|||
index += read; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 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.
|
|||
/// </summary>
|
|||
/// <param name="data">Buffer to read into</param>
|
|||
/// <param name="size">Number of bytes to read</param>
|
|||
/// <returns>Number of bytes actually read</returns>
|
|||
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; |
|||
} |
|||
} |
|||
} |
|||
@ -1,303 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers.Binary; |
|||
using System.IO; |
|||
|
|||
namespace SixLabors.ImageSharp.IO |
|||
{ |
|||
/// <summary>
|
|||
/// Equivalent of <see cref="BinaryWriter"/>, but with either endianness
|
|||
/// </summary>
|
|||
internal class EndianBinaryWriter : IDisposable |
|||
{ |
|||
/// <summary>
|
|||
/// Buffer used for temporary storage during conversion from primitives
|
|||
/// </summary>
|
|||
private readonly byte[] buffer = new byte[16]; |
|||
|
|||
/// <summary>
|
|||
/// The endianness used to write the data
|
|||
/// </summary>
|
|||
private readonly Endianness endianness; |
|||
|
|||
/// <summary>
|
|||
/// Whether or not this writer has been disposed yet.
|
|||
/// </summary>
|
|||
private bool disposed; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="EndianBinaryWriter"/> class
|
|||
/// with the given bit converter, writing to the given stream, using the given encoding.
|
|||
/// </summary>
|
|||
/// <param name="endianness">Endianness to use when writing data</param>
|
|||
/// <param name="stream">Stream to write data to</param>
|
|||
public EndianBinaryWriter(Endianness endianness, Stream stream) |
|||
{ |
|||
Guard.NotNull(stream, nameof(stream)); |
|||
Guard.IsTrue(stream.CanWrite, nameof(stream), "Stream isn't writable"); |
|||
|
|||
this.BaseStream = stream; |
|||
this.endianness = endianness; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the underlying stream of the EndianBinaryWriter.
|
|||
/// </summary>
|
|||
public Stream BaseStream { get; } |
|||
|
|||
/// <summary>
|
|||
/// Closes the writer, including the underlying stream.
|
|||
/// </summary>
|
|||
public void Close() |
|||
{ |
|||
this.Dispose(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Flushes the underlying stream.
|
|||
/// </summary>
|
|||
public void Flush() |
|||
{ |
|||
this.CheckDisposed(); |
|||
this.BaseStream.Flush(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Seeks within the stream.
|
|||
/// </summary>
|
|||
/// <param name="offset">Offset to seek to.</param>
|
|||
/// <param name="origin">Origin of seek operation.</param>
|
|||
public void Seek(int offset, SeekOrigin origin) |
|||
{ |
|||
this.CheckDisposed(); |
|||
this.BaseStream.Seek(offset, origin); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a boolean value to the stream. 1 byte is written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(bool value) |
|||
{ |
|||
this.buffer[0] = value ? (byte)1 : (byte)0; |
|||
|
|||
this.WriteInternal(this.buffer, 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 16-bit signed integer to the stream, using the bit converter
|
|||
/// for this writer. 2 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(short value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteInt16BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteInt16LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 32-bit signed integer to the stream, using the bit converter
|
|||
/// for this writer. 4 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(int value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteInt32BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteInt32LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 4); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 64-bit signed integer to the stream, using the bit converter
|
|||
/// for this writer. 8 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(long value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteInt64BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteInt64LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 8); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 16-bit unsigned integer to the stream, using the bit converter
|
|||
/// for this writer. 2 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(ushort value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteUInt16BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 32-bit unsigned integer to the stream, using the bit converter
|
|||
/// for this writer. 4 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(uint value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteUInt32LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 4); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a 64-bit unsigned integer to the stream, using the bit converter
|
|||
/// for this writer. 8 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(ulong value) |
|||
{ |
|||
if (this.endianness == Endianness.BigEndian) |
|||
{ |
|||
BinaryPrimitives.WriteUInt64BigEndian(this.buffer, value); |
|||
} |
|||
else |
|||
{ |
|||
BinaryPrimitives.WriteUInt64LittleEndian(this.buffer, value); |
|||
} |
|||
|
|||
this.WriteInternal(this.buffer, 8); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a single-precision floating-point value to the stream, using the bit converter
|
|||
/// for this writer. 4 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public unsafe void Write(float value) |
|||
{ |
|||
this.Write(*((int*)&value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a double-precision floating-point value to the stream, using the bit converter
|
|||
/// for this writer. 8 bytes are written.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public unsafe void Write(double value) |
|||
{ |
|||
this.Write(*((long*)&value)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a signed byte to the stream.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(byte value) |
|||
{ |
|||
this.buffer[0] = value; |
|||
this.WriteInternal(this.buffer, 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes an unsigned byte to the stream.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to write</param>
|
|||
public void Write(sbyte value) |
|||
{ |
|||
this.buffer[0] = unchecked((byte)value); |
|||
this.WriteInternal(this.buffer, 1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes an array of bytes to the stream.
|
|||
/// </summary>
|
|||
/// <param name="value">The values to write</param>
|
|||
/// <exception cref="ArgumentNullException">value is null</exception>
|
|||
public void Write(byte[] value) |
|||
{ |
|||
Guard.NotNull(value, nameof(value)); |
|||
this.WriteInternal(value, value.Length); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes a portion of an array of bytes to the stream.
|
|||
/// </summary>
|
|||
/// <param name="value">An array containing the bytes to write</param>
|
|||
/// <param name="offset">The index of the first byte to write within the array</param>
|
|||
/// <param name="count">The number of bytes to write</param>
|
|||
/// <exception cref="ArgumentNullException">value is null</exception>
|
|||
public void Write(byte[] value, int offset, int count) |
|||
{ |
|||
this.CheckDisposed(); |
|||
this.BaseStream.Write(value, offset, count); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes of the underlying stream.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
if (!this.disposed) |
|||
{ |
|||
this.Flush(); |
|||
this.disposed = true; |
|||
((IDisposable)this.BaseStream).Dispose(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks whether or not the writer has been disposed, throwing an exception if so.
|
|||
/// </summary>
|
|||
private void CheckDisposed() |
|||
{ |
|||
if (this.disposed) |
|||
{ |
|||
throw new ObjectDisposedException(nameof(EndianBinaryWriter)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Writes the specified number of bytes from the start of the given byte array,
|
|||
/// after checking whether or not the writer has been disposed.
|
|||
/// </summary>
|
|||
/// <param name="bytes">The array of bytes to write from</param>
|
|||
/// <param name="length">The number of bytes to write</param>
|
|||
private void WriteInternal(byte[] bytes, int length) |
|||
{ |
|||
this.CheckDisposed(); |
|||
this.BaseStream.Write(bytes, 0, length); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Gif |
|||
{ |
|||
public class GifGraphicControlExtensionTests |
|||
{ |
|||
[Fact] |
|||
public void TestPackedValue() |
|||
{ |
|||
Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(DisposalMethod.Unspecified, false, false)); |
|||
Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(DisposalMethod.RestoreToBackground, true, true)); |
|||
Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(DisposalMethod.NotDispose, false, false)); |
|||
Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(DisposalMethod.RestoreToPrevious, true, false)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Gif |
|||
{ |
|||
public class GifImageDescriptorTests |
|||
{ |
|||
[Fact] |
|||
public void TestPackedValue() |
|||
{ |
|||
Assert.Equal(128, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable
|
|||
Assert.Equal(64, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag
|
|||
Assert.Equal(32, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag
|
|||
Assert.Equal(224, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all
|
|||
Assert.Equal(7, GifImageDescriptor.GetPackedValue(false, false, false, 8)); |
|||
Assert.Equal(227, GifImageDescriptor.GetPackedValue(true, true, true, 4)); |
|||
Assert.Equal(231, GifImageDescriptor.GetPackedValue(true, true, true, 8)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using SixLabors.ImageSharp.Formats.Gif; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Formats.Gif |
|||
{ |
|||
public class GifLogicalScreenDescriptorTests |
|||
{ |
|||
[Fact] |
|||
public void TestPackedValue() |
|||
{ |
|||
Assert.Equal(0, GifLogicalScreenDescriptor.GetPackedValue(false, 0, false, 0)); |
|||
Assert.Equal(128, GifLogicalScreenDescriptor.GetPackedValue(true, 0, false, 0)); // globalColorTableFlag
|
|||
Assert.Equal(8, GifLogicalScreenDescriptor.GetPackedValue(false, 0, true, 0)); // sortFlag
|
|||
Assert.Equal(48, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 0)); |
|||
Assert.Equal(155, GifLogicalScreenDescriptor.GetPackedValue(true, 1, true, 3)); |
|||
Assert.Equal(55, GifLogicalScreenDescriptor.GetPackedValue(false, 3, false, 7)); |
|||
} |
|||
} |
|||
} |
|||
@ -1,97 +0,0 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.IO; |
|||
using SixLabors.ImageSharp.IO; |
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.IO |
|||
{ |
|||
public class EndianBinaryReaderWriterTests |
|||
{ |
|||
[Fact] |
|||
public void RoundtripSingles() |
|||
{ |
|||
foreach ((Endianness endianness, byte[] bytes) in new[] { |
|||
(Endianness.BigEndian, new byte[] { 64, 73, 15, 219 }), |
|||
(Endianness.LittleEndian, new byte[] { 219, 15, 73, 64 }) |
|||
}) |
|||
{ |
|||
var stream = new MemoryStream(); |
|||
|
|||
using (var writer = new EndianBinaryWriter(endianness, stream)) |
|||
{ |
|||
writer.Write((float)Math.PI); |
|||
|
|||
Assert.Equal(bytes, stream.ToArray()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void RoundtripDoubles() |
|||
{ |
|||
foreach ((Endianness endianness, byte[] bytes) in new[] { |
|||
(Endianness.BigEndian, new byte[] { 64, 9, 33, 251, 84, 68, 45, 24 }), |
|||
(Endianness.LittleEndian, new byte[] { 24, 45, 68, 84, 251, 33, 9, 64 }) |
|||
}) |
|||
{ |
|||
var stream = new MemoryStream(); |
|||
|
|||
using (var writer = new EndianBinaryWriter(endianness, stream)) |
|||
{ |
|||
writer.Write(Math.PI); |
|||
|
|||
Assert.Equal(bytes, stream.ToArray()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures that the data written through a binary writer can be read back through the reader
|
|||
/// </summary>
|
|||
[Fact] |
|||
public void RoundtripValues() |
|||
{ |
|||
foreach (Endianness endianness in new[] { Endianness.BigEndian, Endianness.LittleEndian }) |
|||
{ |
|||
var stream = new MemoryStream(); |
|||
|
|||
var writer = new EndianBinaryWriter(endianness, stream); |
|||
|
|||
writer.Write(true); // Bool
|
|||
writer.Write((byte)1); // Byte
|
|||
writer.Write((short)1); // Int16
|
|||
writer.Write(1); // Int32
|
|||
writer.Write(1L); // Int64
|
|||
writer.Write(1f); // Single
|
|||
writer.Write(1d); // Double
|
|||
writer.Write((sbyte)1); // SByte
|
|||
writer.Write((ushort)1); // UInt16
|
|||
writer.Write((uint)1); // UInt32
|
|||
writer.Write(1UL); // ULong
|
|||
|
|||
Assert.Equal(43, stream.Length); |
|||
|
|||
stream.Position = 0; |
|||
|
|||
var reader = new EndianBinaryReader(endianness, stream); |
|||
|
|||
Assert.True(reader.ReadBoolean()); // Bool
|
|||
Assert.Equal((byte)1, reader.ReadByte()); // Byte
|
|||
Assert.Equal((short)1, reader.ReadInt16()); // Int16
|
|||
Assert.Equal(1, reader.ReadInt32()); // Int32
|
|||
Assert.Equal(1L, reader.ReadInt64()); // Int64
|
|||
Assert.Equal(1f, reader.ReadSingle()); // Single
|
|||
Assert.Equal(1d, reader.ReadDouble()); // Double
|
|||
Assert.Equal((sbyte)1, reader.ReadSByte()); // SByte
|
|||
Assert.Equal((ushort)1, reader.ReadUInt16()); // UInt16
|
|||
Assert.Equal((uint)1, reader.ReadUInt32()); // UInt32
|
|||
Assert.Equal(1UL, reader.ReadUInt64()); // ULong
|
|||
|
|||
stream.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue