diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 118ec2954..1f2cccc6e 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/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); } /// @@ -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); } /// @@ -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); } /// @@ -390,7 +352,7 @@ namespace SixLabors.ImageSharp.Formats.Gif /// The . /// The pixel array to write to. [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ReadFrameIndices(GifImageDescriptor imageDescriptor, Span indices) + private void ReadFrameIndices(in GifImageDescriptor imageDescriptor, Span 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 /// The indexed pixels. /// The color table containing the available colors. /// The - private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, Span colorTable, GifImageDescriptor descriptor) + private void ReadFrameColors(ref Image image, ref ImageFrame previousFrame, Span indices, Span colorTable, in GifImageDescriptor descriptor) where TPixel : struct, IPixel { 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; } /// diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index cb865e95d..9651cf440 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/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 quantized = this.quantizer.CreateFrameQuantizer().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 frame in image.Frames) @@ -108,16 +106,16 @@ namespace SixLabors.ImageSharp.Formats.Gif quantized = this.quantizer.CreateFrameQuantizer().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); } /// @@ -153,11 +151,11 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// Writes the file header signature and version to the stream. /// - /// The writer to write to the stream with. + /// The stream to write to. [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); } /// @@ -165,43 +163,31 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel format. /// The image to encode. - /// The writer to write to the stream with. + /// The stream to write to. /// The transparency index to set the default background index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int transparencyIndex) + private void WriteLogicalScreenDescriptor(Image image, Stream stream, int transparencyIndex) where TPixel : struct, IPixel { - 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); } /// /// Writes the application extension to the stream. /// - /// The writer to write to the stream with. + /// The stream to write to. /// The animated image repeat count. - 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 /// /// The pixel format. /// The to be encoded. - /// The stream to write to. - private void WriteComments(Image image, EndianBinaryWriter writer) + /// The stream to write to. + private void WriteComments(Image image, Stream stream) where TPixel : struct, IPixel { 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); } /// /// Writes the graphics control extension to the stream. /// /// The metadata of the image or frame. - /// The stream to write to. + /// The stream to write to. /// The index of the color in the color palette to make transparent. - 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); } /// @@ -297,25 +271,26 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel format. /// The to be encoded. - /// The stream to write to. - private void WriteImageDescriptor(ImageFrame image, EndianBinaryWriter writer) + /// The stream to write to. + private void WriteImageDescriptor(ImageFrame image, Stream stream) where TPixel : struct, IPixel { - 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); } /// @@ -323,8 +298,8 @@ namespace SixLabors.ImageSharp.Formats.Gif /// /// The pixel format. /// The to encode. - /// The writer to write to the stream with. - private void WriteColorTable(QuantizedFrame image, EndianBinaryWriter writer) + /// The stream to write to. + private void WriteColorTable(QuantizedFrame image, Stream stream) where TPixel : struct, IPixel { // 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 /// /// The pixel format. /// The containing indexed pixels. - /// The stream to write to. - private void WriteImageData(QuantizedFrame image, EndianBinaryWriter writer) + /// The stream to write to. + private void WriteImageData(QuantizedFrame image, Stream stream) where TPixel : struct, IPixel { using (var encoder = new LzwEncoder(this.memoryManager, image.Pixels, (byte)this.bitDepth)) { - encoder.Encode(writer.BaseStream); + encoder.Encode(stream); } } } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs index 8cdd309d3..bb4c8a59e 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ b/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 { /// /// The Graphic Control Extension contains parameters used when /// processing a graphic rendering block. /// - 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; + } + /// - /// 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. /// - public DisposalMethod DisposalMethod { get; set; } + public DisposalMethod DisposalMethod { get; } /// - /// 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.) /// - public bool TransparencyFlag { get; set; } - - /// - /// 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. - /// - public byte TransparencyIndex { get; set; } + public bool TransparencyFlag { get; } /// - /// 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. /// - public int DelayTime { get; set; } + public ushort DelayTime { get; } + + /// + /// 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. + /// + 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 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 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); + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs index 2ed9e4747..5af3ed814 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs +++ b/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 { /// @@ -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. /// - 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; + } + /// - /// 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. /// - public short Left { get; set; } + public ushort Left { get; } /// - /// 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. /// - public short Top { get; set; } + public ushort Top { get; } /// - /// Gets or sets the width of the image in pixels. + /// Gets the width of the image in pixels. /// - public short Width { get; set; } + public ushort Width { get; } /// - /// Gets or sets the height of the image in pixels. + /// Gets the height of the image in pixels. /// - public short Height { get; set; } + public ushort Height { get; } /// - /// 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. /// - public bool LocalColorTableFlag { get; set; } + public byte Packed { get; } - /// - /// 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. - /// - public int LocalColorTableSize { get; set; } + public bool LocalColorTableFlag => ((this.Packed & 0x80) >> 7) == 1; - /// - /// Gets or sets a value indicating whether the image is to be interlaced. - /// An image is interlaced in a four-pass interlace pattern. - /// - public bool InterlaceFlag { get; set; } + public int LocalColorTableSize => 2 << (this.Packed & 0x07); + + public bool InterlaceFlag => ((this.Packed & 0x40) >> 6) == 1; + + public void WriteTo(Span buffer) + { + buffer[0] = GifConstants.ImageDescriptorLabel; + + ref GifImageDescriptor dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer.Slice(1))); + + dest = this; + } + + public static GifImageDescriptor Parse(ReadOnlySpan buffer) + { + return MemoryMarshal.Cast(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; + } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs index 05f232a4b..4f2a17ddf 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifLogicalScreenDescriptor.cs +++ b/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 { /// @@ -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 /// - internal sealed class GifLogicalScreenDescriptor + internal readonly struct GifLogicalScreenDescriptor { /// - /// Gets or sets the width, in pixels, of the Logical Screen where the images will + /// The size of the written structure. + /// + 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; + } + + /// + /// Gets the width, in pixels, of the Logical Screen where the images will /// be rendered in the displaying device. /// - public short Width { get; set; } + public ushort Width { get; } /// - /// 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. /// - public short Height { get; set; } + public ushort Height { get; } /// - /// Gets or sets the color depth, in number of bits per pixel. + /// Gets the color depth, in number of bits per pixel. /// - public int BitsPerPixel { get; set; } + public int BitsPerPixel { get; } /// - /// 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. /// - public byte BackgroundColorIndex { get; set; } + public byte BackgroundColorIndex { get; } /// - /// Gets or sets the pixel aspect ratio. Default to 0. + /// Gets the pixel aspect ratio. Default to 0. /// - public byte PixelAspectRatio { get; set; } + public byte PixelAspectRatio { get; } /// - /// 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. /// - public bool GlobalColorTableFlag { get; set; } + public bool GlobalColorTableFlag { get; } /// - /// 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. /// - 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 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 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; + } } -} +} \ No newline at end of file