Browse Source

Merge branch 'master' into js/faster-pdf-js-decoder

af/merge-core
James Jackson-South 8 years ago
parent
commit
d9a411e1bb
  1. 68
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  2. 179
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 84
      src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs
  4. 87
      src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
  5. 100
      src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -239,15 +238,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.currentStream.Read(this.buffer, 0, 6);
byte packed = this.buffer[1];
this.graphicsControlExtension = new GifGraphicsControlExtension
{
DelayTime = BitConverter.ToInt16(this.buffer, 2),
TransparencyIndex = this.buffer[4],
TransparencyFlag = (packed & 0x01) == 1,
DisposalMethod = (DisposalMethod)((packed & 0x1C) >> 2)
};
this.graphicsControlExtension = GifGraphicsControlExtension.Parse(this.buffer);
}
/// <summary>
@ -258,20 +249,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.currentStream.Read(this.buffer, 0, 9);
byte packed = this.buffer[8];
var imageDescriptor = new GifImageDescriptor
{
Left = BitConverter.ToInt16(this.buffer, 0),
Top = BitConverter.ToInt16(this.buffer, 2),
Width = BitConverter.ToInt16(this.buffer, 4),
Height = BitConverter.ToInt16(this.buffer, 6),
LocalColorTableFlag = ((packed & 0x80) >> 7) == 1,
LocalColorTableSize = 2 << (packed & 0x07),
InterlaceFlag = ((packed & 0x40) >> 6) == 1
};
return imageDescriptor;
return GifImageDescriptor.Parse(this.buffer);
}
/// <summary>
@ -281,23 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.currentStream.Read(this.buffer, 0, 7);
byte packed = this.buffer[4];
this.logicalScreenDescriptor = new GifLogicalScreenDescriptor
{
Width = BitConverter.ToInt16(this.buffer, 0),
Height = BitConverter.ToInt16(this.buffer, 2),
BitsPerPixel = (this.buffer[4] & 0x07) + 1, // The lowest 3 bits represent the bit depth minus 1
BackgroundColorIndex = this.buffer[5],
PixelAspectRatio = this.buffer[6],
GlobalColorTableFlag = ((packed & 0x80) >> 7) == 1,
GlobalColorTableSize = 2 << (packed & 0x07)
};
if (this.logicalScreenDescriptor.GlobalColorTableSize > 255 * 4)
{
throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'");
}
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
}
/// <summary>
@ -390,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="imageDescriptor">The <see cref="GifImageDescriptor"/>.</param>
/// <param name="indices">The pixel array to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span<byte> indices)
private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span<byte> indices)
{
int dataSize = this.currentStream.ReadByte();
using (var lzwDecoder = new LzwDecoder(this.configuration.MemoryManager, this.currentStream))
@ -408,7 +370,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="indices">The indexed pixels.</param>
/// <param name="colorTable">The color table containing the available colors.</param>
/// <param name="descriptor">The <see cref="GifImageDescriptor"/></param>
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, Span<byte> colorTable, GifImageDescriptor descriptor)
private void ReadFrameColors<TPixel>(ref Image<TPixel> image, ref ImageFrame<TPixel> previousFrame, Span<byte> indices, Span<byte> colorTable, in GifImageDescriptor descriptor)
where TPixel : struct, IPixel<TPixel>
{
ref byte indicesRef = ref MemoryMarshal.GetReference(indices);
@ -430,8 +392,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
{
prevFrame = previousFrame;
}
@ -494,8 +455,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int index = Unsafe.Add(ref indicesRef, i);
if (this.graphicsControlExtension == null ||
this.graphicsControlExtension.TransparencyFlag == false ||
if (this.graphicsControlExtension.TransparencyFlag == false ||
this.graphicsControlExtension.TransparencyIndex != index)
{
int indexOffset = index * 3;
@ -518,8 +478,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
previousFrame = currentFrame ?? image.Frames.RootFrame;
if (this.graphicsControlExtension != null &&
this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
@ -551,15 +510,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(ImageFrameMetaData meta)
{
if (this.graphicsControlExtension != null)
if (this.graphicsControlExtension.DelayTime > 0)
{
if (this.graphicsControlExtension.DelayTime > 0)
{
meta.FrameDelay = this.graphicsControlExtension.DelayTime;
}
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
meta.FrameDelay = this.graphicsControlExtension.DelayTime;
}
meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
/// <summary>

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@ -75,9 +76,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
// Do not use IDisposable pattern here as we want to preserve the stream.
var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream);
// Quantize the image returning a palette.
QuantizedFrame<TPixel> quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(image.Frames.RootFrame);
@ -87,18 +85,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
int index = this.GetTransparentIndex(quantized);
// Write the header.
this.WriteHeader(writer);
this.WriteHeader(stream);
// Write the LSD. We'll use local color tables for now.
this.WriteLogicalScreenDescriptor(image, writer, index);
this.WriteLogicalScreenDescriptor(image, stream, index);
// Write the first frame.
this.WriteComments(image, writer);
this.WriteComments(image, stream);
// Write additional frames.
if (image.Frames.Count > 1)
{
this.WriteApplicationExtension(writer, image.MetaData.RepeatCount);
this.WriteApplicationExtension(stream, image.MetaData.RepeatCount);
}
foreach (ImageFrame<TPixel> frame in image.Frames)
@ -108,16 +106,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
quantized = this.quantizer.CreateFrameQuantizer<TPixel>().QuantizeFrame(frame);
}
this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantized));
this.WriteImageDescriptor(frame, writer);
this.WriteColorTable(quantized, writer);
this.WriteImageData(quantized, writer);
this.WriteGraphicalControlExtension(frame.MetaData, stream, this.GetTransparentIndex(quantized));
this.WriteImageDescriptor(frame, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);
quantized = null; // So next frame can regenerate it
}
// TODO: Write extension etc
writer.Write(GifConstants.EndIntroducer);
stream.WriteByte(GifConstants.EndIntroducer);
}
/// <summary>
@ -153,11 +151,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// Writes the file header signature and version to the stream.
/// </summary>
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="stream">The stream to write to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void WriteHeader(EndianBinaryWriter writer)
private void WriteHeader(Stream stream)
{
writer.Write(GifConstants.MagicNumber);
stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
}
/// <summary>
@ -165,43 +163,31 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The image to encode.</param>
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="transparencyIndex">The transparency index to set the default background index to.</param>
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, EndianBinaryWriter writer, int transparencyIndex)
private void WriteLogicalScreenDescriptor<TPixel>(Image<TPixel> image, Stream stream, int transparencyIndex)
where TPixel : struct, IPixel<TPixel>
{
var descriptor = new GifLogicalScreenDescriptor
{
Width = (short)image.Width,
Height = (short)image.Height,
GlobalColorTableFlag = false, // TODO: Always false for now.
GlobalColorTableSize = this.bitDepth - 1,
BackgroundColorIndex = unchecked((byte)transparencyIndex)
};
writer.Write((ushort)descriptor.Width);
writer.Write((ushort)descriptor.Height);
var field = default(PackedField);
field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used)
field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution
field.SetBit(4, false); // 5 : GCT sort flag = 0
field.SetBits(5, 3, descriptor.GlobalColorTableSize); // 6-8 : GCT size. 2^(N+1)
// Reduce the number of writes
this.buffer[0] = field.Byte;
this.buffer[1] = descriptor.BackgroundColorIndex; // Background Color Index
this.buffer[2] = descriptor.PixelAspectRatio; // Pixel aspect ratio. Assume 1:1
writer.Write(this.buffer, 0, 3);
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,
backgroundColorIndex: unchecked((byte)transparencyIndex));
descriptor.WriteTo(this.buffer);
stream.Write(this.buffer, 0, GifLogicalScreenDescriptor.Size);
}
/// <summary>
/// Writes the application extension to the stream.
/// </summary>
/// <param name="writer">The writer to write to the stream with.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="repeatCount">The animated image repeat count.</param>
private void WriteApplicationExtension(EndianBinaryWriter writer, ushort repeatCount)
private void WriteApplicationExtension(Stream stream, ushort repeatCount)
{
// Application Extension Header
if (repeatCount != 1)
@ -210,17 +196,21 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.buffer[1] = GifConstants.ApplicationExtensionLabel;
this.buffer[2] = GifConstants.ApplicationBlockSize;
writer.Write(this.buffer, 0, 3);
stream.Write(this.buffer, 0, 3);
stream.Write(GifConstants.ApplicationIdentificationBytes, 0, 11); // NETSCAPE2.0
writer.Write(GifConstants.ApplicationIdentificationBytes); // NETSCAPE2.0
writer.Write((byte)3); // Application block length
writer.Write((byte)1); // Data sub-block index (always 1)
this.buffer[0] = 3; // Application block length
this.buffer[1] = 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);
writer.Write(repeatCount); // Repeat count for images.
writer.Write(GifConstants.Terminator); // Terminator
BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(2, 2), repeatCount); // Repeat count for images.
this.buffer[4] = GifConstants.Terminator; // Terminator
stream.Write(this.buffer, 0, 5);
}
}
@ -229,8 +219,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteComments<TPixel>(Image<TPixel> image, EndianBinaryWriter writer)
/// <param name="stream">The stream to write to.</param>
private void WriteComments<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
if (this.ignoreMetadata)
@ -252,44 +242,28 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.buffer[1] = GifConstants.CommentLabel;
this.buffer[2] = (byte)count;
writer.Write(this.buffer, 0, 3);
writer.Write(comments, 0, count);
writer.Write(GifConstants.Terminator);
stream.Write(this.buffer, 0, 3);
stream.Write(comments, 0, count);
stream.WriteByte(GifConstants.Terminator);
}
/// <summary>
/// Writes the graphics control extension to the stream.
/// </summary>
/// <param name="metaData">The metadata of the image or frame.</param>
/// <param name="writer">The stream to write to.</param>
/// <param name="stream">The stream to write to.</param>
/// <param name="transparencyIndex">The index of the color in the color palette to make transparent.</param>
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, EndianBinaryWriter writer, int transparencyIndex)
private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, Stream stream, int transparencyIndex)
{
var extension = new GifGraphicsControlExtension
{
DisposalMethod = metaData.DisposalMethod,
TransparencyFlag = transparencyIndex > -1,
TransparencyIndex = unchecked((byte)transparencyIndex),
DelayTime = metaData.FrameDelay
};
var extension = new GifGraphicsControlExtension(
disposalMethod: metaData.DisposalMethod,
transparencyFlag: transparencyIndex > -1,
transparencyIndex: unchecked((byte)transparencyIndex),
delayTime: (ushort)metaData.FrameDelay);
// Write the intro.
this.buffer[0] = GifConstants.ExtensionIntroducer;
this.buffer[1] = GifConstants.GraphicControlLabel;
this.buffer[2] = 4;
writer.Write(this.buffer, 0, 3);
var field = default(PackedField);
field.SetBits(3, 3, (int)extension.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, extension.TransparencyFlag); // 8: Has transparent.
extension.WriteTo(this.buffer);
writer.Write(field.Byte);
writer.Write((ushort)extension.DelayTime);
writer.Write(extension.TransparencyIndex);
writer.Write(GifConstants.Terminator);
stream.Write(this.buffer, 0, GifGraphicsControlExtension.Size);
}
/// <summary>
@ -297,25 +271,26 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to be encoded.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, EndianBinaryWriter writer)
/// <param name="stream">The stream to write to.</param>
private void WriteImageDescriptor<TPixel>(ImageFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
writer.Write(GifConstants.ImageDescriptorLabel); // 2c
// TODO: Can we capture this?
writer.Write((ushort)0); // Left position
writer.Write((ushort)0); // Top position
writer.Write((ushort)image.Width);
writer.Write((ushort)image.Height);
var field = default(PackedField);
field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used)
field.SetBit(1, false); // 2: Interlace flag 0
field.SetBit(2, false); // 3: Sort flag 0
field.SetBits(5, 3, this.bitDepth - 1); // 4-5: Reserved, 6-8 : LCT size. 2^(N+1)
writer.Write(field.Byte);
byte packedValue = GifImageDescriptor.GetPackedValue(
localColorTableFlag: true,
interfaceFlag: false,
sortFlag: false,
localColorTableSize: this.bitDepth); // Note: we subtract 1 from the colorTableSize writing
var descriptor = new GifImageDescriptor(
left: 0,
top: 0,
width: (ushort)image.Width,
height: (ushort)image.Height,
packed: packedValue);
descriptor.WriteTo(this.buffer);
stream.Write(this.buffer, 0, GifImageDescriptor.Size);
}
/// <summary>
@ -323,8 +298,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode.</param>
/// <param name="writer">The writer to write to the stream with.</param>
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, EndianBinaryWriter writer)
/// <param name="stream">The stream to write to.</param>
private void WriteColorTable<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
// Grab the palette and write it to the stream.
@ -345,7 +320,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
Unsafe.Add(ref rgb24Ref, i) = rgb;
}
writer.Write(colorTable.Array, 0, colorTableLength);
stream.Write(colorTable.Array, 0, colorTableLength);
}
}
@ -354,13 +329,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="QuantizedFrame{TPixel}"/> containing indexed pixels.</param>
/// <param name="writer">The stream to write to.</param>
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, EndianBinaryWriter writer)
/// <param name="stream">The stream to write to.</param>
private void WriteImageData<TPixel>(QuantizedFrame<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
using (var encoder = new LzwEncoder(this.memoryManager, image.Pixels, (byte)this.bitDepth))
{
encoder.Encode(writer.BaseStream);
encoder.Encode(stream);
}
}
}

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

@ -1,40 +1,94 @@
// 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 sealed class GifGraphicsControlExtension
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 or sets the disposal method which indicates the way in which the
/// Gets the disposal method which indicates the way in which the
/// graphic is to be treated after being displayed.
/// </summary>
public DisposalMethod DisposalMethod { get; set; }
public DisposalMethod DisposalMethod { get; }
/// <summary>
/// Gets or sets a value indicating whether transparency flag is to be set.
/// 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; set; }
/// <summary>
/// Gets or sets 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; set; }
public bool TransparencyFlag { get; }
/// <summary>
/// Gets or sets the delay time.
/// 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 int DelayTime { get; set; }
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);
}
}
}
}

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

@ -1,6 +1,10 @@
// 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>
@ -9,49 +13,84 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Each image must fit within the boundaries of the
/// Logical Screen, as defined in the Logical Screen Descriptor.
/// </summary>
internal sealed class GifImageDescriptor
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal readonly struct GifImageDescriptor
{
public const int Size = 10;
public GifImageDescriptor(
ushort left,
ushort top,
ushort width,
ushort height,
byte packed)
{
this.Left = left;
this.Top = top;
this.Width = width;
this.Height = height;
this.Packed = packed;
}
/// <summary>
/// Gets or sets the column number, in pixels, of the left edge of the image,
/// Gets the column number, in pixels, of the left edge of the image,
/// with respect to the left edge of the Logical Screen.
/// Leftmost column of the Logical Screen is 0.
/// </summary>
public short Left { get; set; }
public ushort Left { get; }
/// <summary>
/// Gets or sets the row number, in pixels, of the top edge of the image with
/// Gets the row number, in pixels, of the top edge of the image with
/// respect to the top edge of the Logical Screen.
/// Top row of the Logical Screen is 0.
/// </summary>
public short Top { get; set; }
public ushort Top { get; }
/// <summary>
/// Gets or sets the width of the image in pixels.
/// Gets the width of the image in pixels.
/// </summary>
public short Width { get; set; }
public ushort Width { get; }
/// <summary>
/// Gets or sets the height of the image in pixels.
/// Gets the height of the image in pixels.
/// </summary>
public short Height { get; set; }
public ushort Height { get; }
/// <summary>
/// Gets or sets a value indicating whether the presence of a Local Color Table immediately
/// follows this Image Descriptor.
/// Gets the packed value of localColorTableFlag, interlaceFlag, sortFlag, and localColorTableSize.
/// </summary>
public bool LocalColorTableFlag { get; set; }
public byte Packed { get; }
/// <summary>
/// Gets or sets the local color table size.
/// If the Local Color Table Flag is set to 1, the value in this field
/// is used to calculate the number of bytes contained in the Local Color Table.
/// </summary>
public int LocalColorTableSize { get; set; }
public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1;
/// <summary>
/// Gets or sets a value indicating whether the image is to be interlaced.
/// An image is interlaced in a four-pass interlace pattern.
/// </summary>
public bool InterlaceFlag { get; set; }
public int LocalColorTableSize => 2 << (this.Packed & 0x07);
public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1;
public void WriteTo(Span<byte> buffer)
{
buffer[0] = GifConstants.ImageDescriptorLabel;
ref GifImageDescriptor dest = ref Unsafe.As<byte, GifImageDescriptor>(ref MemoryMarshal.GetReference(buffer.Slice(1)));
dest = this;
}
public static GifImageDescriptor Parse(ReadOnlySpan<byte> buffer)
{
return MemoryMarshal.Cast<byte, GifImageDescriptor>(buffer)[0];
}
public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int 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;
}
}
}
}

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

@ -1,6 +1,9 @@
// 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>
@ -8,51 +11,116 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// necessary to define the area of the display device
/// within which the images will be rendered
/// </summary>
internal sealed class GifLogicalScreenDescriptor
internal readonly struct GifLogicalScreenDescriptor
{
/// <summary>
/// Gets or sets the width, in pixels, of the Logical Screen where the images will
/// The size of the written structure.
/// </summary>
public const int Size = 7;
public GifLogicalScreenDescriptor(
ushort width,
ushort height,
int bitsPerPixel,
byte backgroundColorIndex,
byte pixelAspectRatio,
bool globalColorTableFlag,
int globalColorTableSize)
{
this.Width = width;
this.Height = height;
this.BitsPerPixel = bitsPerPixel;
this.BackgroundColorIndex = backgroundColorIndex;
this.PixelAspectRatio = pixelAspectRatio;
this.GlobalColorTableFlag = globalColorTableFlag;
this.GlobalColorTableSize = globalColorTableSize;
}
/// <summary>
/// Gets the width, in pixels, of the Logical Screen where the images will
/// be rendered in the displaying device.
/// </summary>
public short Width { get; set; }
public ushort Width { get; }
/// <summary>
/// Gets or sets the height, in pixels, of the Logical Screen where the images will be
/// Gets the height, in pixels, of the Logical Screen where the images will be
/// rendered in the displaying device.
/// </summary>
public short Height { get; set; }
public ushort Height { get; }
/// <summary>
/// Gets or sets the color depth, in number of bits per pixel.
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel { get; set; }
public int BitsPerPixel { get; }
/// <summary>
/// Gets or sets the index at the Global Color Table for the Background Color.
/// Gets the index at the Global Color Table for the Background Color.
/// The Background Color is the color used for those
/// pixels on the screen that are not covered by an image.
/// </summary>
public byte BackgroundColorIndex { get; set; }
public byte BackgroundColorIndex { get; }
/// <summary>
/// Gets or sets the pixel aspect ratio. Default to 0.
/// Gets the pixel aspect ratio. Default to 0.
/// </summary>
public byte PixelAspectRatio { get; set; }
public byte PixelAspectRatio { get; }
/// <summary>
/// Gets or sets 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.
/// If the flag is set, the Global Color Table will immediately
/// follow the Logical Screen Descriptor.
/// </summary>
public bool GlobalColorTableFlag { get; set; }
public bool GlobalColorTableFlag { get; }
/// <summary>
/// Gets or sets the global color table size.
/// Gets the global color table size.
/// If the Global Color Table Flag is set to 1,
/// the value in this field is used to calculate the number of
/// bytes contained in the Global Color Table.
/// </summary>
public int GlobalColorTableSize { get; set; }
public int GlobalColorTableSize { get; }
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;
}
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
}
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));
if (result.GlobalColorTableSize > 255 * 4)
{
throw new ImageFormatException($"Invalid gif colormap size '{result.GlobalColorTableSize}'");
}
return result;
}
}
}
}
Loading…
Cancel
Save