Browse Source

Merge branch 'master' into jpeg-cleanup

pull/537/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
c968e2913d
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 public enum DisposalMethod
{ {
/// <summary> /// <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> /// </summary>
Unspecified = 0, Unspecified = 0,
/// <summary> /// <summary>
/// Do not dispose. The graphic is to be left in place. /// Do not dispose.
/// The graphic is to be left in place.
/// </summary> /// </summary>
NotDispose = 1, NotDispose = 1,
/// <summary> /// <summary>
/// Restore to background color. The area used by the graphic must be restored to /// Restore to background color.
/// the background color. /// The area used by the graphic must be restored to the background color.
/// </summary> /// </summary>
RestoreToBackground = 2, RestoreToBackground = 2,
/// <summary> /// <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. /// graphic with what was there prior to rendering the graphic.
/// </summary> /// </summary>
RestoreToPrevious = 3 RestoreToPrevious = 3

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

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

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

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

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

@ -4,50 +4,45 @@
using System; using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.MetaData; using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Quantization; 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 namespace SixLabors.ImageSharp.Formats.Gif
{ {
/// <summary> /// <summary>
/// Performs the gif encoding operation. /// Implements the GIF encoding protocol.
/// </summary> /// </summary>
internal sealed class GifEncoderCore internal sealed class GifEncoderCore
{ {
private readonly MemoryManager memoryManager; private readonly MemoryManager memoryManager;
/// <summary> /// <summary>
/// The temp buffer used to reduce allocations. /// A reusable buffer used to reduce allocations.
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[16]; private readonly byte[] buffer = new byte[20];
/// <summary> /// <summary>
/// Gets the TextEncoding /// Gets the text encoding used to write comments.
/// </summary> /// </summary>
private readonly Encoding textEncoding; private readonly Encoding textEncoding;
/// <summary> /// <summary>
/// Gets or sets the quantizer for reducing the color count. /// Gets or sets the quantizer used to generate the color palette.
/// </summary> /// </summary>
private readonly IQuantizer quantizer; private readonly IQuantizer quantizer;
/// <summary> /// <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> /// </summary>
private readonly bool ignoreMetadata; private readonly bool ignoreMetadata;
/// <summary> /// <summary>
/// The number of bits requires to store the image palette. /// The number of bits requires to store the color palette.
/// </summary> /// </summary>
private int bitDepth; private int bitDepth;
@ -91,7 +86,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.WriteLogicalScreenDescriptor(image, stream, index); this.WriteLogicalScreenDescriptor(image, stream, index);
// Write the first frame. // Write the first frame.
this.WriteComments(image, stream); this.WriteComments(image.MetaData, stream);
// Write additional frames. // Write additional frames.
if (image.Frames.Count > 1) 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 // Transparent pixels are much more likely to be found at the end of a palette
int index = -1; int index = -1;
var trans = default(Rgba32); Rgba32 trans = default;
ref TPixel paletteRef = ref MemoryMarshal.GetReference(quantized.Palette.AsSpan()); ref TPixel paletteRef = ref MemoryMarshal.GetReference(quantized.Palette.AsSpan());
for (int i = quantized.Palette.Length - 1; i >= 0; i--) 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) private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, Stream stream, int transparencyIndex)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(false, this.bitDepth - 1, false, this.bitDepth - 1);
var descriptor = new GifLogicalScreenDescriptor( var descriptor = new GifLogicalScreenDescriptor(
width: (ushort)image.Width, width: (ushort)image.Width,
height: (ushort)image.Height, height: (ushort)image.Height,
bitsPerPixel: 0, packed: packedValue,
pixelAspectRatio: 0,
globalColorTableFlag: false, // TODO: Always false for now.
globalColorTableSize: this.bitDepth - 1,
backgroundColorIndex: unchecked((byte)transparencyIndex)); backgroundColorIndex: unchecked((byte)transparencyIndex));
descriptor.WriteTo(this.buffer); descriptor.WriteTo(this.buffer);
@ -196,40 +191,37 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.buffer[1] = GifConstants.ApplicationExtensionLabel; this.buffer[1] = GifConstants.ApplicationExtensionLabel;
this.buffer[2] = GifConstants.ApplicationBlockSize; this.buffer[2] = GifConstants.ApplicationBlockSize;
stream.Write(this.buffer, 0, 3); // Write NETSCAPE2.0
GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11));
stream.Write(GifConstants.ApplicationIdentificationBytes, 0, 11); // NETSCAPE2.0
this.buffer[0] = 3; // Application block length // Application Data ----
this.buffer[1] = 1; // Data sub-block index (always 1) 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. // 0 means loop indefinitely. Count is set as play n + 1 times.
repeatCount = (ushort)Math.Max(0, repeatCount - 1); 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> /// <summary>
/// Writes the image comments to the stream. /// Writes the image comments to the stream.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <param name="metadata">The metadata to be extract the comment data.</param>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
/// <param name="stream">The stream to write to.</param> /// <param name="stream">The stream to write to.</param>
private void WriteComments<TPixel>(Image<TPixel> image, Stream stream) private void WriteComments(ImageMetaData metadata, Stream stream)
where TPixel : struct, IPixel<TPixel>
{ {
if (this.ignoreMetadata) if (this.ignoreMetadata)
{ {
return; return;
} }
ImageProperty property = image.MetaData.Properties.FirstOrDefault(p => p.Name == GifConstants.Comments); if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value))
if (property == null || string.IsNullOrEmpty(property.Value))
{ {
return; 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> /// <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) private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, Stream stream, int transparencyIndex)
{ {
var extension = new GifGraphicsControlExtension( byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metaData.DisposalMethod, disposalMethod: metaData.DisposalMethod,
transparencyFlag: transparencyIndex > -1, transparencyFlag: transparencyIndex > -1);
var extension = new GifGraphicControlExtension(
packed: packedValue,
transparencyIndex: unchecked((byte)transparencyIndex), transparencyIndex: unchecked((byte)transparencyIndex),
delayTime: (ushort)metaData.FrameDelay); 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> /// <summary>
@ -279,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
localColorTableFlag: true, localColorTableFlag: true,
interfaceFlag: false, interfaceFlag: false,
sortFlag: 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( var descriptor = new GifImageDescriptor(
left: 0, left: 0,
@ -302,17 +312,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream) private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
// Grab the palette and write it to the stream.
int pixelCount = image.Palette.Length; int pixelCount = image.Palette.Length;
// Get max colors for bit depth. int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth
int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; Rgb24 rgb = default;
var rgb = default(Rgb24);
using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength)) using (IManagedByteBuffer colorTable = this.memoryManager.AllocateManagedByteBuffer(colorTableLength))
{ {
ref TPixel paletteRef = ref MemoryMarshal.GetReference(image.Palette.AsSpan()); ref TPixel paletteRef = ref MemoryMarshal.GetReference(image.Palette.AsSpan());
ref Rgb24 rgb24Ref = ref Unsafe.As<byte, Rgb24>(ref MemoryMarshal.GetReference(colorTable.Span)); ref Rgb24 rgb24Ref = ref Unsafe.As<byte, Rgb24>(ref MemoryMarshal.GetReference(colorTable.Span));
for (int i = 0; i < pixelCount; i++) for (int i = 0; i < pixelCount; i++)
{ {
ref TPixel entry = ref Unsafe.Add(ref paletteRef, 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; Unsafe.Add(ref rgb24Ref, i) = rgb;
} }
// Write the palette to the stream
stream.Write(colorTable.Array, 0, colorTableLength); stream.Write(colorTable.Array, 0, colorTableLength);
} }
} }

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

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

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

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

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

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

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

@ -1,10 +1,8 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -16,7 +14,7 @@ namespace SixLabors.ImageSharp
public static partial class ImageExtensions public static partial class ImageExtensions
{ {
/// <summary> /// <summary>
/// Saves the image to the given stream with the gif format. /// Saves the image to the given stream in the gif format.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
@ -27,7 +25,7 @@ namespace SixLabors.ImageSharp
=> source.SaveAsGif(stream, null); => source.SaveAsGif(stream, null);
/// <summary> /// <summary>
/// Saves the image to the given stream with the gif format. /// Saves the image to the given stream in the gif format.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param> /// <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> /// </summary>
private readonly IBuffer<int> pixelStack; 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> /// <summary>
/// Initializes a new instance of the <see cref="LzwDecoder"/> class /// Initializes a new instance of the <see cref="LzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from. /// 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> /// <summary>
/// Reads the next data block from the stream. A data block begins with a byte, /// 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. /// 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; return count != bufferSize ? 0 : bufferSize;
} }
/// <summary> /// <inheritdoc />
/// Disposes the object and frees resources for the Garbage Collector. public void Dispose()
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{ {
if (this.isDisposed) this.prefix.Dispose();
{ this.suffix.Dispose();
return; this.pixelStack.Dispose();
}
if (disposing)
{
this.prefix?.Dispose();
this.suffix?.Dispose();
this.pixelStack?.Dispose();
}
this.isDisposed = true;
} }
} }
} }

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

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

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

30
src/ImageSharp/MetaData/ImageMetaData.cs

@ -43,17 +43,15 @@ namespace SixLabors.ImageSharp.MetaData
/// <param name="other"> /// <param name="other">
/// The other <see cref="ImageMetaData"/> to create this instance from. /// The other <see cref="ImageMetaData"/> to create this instance from.
/// </param> /// </param>
internal ImageMetaData(ImageMetaData other) private ImageMetaData(ImageMetaData other)
{ {
DebugGuard.NotNull(other, nameof(other));
this.HorizontalResolution = other.HorizontalResolution; this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution; this.VerticalResolution = other.VerticalResolution;
this.RepeatCount = other.RepeatCount; this.RepeatCount = other.RepeatCount;
foreach (ImageProperty property in other.Properties) foreach (ImageProperty property in other.Properties)
{ {
this.Properties.Add(new ImageProperty(property)); this.Properties.Add(property);
} }
this.ExifProfile = other.ExifProfile != null this.ExifProfile = other.ExifProfile != null
@ -114,7 +112,6 @@ namespace SixLabors.ImageSharp.MetaData
/// <summary> /// <summary>
/// Gets the list of properties for storing meta information about this image. /// Gets the list of properties for storing meta information about this image.
/// </summary> /// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>(); public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <summary> /// <summary>
@ -123,6 +120,29 @@ namespace SixLabors.ImageSharp.MetaData
/// </summary> /// </summary>
public ushort RepeatCount { get; set; } 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> /// <summary>
/// Clones this into a new instance /// Clones this into a new instance
/// </summary> /// </summary>

15
src/ImageSharp/MetaData/ImageProperty.cs

@ -25,21 +25,6 @@ namespace SixLabors.ImageSharp.MetaData
this.Value = value; 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> /// <summary>
/// Gets the name of this <see cref="ImageProperty"/> indicating which kind of /// Gets the name of this <see cref="ImageProperty"/> indicating which kind of
/// information this property stores. /// information this property stores.

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

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

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

@ -10,7 +10,7 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit; using Xunit;
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests namespace SixLabors.ImageSharp.Tests.Formats.Gif
{ {
public class GifEncoderTests 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" Version="2.3.1" />
<PackageReference Include="xunit.runner.console" Version="2.3.1" /> <PackageReference Include="xunit.runner.console" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" 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.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.8.1" /> <PackageReference Include="Moq" Version="4.8.2" />
<!--<PackageReference Include="StyleCop.Analyzers" Version="1.1.0-beta001">
<PrivateAssets>All</PrivateAssets>
</PackageReference>-->
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AdditionalFiles Include="..\..\stylecop.json" /> <AdditionalFiles Include="..\..\stylecop.json" />
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" /> <ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
<ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" /> <ProjectReference Include="..\..\src\ImageSharp\ImageSharp.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" /> <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup> </ItemGroup>

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

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

Loading…
Cancel
Save