Browse Source

Merge pull request #536 from carbon/gif-cleanup

Improve Gif CQ, Part 2
af/merge-core
James Jackson-South 8 years ago
committed by GitHub
parent
commit
cfa441fdaa
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      src/ImageSharp/Formats/Gif/DisposalMethod.cs
  2. 2
      src/ImageSharp/Formats/Gif/GifConstants.cs
  3. 34
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  4. 93
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  5. 8
      src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
  6. 2
      src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
  7. 6
      src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
  8. 6
      src/ImageSharp/Formats/Gif/ImageExtensions.cs
  9. 43
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  10. 118
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  11. 177
      src/ImageSharp/Formats/Gif/PackedField.cs
  12. 106
      src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
  13. 94
      src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs
  14. 38
      src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
  15. 97
      src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs
  16. 22
      src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs
  17. 364
      src/ImageSharp/IO/EndianBinaryReader.cs
  18. 303
      src/ImageSharp/IO/EndianBinaryWriter.cs
  19. 3
      src/ImageSharp/ImageSharp.csproj
  20. 30
      src/ImageSharp/MetaData/ImageMetaData.cs
  21. 15
      src/ImageSharp/MetaData/ImageProperty.cs
  22. 2
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  23. 2
      tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
  24. 21
      tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
  25. 24
      tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs
  26. 23
      tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs
  27. 97
      tests/ImageSharp.Tests/IO/EndianBinaryReaderWriterTests.cs
  28. 11
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  29. 18
      tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

13
src/ImageSharp/Formats/Gif/DisposalMethod.cs

@ -11,23 +11,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
public enum DisposalMethod
{
/// <summary>
/// No disposal specified. The decoder is not required to take any action.
/// No disposal specified.
/// The decoder is not required to take any action.
/// </summary>
Unspecified = 0,
/// <summary>
/// Do not dispose. The graphic is to be left in place.
/// Do not dispose.
/// The graphic is to be left in place.
/// </summary>
NotDispose = 1,
/// <summary>
/// Restore to background color. The area used by the graphic must be restored to
/// the background color.
/// Restore to background color.
/// The area used by the graphic must be restored to the background color.
/// </summary>
RestoreToBackground = 2,
/// <summary>
/// Restore to previous. The decoder is required to restore the area overwritten by the
/// Restore to previous.
/// The decoder is required to restore the area overwritten by the
/// graphic with what was there prior to rendering the graphic.
/// </summary>
RestoreToPrevious = 3

2
src/ImageSharp/Formats/Gif/GifConstants.cs

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The application block size.
/// </summary>
public const byte ApplicationBlockSize = 0x0b;
public const byte ApplicationBlockSize = 11;
/// <summary>
/// The comment label.

34
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The currently loaded stream.
/// </summary>
private Stream currentStream;
private Stream stream;
/// <summary>
/// The global color table.
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The graphics control extension.
/// </summary>
private GifGraphicsControlExtension graphicsControlExtension;
private GifGraphicControlExtension graphicsControlExtension;
/// <summary>
/// The metadata
@ -236,9 +236,9 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private void ReadGraphicalControlExtension()
{
this.currentStream.Read(this.buffer, 0, 6);
this.stream.Read(this.buffer, 0, 6);
this.graphicsControlExtension = GifGraphicsControlExtension.Parse(this.buffer);
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
}
/// <summary>
@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <returns><see cref="GifImageDescriptor"/></returns>
private GifImageDescriptor ReadImageDescriptor()
{
this.currentStream.Read(this.buffer, 0, 9);
this.stream.Read(this.buffer, 0, 9);
return GifImageDescriptor.Parse(this.buffer);
}
@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private void ReadLogicalScreenDescriptor()
{
this.currentStream.Read(this.buffer, 0, 7);
this.stream.Read(this.buffer, 0, 7);
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
}
@ -268,13 +268,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="length">The number of bytes to skip.</param>
private void Skip(int length)
{
this.currentStream.Skip(length);
this.stream.Skip(length);
int flag;
while ((flag = this.currentStream.ReadByte()) != 0)
while ((flag = this.stream.ReadByte()) != 0)
{
this.currentStream.Skip(flag);
this.stream.Skip(flag);
}
}
@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int length;
while ((length = this.currentStream.ReadByte()) != 0)
while ((length = this.stream.ReadByte()) != 0)
{
if (length > GifConstants.MaxCommentLength)
{
@ -294,13 +294,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.IgnoreMetadata)
{
this.currentStream.Seek(length, SeekOrigin.Current);
this.stream.Seek(length, SeekOrigin.Current);
continue;
}
using (IManagedByteBuffer commentsBuffer = this.MemoryManager.AllocateManagedByteBuffer(length))
{
this.currentStream.Read(commentsBuffer.Array, 0, length);
this.stream.Read(commentsBuffer.Array, 0, length);
string comments = this.TextEncoding.GetString(commentsBuffer.Array, 0, length);
this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments));
}
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int length = imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryManager.AllocateManagedByteBuffer(length, true);
this.currentStream.Read(localColorTable.Array, 0, length);
this.stream.Read(localColorTable.Array, 0, length);
}
indices = this.configuration.MemoryManager.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, true);
@ -354,8 +354,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span<byte> indices)
{
int dataSize = this.currentStream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream))
int dataSize = this.stream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.stream))
{
lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices);
}
@ -526,10 +526,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.metaData = new ImageMetaData();
this.currentStream = stream;
this.stream = stream;
// Skip the identifier
this.currentStream.Skip(6);
this.stream.Skip(6);
this.ReadLogicalScreenDescriptor();
if (this.logicalScreenDescriptor.GlobalColorTableFlag)

93
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -4,50 +4,45 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Quantization;
// TODO: This is causing more GC collections than I'm happy with.
// This is likely due to the number of short writes to the stream we are doing.
// We should investigate reducing them since we know the length of the byte array we require for multiple parts.
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// Performs the gif encoding operation.
/// Implements the GIF encoding protocol.
/// </summary>
internal sealed class GifEncoderCore
{
private readonly MemoryManager memoryManager;
/// <summary>
/// The temp buffer used to reduce allocations.
/// A reusable buffer used to reduce allocations.
/// </summary>
private readonly byte[] buffer = new byte[16];
private readonly byte[] buffer = new byte[20];
/// <summary>
/// Gets the TextEncoding
/// Gets the text encoding used to write comments.
/// </summary>
private readonly Encoding textEncoding;
/// <summary>
/// Gets or sets the quantizer for reducing the color count.
/// Gets or sets the quantizer used to generate the color palette.
/// </summary>
private readonly IQuantizer quantizer;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// A flag indicating whether to ingore the metadata when writing the image.
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// The number of bits requires to store the image palette.
/// The number of bits requires to store the color palette.
/// </summary>
private int bitDepth;
@ -91,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.WriteLogicalScreenDescriptor(image, stream, index);
// Write the first frame.
this.WriteComments(image, stream);
this.WriteComments(image.MetaData, stream);
// Write additional frames.
if (image.Frames.Count > 1)
@ -133,7 +128,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
// Transparent pixels are much more likely to be found at the end of a palette
int index = -1;
var trans = default(Rgba32);
Rgba32 trans = default;
ref TPixel paletteRef = ref MemoryMarshal.GetReference(quantized.Palette.AsSpan());
for (int i = quantized.Palette.Length - 1; i >= 0; i--)
{
@ -168,13 +164,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, Stream stream, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(false, this.bitDepth - 1, false, this.bitDepth - 1);
var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width,
height: (ushort)image.Height,
bitsPerPixel: 0,
pixelAspectRatio: 0,
globalColorTableFlag: false, // TODO: Always false for now.
globalColorTableSize: this.bitDepth - 1,
packed: packedValue,
backgroundColorIndex: unchecked((byte)transparencyIndex));
descriptor.WriteTo(this.buffer);
@ -196,40 +191,37 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.buffer[1] = GifConstants.ApplicationExtensionLabel;
this.buffer[2] = GifConstants.ApplicationBlockSize;
stream.Write(this.buffer, 0, 3);
stream.Write(GifConstants.ApplicationIdentificationBytes, 0, 11); // NETSCAPE2.0
// Write NETSCAPE2.0
GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11));
this.buffer[0] = 3; // Application block length
this.buffer[1] = 1; // Data sub-block index (always 1)
// Application Data ----
this.buffer[14] = 3; // Application block length
this.buffer[15] = 1; // Data sub-block index (always 1)
// 0 means loop indefinitely. Count is set as play n + 1 times.
repeatCount = (ushort)Math.Max(0, repeatCount - 1);
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(2, 2), repeatCount); // Repeat count for images.
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images.
this.buffer[4] = GifConstants.Terminator; // Terminator
this.buffer[18] = GifConstants.Terminator; // Terminator
stream.Write(this.buffer, 0, 5);
stream.Write(this.buffer, 0, 19);
}
}
/// <summary>
/// Writes the image comments to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
/// <param name="metadata">The metadata to be extract the comment data.</param>
/// <param name="stream">The stream to write to.</param>
private void WriteComments<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
private void WriteComments(ImageMetaData metadata, Stream stream)
{
if (this.ignoreMetadata)
{
return;
}
ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments);
if (property == null || string.IsNullOrEmpty(property.Value))
if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value))
{
return;
}
@ -255,15 +247,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, Stream stream, int transparencyIndex)
{
var extension = new GifGraphicsControlExtension(
byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metaData.DisposalMethod,
transparencyFlag: transparencyIndex > -1,
transparencyFlag: transparencyIndex > -1);
var extension = new GifGraphicControlExtension(
packed: packedValue,
transparencyIndex: unchecked((byte)transparencyIndex),
delayTime: (ushort)metaData.FrameDelay);
extension.WriteTo(this.buffer);
this.WriteExtension(extension, stream);
}
/// <summary>
/// Writes the provided extension to the stream.
/// </summary>
/// <param name="extension">The extension to write to the stream.</param>
/// <param name="stream">The stream to write to.</param>
public void WriteExtension(IGifExtension extension, Stream stream)
{
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = extension.Label;
stream.Write(this.buffer, 0, GifGraphicsControlExtension.Size);
int extensionSize = extension.WriteTo(this.buffer.AsSpan(2));
this.buffer[extensionSize + 2] = GifConstants.Terminator;
stream.Write(this.buffer, 0, extensionSize + 3);
}
/// <summary>
@ -279,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
localColorTableFlag: true,
interfaceFlag: false,
sortFlag: false,
localColorTableSize: this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
localColorTableSize: (byte)this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
var descriptor = new GifImageDescriptor(
left: 0,
@ -302,17 +312,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
// Grab the palette and write it to the stream.
int pixelCount = image.Palette.Length;
// Get max colors for bit depth.
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3;
var rgb = default(Rgb24);
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth
Rgb24 rgb = default;
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
{
ref TPixel paletteRef = ref MemoryMarshal.GetReference(image.Palette.AsSpan());
ref Rgb24 rgb24Ref = ref Unsafe.As<byte, Rgb24>(ref MemoryMarshal.GetReference(colorTable.Span));
for (int i = 0; i < pixelCount; i++)
{
ref TPixel entry = ref Unsafe.Add(ref paletteRef, i);
@ -320,6 +328,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
Unsafe.Add(ref rgb24Ref, i) = rgb;
}
// Write the palette to the stream
stream.Write(colorTable.Array, 0, colorTableLength);
}
}

8
src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs

@ -16,17 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Gif;
}
return null;
return this.IsSupportedFileFormat(header) ? ImageFormats.Gif : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
// TODO: This should be in constants
return header.Length >= this.HeaderSize &&
header[0] == 0x47 && // G
header[1] == 0x49 && // I

2
src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the encoding that should be used when reading comments.
/// Gets the text encoding that should be used when reading comments.
/// </summary>
Encoding TextEncoding { get; }

6
src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs

@ -7,7 +7,7 @@ using SixLabors.ImageSharp.Processing.Quantization;
namespace SixLabors.ImageSharp.Formats.Gif
{
/// <summary>
/// The configuration options used for encoding gifs
/// The configuration options used for encoding gifs.
/// </summary>
internal interface IGifEncoderOptions
{
@ -17,12 +17,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
bool IgnoreMetadata { get; }
/// <summary>
/// Gets the encoding that should be used when writing comments.
/// Gets the text encoding used to write comments.
/// </summary>
Encoding TextEncoding { get; }
/// <summary>
/// Gets the quantizer for reducing the color count.
/// Gets the quantizer used to generate the color palette.
/// </summary>
IQuantizer Quantizer { get; }
}

6
src/ImageSharp/Formats/Gif/ImageExtensions.cs

@ -1,10 +1,8 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats;
@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the gif format.
/// Saves the image to the given stream in the gif format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp
=> source.SaveAsGif(stream, null);
/// <summary>
/// Saves the image to the given stream with the gif format.
/// Saves the image to the given stream in the gif format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>

43
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -44,19 +44,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
private readonly IBuffer<int> pixelStack;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from.
@ -225,13 +212,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
this.Dispose(true);
}
/// <summary>
/// Reads the next data block from the stream. A data block begins with a byte,
/// which defines the size of the block, followed by the block itself.
@ -253,25 +233,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
return count != bufferSize ? 0 : bufferSize;
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.prefix?.Dispose();
this.suffix?.Dispose();
this.pixelStack?.Dispose();
}
this.isDisposed = true;
this.prefix.Dispose();
this.suffix.Dispose();
this.pixelStack.Dispose();
}
}
}

118
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -34,16 +34,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </remarks>
internal sealed class LzwEncoder : IDisposable
{
/// <summary>
/// The end-of-file marker
/// </summary>
private const int Eof = -1;
/// <summary>
/// The maximum number of bits.
/// </summary>
private const int Bits = 12;
/// <summary>
/// 80% occupancy
/// </summary>
@ -59,7 +49,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
};
/// <summary>
/// The working pixel array
/// The maximium number of bits/code.
/// </summary>
private const int MaxBits = 12;
/// <summary>
/// Should NEVER generate this code.
/// </summary>
private const int MaxMaxCode = 1 << MaxBits;
/// <summary>
/// The working pixel array.
/// </summary>
private readonly byte[] pixelArray;
@ -84,42 +84,19 @@ namespace SixLabors.ImageSharp.Formats.Gif
private readonly byte[] accumulators = new byte[256];
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// The current position within the pixelArray.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
/// </remarks>
private bool isDisposed;
/// <summary>
/// The current pixel
/// </summary>
private int currentPixel;
private int position;
/// <summary>
/// Number of bits/code
/// </summary>
private int bitCount;
/// <summary>
/// User settable max # bits/code
/// </summary>
private int maxbits = Bits;
/// <summary>
/// maximum code, given bitCount
/// </summary>
private int maxcode;
/// <summary>
/// should NEVER generate this code
/// </summary>
private int maxmaxcode = 1 << Bits;
private int maxCode;
/// <summary>
/// For dynamic table sizing
@ -212,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Write "initial code size" byte
stream.WriteByte((byte)this.initialCodeSize);
this.currentPixel = 0;
this.position = 0;
// Compress and write the pixel data
this.Compress(this.initialCodeSize + 1, stream);
@ -221,15 +198,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
stream.WriteByte(GifConstants.Terminator);
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
this.Dispose(true);
}
/// <summary>
/// Gets the maximum code value
/// Gets the maximum code value.
/// </summary>
/// <param name="bitCount">The number of bits</param>
/// <returns>See <see cref="int"/></returns>
@ -257,7 +227,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <summary>
/// Table clear for block compress
/// Table clear for block compress.
/// </summary>
/// <param name="stream">The output stream.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -292,13 +262,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
int hsizeReg;
int hshift;
// Set up the globals: globalInitialBits - initial number of bits
// Set up the globals: globalInitialBits - initial number of bits
this.globalInitialBits = intialBits;
// Set up the necessary values
this.clearFlag = false;
this.bitCount = this.globalInitialBits;
this.maxcode = GetMaxcode(this.bitCount);
this.maxCode = GetMaxcode(this.bitCount);
this.clearCode = 1 << (intialBits - 1);
this.eofCode = this.clearCode + 1;
@ -326,9 +296,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
ref int hashTableRef = ref MemoryMarshal.GetReference(this.hashTable.Span);
ref int codeTableRef = ref MemoryMarshal.GetReference(this.codeTable.Span);
while ((c = this.NextPixel()) != Eof)
while (this.position < this.pixelArray.Length)
{
fcode = (c << this.maxbits) + ent;
c = this.NextPixel();
fcode = (c << MaxBits) + ent;
int i = (c << hshift) ^ ent /* = 0 */;
if (Unsafe.Add(ref hashTableRef, i) == fcode)
@ -369,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.Output(ent, stream);
ent = c;
if (this.freeEntry < this.maxmaxcode)
if (this.freeEntry < MaxMaxCode)
{
Unsafe.Add(ref codeTableRef, i) = this.freeEntry++; // code -> hashtable
Unsafe.Add(ref hashTableRef, i) = fcode;
@ -387,7 +359,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <summary>
/// Flush the packet to disk, and reset the accumulator.
/// Flush the packet to disk and reset the accumulator.
/// </summary>
/// <param name="outStream">The output stream.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -399,20 +371,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
/// <summary>
/// Return the next pixel from the image
/// Reads the next pixel from the image.
/// </summary>
/// <returns>
/// The <see cref="int"/>
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int NextPixel()
{
if (this.currentPixel == this.pixelArray.Length)
{
return Eof;
}
this.currentPixel++;
return this.pixelArray[this.currentPixel - 1] & 0xff;
return this.pixelArray[this.position++] & 0xff;
}
/// <summary>
@ -445,18 +412,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
// If the next entry is going to be too big for the code size,
// then increase it, if possible.
if (this.freeEntry > this.maxcode || this.clearFlag)
if (this.freeEntry > this.maxCode || this.clearFlag)
{
if (this.clearFlag)
{
this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits);
this.maxCode = GetMaxcode(this.bitCount = this.globalInitialBits);
this.clearFlag = false;
}
else
{
++this.bitCount;
this.maxcode = this.bitCount == this.maxbits
? this.maxmaxcode
this.maxCode = this.bitCount == MaxBits
? MaxMaxCode
: GetMaxcode(this.bitCount);
}
}
@ -478,24 +445,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
this.hashTable?.Dispose();
this.codeTable?.Dispose();
}
this.isDisposed = true;
this.hashTable?.Dispose();
this.codeTable?.Dispose();
}
}
}

177
src/ImageSharp/Formats/Gif/PackedField.cs

@ -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);
}
}
}
}

106
src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs

@ -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;
}
}
}

94
src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs

@ -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);
}
}
}

38
src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs

@ -81,16 +81,36 @@ namespace SixLabors.ImageSharp.Formats.Gif
return MemoryMarshal.Cast<byte, GifImageDescriptor>(buffer)[0];
}
public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize)
public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, byte localColorTableSize)
{
var field = default(PackedField);
field.SetBit(0, localColorTableFlag); // 0: Local color table flag = 1 (LCT used)
field.SetBit(1, interfaceFlag); // 1: Interlace flag 0
field.SetBit(2, sortFlag); // 2: Sort flag 0
field.SetBits(5, 3, localColorTableSize - 1); // 3-4: Reserved, 5-7 : LCT size. 2^(N+1)
return field.Byte;
/*
Local Color Table Flag | 1 Bit
Interlace Flag | 1 Bit
Sort Flag | 1 Bit
Reserved | 2 Bits
Size of Local Color Table | 3 Bits
*/
byte value = 0;
if (localColorTableFlag)
{
value |= 1 << 7;
}
if (interfaceFlag)
{
value |= 1 << 6;
}
if (sortFlag)
{
value |= 1 << 5;
}
value |= (byte)(localColorTableSize - 1);
return value;
}
}
}

97
src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs

@ -2,7 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Gif
{
@ -11,29 +12,23 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// necessary to define the area of the display device
/// within which the images will be rendered
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal readonly struct GifLogicalScreenDescriptor
{
/// <summary>
/// The size of the written structure.
/// </summary>
public const int Size = 7;
public GifLogicalScreenDescriptor(
ushort width,
ushort height,
int bitsPerPixel,
byte packed,
byte backgroundColorIndex,
byte pixelAspectRatio,
bool globalColorTableFlag,
int globalColorTableSize)
byte pixelAspectRatio = 0)
{
this.Width = width;
this.Height = height;
this.BitsPerPixel = bitsPerPixel;
this.Packed = packed;
this.BackgroundColorIndex = backgroundColorIndex;
this.PixelAspectRatio = pixelAspectRatio;
this.GlobalColorTableFlag = globalColorTableFlag;
this.GlobalColorTableSize = globalColorTableSize;
}
/// <summary>
@ -49,9 +44,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
public ushort Height { get; }
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// Gets the packed value consisting of:
/// globalColorTableFlag, colorResolution, sortFlag, and sizeOfGlobalColorTable.
/// </summary>
public int BitsPerPixel { get; }
public byte Packed { get; }
/// <summary>
/// Gets the index at the Global Color Table for the Background Color.
@ -61,59 +57,42 @@ namespace SixLabors.ImageSharp.Formats.Gif
public byte BackgroundColorIndex { get; }
/// <summary>
/// Gets the pixel aspect ratio. Default to 0.
/// Gets the pixel aspect ratio.
/// </summary>
public byte PixelAspectRatio { get; }
/// <summary>
/// Gets a value indicating whether a flag denoting the presence of a Global Color Table
/// should be set.
/// If the flag is set, the Global Color Table will immediately
/// follow the Logical Screen Descriptor.
/// If the flag is set, the Global Color Table will included after
/// the Logical Screen Descriptor.
/// </summary>
public bool GlobalColorTableFlag { get; }
public bool GlobalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1;
/// <summary>
/// Gets the global color table size.
/// If the Global Color Table Flag is set to 1,
/// If the Global Color Table Flag is set,
/// the value in this field is used to calculate the number of
/// bytes contained in the Global Color Table.
/// </summary>
public int GlobalColorTableSize { get; }
public int GlobalColorTableSize => 2 << (this.Packed & 0x07);
public byte PackFields()
{
PackedField field = default;
field.SetBit(0, this.GlobalColorTableFlag); // 0 : Global Color Table Flag | 1 bit
field.SetBits(1, 3, this.GlobalColorTableSize); // 1-3 : Color Resolution | 3 bits
field.SetBit(4, false); // 4 : Sort Flag | 1 bits
field.SetBits(5, 3, this.GlobalColorTableSize); // 5-7 : Size of Global Color Table | 3 bits
return field.Byte;
}
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// The lowest 3 packed bits represent the bit depth minus 1.
/// </summary>
public int BitsPerPixel => (this.Packed & 0x07) + 1;
public void WriteTo(Span<byte> buffer)
{
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(0, 2), this.Width); // Logical Screen Width
BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(2, 2), this.Height); // Logical Screen Height
buffer[4] = this.PackFields(); // Packed Fields
buffer[5] = this.BackgroundColorIndex; // Background Color Index
buffer[6] = this.PixelAspectRatio; // Pixel Aspect Ratio
ref GifLogicalScreenDescriptor dest = ref Unsafe.As<byte, GifLogicalScreenDescriptor>(ref MemoryMarshal.GetReference(buffer));
dest = this;
}
public static GifLogicalScreenDescriptor Parse(ReadOnlySpan<byte> buffer)
{
byte packed = buffer[4];
var result = new GifLogicalScreenDescriptor(
width: BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2)),
height: BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(2, 2)),
bitsPerPixel: (buffer[4] & 0x07) + 1, // The lowest 3 bits represent the bit depth minus 1
backgroundColorIndex: buffer[5],
pixelAspectRatio: buffer[6],
globalColorTableFlag: ((packed & 0x80) >> 7) == 1,
globalColorTableSize: 2 << (packed & 0x07));
GifLogicalScreenDescriptor result = MemoryMarshal.Cast<byte, GifLogicalScreenDescriptor>(buffer)[0];
if (result.GlobalColorTableSize > 255 * 4)
{
@ -122,5 +101,33 @@ namespace SixLabors.ImageSharp.Formats.Gif
return result;
}
public static byte GetPackedValue(bool globalColorTableFlag, int colorResolution, bool sortFlag, int globalColorTableSize)
{
/*
Global Color Table Flag | 1 Bit
Color Resolution | 3 Bits
Sort Flag | 1 Bit
Size of Global Color Table | 3 Bits
*/
byte value = 0;
if (globalColorTableFlag)
{
value |= 1 << 7;
}
value |= (byte)(colorResolution << 4);
if (sortFlag)
{
value |= 1 << 3;
}
value |= (byte)globalColorTableSize;
return value;
}
}
}

22
src/ImageSharp/Formats/Gif/Sections/IGifExtension.cs

@ -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);
}
}

364
src/ImageSharp/IO/EndianBinaryReader.cs

@ -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;
}
}
}

303
src/ImageSharp/IO/EndianBinaryWriter.cs

@ -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);
}
}
}

3
src/ImageSharp/ImageSharp.csproj

@ -86,9 +86,6 @@
<Generator>TextTemplatingFileGenerator</Generator>
</None>
</ItemGroup>
<ItemGroup>
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
</ItemGroup>
<ItemGroup>
<Compile Update="Formats\Jpeg\Common\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime>

30
src/ImageSharp/MetaData/ImageMetaData.cs

@ -43,17 +43,15 @@ namespace SixLabors.ImageSharp.MetaData
/// <param name="other">
/// The other <see cref="ImageMetaData"/> to create this instance from.
/// </param>
internal ImageMetaData(ImageMetaData other)
private ImageMetaData(ImageMetaData other)
{
DebugGuard.NotNull(other, nameof(other));
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties)
{
this.Properties.Add(new ImageProperty(property));
this.Properties.Add(property);
}
this.ExifProfile = other.ExifProfile != null
@ -114,7 +112,6 @@ namespace SixLabors.ImageSharp.MetaData
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary>
@ -123,6 +120,29 @@ namespace SixLabors.ImageSharp.MetaData
/// </summary>
public ushort RepeatCount { get; set; }
/// <summary>
/// Looks up a property with the provided name.
/// </summary>
/// <param name="name">The name of the property to lookup.</param>
/// <param name="result">The property, if found, with the provided name.</param>
/// <returns>Whether the property was found.</returns>
internal bool TryGetProperty(string name, out ImageProperty result)
{
foreach (ImageProperty property in this.Properties)
{
if (property.Name == name)
{
result = property;
return true;
}
}
result = default;
return false;
}
/// <summary>
/// Clones this into a new instance
/// </summary>

15
src/ImageSharp/MetaData/ImageProperty.cs

@ -25,21 +25,6 @@ namespace SixLabors.ImageSharp.MetaData
this.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageProperty"/> struct
/// by making a copy from another property.
/// </summary>
/// <param name="other">
/// The other <see cref="ImageProperty"/> to create this instance from.
/// </param>
internal ImageProperty(ImageProperty other)
{
DebugGuard.NotNull(other, nameof(other));
this.Name = other.Name;
this.Value = other.Value;
}
/// <summary>
/// Gets the name of this <see cref="ImageProperty"/> indicating which kind of
/// information this property stores.

2
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -10,7 +10,7 @@ using System.IO;
using SixLabors.ImageSharp.Advanced;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests
namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
using System.Collections.Generic;

2
tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests
namespace SixLabors.ImageSharp.Tests.Formats.Gif
{
public class GifEncoderTests
{

21
tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs

@ -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));
}
}
}

24
tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs

@ -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));
}
}
}

23
tests/ImageSharp.Tests/Formats/Gif/Sections/GifLogicalScreenDescriptorTests.cs

@ -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));
}
}
}

97
tests/ImageSharp.Tests/IO/EndianBinaryReaderWriterTests.cs

@ -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();
}
}
}
}

11
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -32,23 +32,16 @@
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.7.145" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.1" />
<!--<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>-->
<PackageReference Include="Moq" Version="4.8.2" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
</ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>

18
tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
@ -20,10 +18,10 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConstructorImageMetaData()
{
ImageMetaData metaData = new ImageMetaData();
var metaData = new ImageMetaData();
ExifProfile exifProfile = new ExifProfile();
ImageProperty imageProperty = new ImageProperty("name", "value");
var exifProfile = new ExifProfile();
var imageProperty = new ImageProperty("name", "value");
metaData.ExifProfile = exifProfile;
metaData.HorizontalResolution = 4;
@ -31,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests
metaData.Properties.Add(imageProperty);
metaData.RepeatCount = 1;
ImageMetaData clone = new ImageMetaData(metaData);
ImageMetaData clone = metaData.Clone();
Assert.Equal(exifProfile.ToByteArray(), clone.ExifProfile.ToByteArray());
Assert.Equal(4, clone.HorizontalResolution);
@ -43,7 +41,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void HorizontalResolution()
{
ImageMetaData metaData = new ImageMetaData();
var metaData = new ImageMetaData();
Assert.Equal(96, metaData.HorizontalResolution);
metaData.HorizontalResolution=0;
@ -59,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void VerticalResolution()
{
ImageMetaData metaData = new ImageMetaData();
var metaData = new ImageMetaData();
Assert.Equal(96, metaData.VerticalResolution);
metaData.VerticalResolution = 0;
@ -75,11 +73,11 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void SyncProfiles()
{
ExifProfile exifProfile = new ExifProfile();
var exifProfile = new ExifProfile();
exifProfile.SetValue(ExifTag.XResolution, new Rational(200));
exifProfile.SetValue(ExifTag.YResolution, new Rational(300));
Image<Rgba32> image = new Image<Rgba32>(1, 1);
var image = new Image<Rgba32>(1, 1);
image.MetaData.ExifProfile = exifProfile;
image.MetaData.HorizontalResolution = 400;
image.MetaData.VerticalResolution = 500;

Loading…
Cancel
Save