diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 3c48488ec..c15e0a732 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -36,10 +36,15 @@ namespace SixLabors.ImageSharp
/// The
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static int GetBitsNeededForColorDepth(int colors)
- {
- return Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
- }
+ public static int GetBitsNeededForColorDepth(int colors) => Math.Max(1, (int)Math.Ceiling(Math.Log(colors, 2)));
+
+ ///
+ /// Returns how many colors will be created by the specified number of bits.
+ ///
+ /// The bit depth.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetColorCountForBitDepth(int bitDepth) => 1 << bitDepth;
///
/// Implementation of 1D Gaussian G(x) function
@@ -132,10 +137,7 @@ namespace SixLabors.ImageSharp
/// The bounding .
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight)
- {
- return new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
- }
+ public static Rectangle GetBoundingRectangle(Point topLeft, Point bottomRight) => new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);
///
/// Finds the bounding rectangle based on the first instance of any color component other
diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
index 0029a6b68..618999c87 100644
--- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
@@ -6,16 +6,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Enumerates the available bits per pixel for bitmap.
///
- public enum BmpBitsPerPixel
+ public enum BmpBitsPerPixel : short
{
///
/// 24 bits per pixel. Each pixel consists of 3 bytes.
///
- Pixel24 = 3,
+ Pixel24 = 24,
///
/// 32 bits per pixel. Each pixel consists of 4 bytes.
///
- Pixel32 = 4
+ Pixel32 = 32
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
index 956acc157..57117cc07 100644
--- a/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpConfigurationModule.cs
@@ -9,11 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public sealed class BmpConfigurationModule : IConfigurationModule
{
///
- public void Configure(Configuration config)
+ public void Configure(Configuration configuration)
{
- config.ImageFormatsManager.SetEncoder(ImageFormats.Bmp, new BmpEncoder());
- config.ImageFormatsManager.SetDecoder(ImageFormats.Bmp, new BmpDecoder());
- config.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector());
+ configuration.ImageFormatsManager.SetEncoder(BmpFormat.Instance, new BmpEncoder());
+ configuration.ImageFormatsManager.SetDecoder(BmpFormat.Instance, new BmpDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new BmpImageFormatDetector());
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index d67beb036..71852acdd 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -175,10 +175,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Whether the bitmap is inverted.
/// The representing the inverted value.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static int Invert(int y, int height, bool inverted)
- {
- return (!inverted) ? height - y - 1 : y;
- }
+ private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
///
/// Calculates the amount of bytes to pad a row.
@@ -206,10 +203,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The masked and shifted value
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static byte GetBytesFrom5BitValue(int value)
- {
- return (byte)((value << 3) | (value >> 2));
- }
+ private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
///
/// Looks up color values and builds the image from de-compressed RLE8 data.
@@ -524,8 +518,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
// Resolution is stored in PPM.
- var meta = new ImageMetaData();
- meta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
+ var meta = new ImageMetaData
+ {
+ ResolutionUnits = PixelResolutionUnit.PixelsPerMeter
+ };
if (this.infoHeader.XPelsPerMeter > 0 && this.infoHeader.YPelsPerMeter > 0)
{
meta.HorizontalResolution = this.infoHeader.XPelsPerMeter;
@@ -540,6 +536,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.metaData = meta;
+ short bitsPerPixel = this.infoHeader.BitsPerPixel;
+ var bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
+
+ // We can only encode at these bit rates so far.
+ if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
+ || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
+ {
+ bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
+ }
+
// skip the remaining header because we can't read those parts
this.stream.Skip(skipAmount);
}
@@ -585,11 +591,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (this.infoHeader.ClrUsed == 0)
{
- if (this.infoHeader.BitsPerPixel == 1 ||
- this.infoHeader.BitsPerPixel == 4 ||
- this.infoHeader.BitsPerPixel == 8)
+ if (this.infoHeader.BitsPerPixel == 1
+ || this.infoHeader.BitsPerPixel == 4
+ || this.infoHeader.BitsPerPixel == 8)
{
- colorMapSize = (int)Math.Pow(2, this.infoHeader.BitsPerPixel) * 4;
+ colorMapSize = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * 4;
}
}
else
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index 23b01ae9e..b1a66acce 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets or sets the number of bits per pixel.
///
- public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
+ public BmpBitsPerPixel? BitsPerPixel { get; set; }
///
public void Encode(Image image, Stream stream)
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index b49b8a895..4ffaf3950 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -21,10 +21,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private int padding;
- private readonly BmpBitsPerPixel bitsPerPixel;
-
private readonly MemoryAllocator memoryAllocator;
+ private BmpBitsPerPixel? bitsPerPixel;
+
///
/// Initializes a new instance of the class.
///
@@ -48,10 +48,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
- // Cast to int will get the bytes per pixel
- short bpp = (short)(8 * (int)this.bitsPerPixel);
+ BmpMetaData bmpMetaData = image.MetaData.GetFormatMetaData(BmpFormat.Instance);
+ this.bitsPerPixel = this.bitsPerPixel ?? bmpMetaData.BitsPerPixel;
+
+ short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
- this.padding = bytesPerLine - (image.Width * (int)this.bitsPerPixel);
+ this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
// Set Resolution.
ImageMetaData meta = image.MetaData;
@@ -145,10 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
- private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel)
- {
- return this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
- }
+ private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
///
/// Writes the 32bit color palette to the stream.
diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
index 64c6574c1..665f492da 100644
--- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs
@@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Registers the image encoders, decoders and mime type detectors for the bmp format.
///
- internal sealed class BmpFormat : IImageFormat
+ internal sealed class BmpFormat : IImageFormat
{
+ private BmpFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static BmpFormat Instance { get; } = new BmpFormat();
+
///
public string Name => "BMP";
@@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public IEnumerable FileExtensions => BmpConstants.FileExtensions;
+
+ ///
+ public BmpMetaData CreateDefaultFormatMetaData() => new BmpMetaData();
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
index bb884019b..6a740d47d 100644
--- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public IImageFormat DetectFormat(ReadOnlySpan header)
{
- return this.IsSupportedFileFormat(header) ? ImageFormats.Bmp : null;
+ return this.IsSupportedFileFormat(header) ? BmpFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan header)
diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs
new file mode 100644
index 000000000..3d678c13e
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ ///
+ /// Provides Bmp specific metadata information for the image.
+ ///
+ public class BmpMetaData
+ {
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public BmpBitsPerPixel BitsPerPixel { get; set; } = BmpBitsPerPixel.Pixel24;
+
+ // TODO: Colors used once we support encoding palette bmps.
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
index 56952f035..f62504d08 100644
--- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
@@ -12,6 +12,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Gets the number of bits per pixel.
///
- BmpBitsPerPixel BitsPerPixel { get; }
+ BmpBitsPerPixel? BitsPerPixel { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs
index 57e4615ba..aa1c353db 100644
--- a/src/ImageSharp/Formats/Bmp/ImageExtensions.cs
+++ b/src/ImageSharp/Formats/Bmp/ImageExtensions.cs
@@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is null.
public static void SaveAsBmp(this Image source, Stream stream, BmpEncoder encoder)
where TPixel : struct, IPixel
- => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Bmp));
+ => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(BmpFormat.Instance));
}
}
diff --git a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs
index aa4192863..95b333562 100644
--- a/src/ImageSharp/Formats/Gif/GifColorTableMode.cs
+++ b/src/ImageSharp/Formats/Gif/GifColorTableMode.cs
@@ -4,7 +4,7 @@
namespace SixLabors.ImageSharp.Formats.Gif
{
///
- /// Provides enumeration for the available Gif color table modes.
+ /// Provides enumeration for the available color table modes.
///
public enum GifColorTableMode
{
diff --git a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
index 0bb62779e..861d3e036 100644
--- a/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
+++ b/src/ImageSharp/Formats/Gif/GifConfigurationModule.cs
@@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
public sealed class GifConfigurationModule : IConfigurationModule
{
///
- public void Configure(Configuration config)
+ public void Configure(Configuration configuration)
{
- config.ImageFormatsManager.SetEncoder(ImageFormats.Gif, new GifEncoder());
- config.ImageFormatsManager.SetDecoder(ImageFormats.Gif, new GifDecoder());
-
- config.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector());
+ configuration.ImageFormatsManager.SetEncoder(GifFormat.Instance, new GifEncoder());
+ configuration.ImageFormatsManager.SetDecoder(GifFormat.Instance, new GifDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new GifImageFormatDetector());
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifConstants.cs b/src/ImageSharp/Formats/Gif/GifConstants.cs
index 0dbd39b99..8167d0d2e 100644
--- a/src/ImageSharp/Formats/Gif/GifConstants.cs
+++ b/src/ImageSharp/Formats/Gif/GifConstants.cs
@@ -41,20 +41,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public const byte ApplicationExtensionLabel = 0xFF;
+ ///
+ /// The application block size.
+ ///
+ public const byte ApplicationBlockSize = 11;
+
///
/// The application identification.
///
- public const string ApplicationIdentification = "NETSCAPE2.0";
+ public const string NetscapeApplicationIdentification = "NETSCAPE2.0";
///
/// The ASCII encoded application identification bytes.
///
- internal static readonly byte[] ApplicationIdentificationBytes = Encoding.UTF8.GetBytes(ApplicationIdentification);
+ internal static readonly byte[] NetscapeApplicationIdentificationBytes = Encoding.UTF8.GetBytes(NetscapeApplicationIdentification);
///
- /// The application block size.
+ /// The Netscape looping application sub block size.
///
- public const byte ApplicationBlockSize = 11;
+ public const byte NetscapeLoopingSubBlockSize = 3;
///
/// The comment label.
diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs
index ac451a355..42c76d640 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs
@@ -3,6 +3,7 @@
using System.IO;
using System.Text;
+using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Gif
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index 2a4d981eb..207f126f9 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -56,10 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Gif
private GifGraphicControlExtension graphicsControlExtension;
///
- /// The metadata
+ /// The image desciptor.
+ ///
+ private GifImageDescriptor imageDescriptor;
+
+ ///
+ /// The abstract metadata.
///
private ImageMetaData metaData;
+ ///
+ /// The gif specific metadata.
+ ///
+ private GifMetaData gifMetaData;
+
///
/// Initializes a new instance of the class.
///
@@ -120,8 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
- int label = stream.ReadByte();
- switch (label)
+ switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
this.ReadGraphicalControlExtension();
@@ -130,11 +139,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadComments();
break;
case GifConstants.ApplicationExtensionLabel:
-
- // The application extension length should be 11 but we've got test images that incorrectly
- // set this to 252.
- int appLength = stream.ReadByte();
- this.Skip(appLength); // No need to read.
+ this.ReadApplicationExtension();
break;
case GifConstants.PlainTextLabel:
int plainLength = stream.ReadByte();
@@ -178,13 +183,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
if (nextFlag == GifConstants.ImageLabel)
{
- // Skip image block
- this.Skip(0);
+ this.ReadImageDescriptor();
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
- int label = stream.ReadByte();
- switch (label)
+ switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
@@ -195,11 +198,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.ReadComments();
break;
case GifConstants.ApplicationExtensionLabel:
-
- // The application extension length should be 11 but we've got test images that incorrectly
- // set this to 252.
- int appLength = stream.ReadByte();
- this.Skip(appLength); // No need to read.
+ this.ReadApplicationExtension();
break;
case GifConstants.PlainTextLabel:
int plainLength = stream.ReadByte();
@@ -224,7 +223,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.globalColorTable?.Dispose();
}
- return new ImageInfo(new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel), this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height, this.metaData);
+ return new ImageInfo(
+ new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
+ this.logicalScreenDescriptor.Width,
+ this.logicalScreenDescriptor.Height,
+ this.metaData);
}
///
@@ -238,14 +241,13 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
///
- /// Reads the image descriptor
+ /// Reads the image descriptor.
///
- ///
- private GifImageDescriptor ReadImageDescriptor()
+ private void ReadImageDescriptor()
{
this.stream.Read(this.buffer, 0, 9);
- return GifImageDescriptor.Parse(this.buffer);
+ this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
}
///
@@ -258,6 +260,41 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
}
+ ///
+ /// Reads the application extension block parsing any animation information
+ /// if present.
+ ///
+ private void ReadApplicationExtension()
+ {
+ int appLength = this.stream.ReadByte();
+
+ // If the length is 11 then it's a valid extension and most likely
+ // a NETSCAPE or ANIMEXTS extension. We want the loop count from this.
+ if (appLength == GifConstants.ApplicationBlockSize)
+ {
+ this.stream.Skip(appLength);
+ int subBlockSize = this.stream.ReadByte();
+
+ // TODO: There's also a NETSCAPE buffer extension.
+ // http://www.vurdalakov.net/misc/gif/netscape-buffering-application-extension
+ if (subBlockSize == GifConstants.NetscapeLoopingSubBlockSize)
+ {
+ this.stream.Read(this.buffer, 0, GifConstants.NetscapeLoopingSubBlockSize);
+ this.gifMetaData.RepeatCount = GifNetscapeLoopingApplicationExtension.Parse(this.buffer.AsSpan(1)).RepeatCount;
+ this.stream.Skip(1); // Skip the terminator.
+ return;
+ }
+
+ // Could be XMP or something else not supported yet.
+ // Back up and skip.
+ this.stream.Position -= appLength + 1;
+ this.Skip(appLength);
+ return;
+ }
+
+ this.Skip(appLength); // Not supported by any known decoder.
+ }
+
///
/// Skips the designated number of bytes in the stream.
///
@@ -312,25 +349,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void ReadFrame(ref Image image, ref ImageFrame previousFrame)
where TPixel : struct, IPixel
{
- GifImageDescriptor imageDescriptor = this.ReadImageDescriptor();
+ this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null;
IManagedByteBuffer indices = null;
try
{
// Determine the color table for this frame. If there is a local one, use it otherwise use the global color table.
- if (imageDescriptor.LocalColorTableFlag)
+ if (this.imageDescriptor.LocalColorTableFlag)
{
- int length = imageDescriptor.LocalColorTableSize * 3;
+ int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length);
}
- indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(imageDescriptor.Width * imageDescriptor.Height, AllocationOptions.Clean);
+ indices = this.configuration.MemoryAllocator.AllocateManagedByteBuffer(this.imageDescriptor.Width * this.imageDescriptor.Height, AllocationOptions.Clean);
- this.ReadFrameIndices(imageDescriptor, indices.GetSpan());
+ this.ReadFrameIndices(this.imageDescriptor, indices.GetSpan());
ReadOnlySpan colorTable = MemoryMarshal.Cast((localColorTable ?? this.globalColorTable).GetSpan());
- this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, imageDescriptor);
+ this.ReadFrameColors(ref image, ref previousFrame, indices.GetSpan(), colorTable, this.imageDescriptor);
// Skip any remaining blocks
this.Skip(0);
@@ -388,7 +425,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
- if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToPrevious)
+ if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToPrevious)
{
prevFrame = previousFrame;
}
@@ -471,7 +508,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
previousFrame = currentFrame ?? image.Frames.RootFrame;
- if (this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground)
+ if (this.graphicsControlExtension.DisposalMethod == GifDisposalMethod.RestoreToBackground)
{
this.restoreArea = new Rectangle(descriptor.Left, descriptor.Top, descriptor.Width, descriptor.Height);
}
@@ -503,12 +540,25 @@ namespace SixLabors.ImageSharp.Formats.Gif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetaData(ImageFrameMetaData meta)
{
+ GifFrameMetaData gifMeta = meta.GetFormatMetaData(GifFormat.Instance);
if (this.graphicsControlExtension.DelayTime > 0)
{
- meta.FrameDelay = this.graphicsControlExtension.DelayTime;
+ gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
+ }
+
+ // Frames can either use the global table or their own local table.
+ if (this.logicalScreenDescriptor.GlobalColorTableFlag
+ && this.logicalScreenDescriptor.GlobalColorTableSize > 0)
+ {
+ gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize;
+ }
+ else if (this.imageDescriptor.LocalColorTableFlag
+ && this.imageDescriptor.LocalColorTableSize > 0)
+ {
+ gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize;
}
- meta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
+ gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}
///
@@ -552,10 +602,15 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
this.metaData = meta;
+ this.gifMetaData = meta.GetFormatMetaData(GifFormat.Instance);
+ this.gifMetaData.ColorTableMode = this.logicalScreenDescriptor.GlobalColorTableFlag
+ ? GifColorTableMode.Global
+ : GifColorTableMode.Local;
if (this.logicalScreenDescriptor.GlobalColorTableFlag)
{
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
+ this.gifMetaData.GlobalColorTableLength = globalColorTableLength;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);
diff --git a/src/ImageSharp/Formats/Gif/DisposalMethod.cs b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
similarity index 97%
rename from src/ImageSharp/Formats/Gif/DisposalMethod.cs
rename to src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
index 5d3e1b4d8..982340db6 100644
--- a/src/ImageSharp/Formats/Gif/DisposalMethod.cs
+++ b/src/ImageSharp/Formats/Gif/GifDisposalMethod.cs
@@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// in an animation sequence.
/// section 23
///
- public enum DisposalMethod
+ public enum GifDisposalMethod
{
///
/// No disposal specified.
diff --git a/src/ImageSharp/Formats/Gif/GifEncoder.cs b/src/ImageSharp/Formats/Gif/GifEncoder.cs
index e8e28ccdd..9f376044d 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -33,7 +33,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Gets or sets the color table mode: Global or local.
///
- public GifColorTableMode ColorTableMode { get; set; }
+ public GifColorTableMode? ColorTableMode { get; set; }
///
public void Encode(Image image, Stream stream)
diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
index 553290035..a516b5fef 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs
@@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
-using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -44,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The color table mode: Global or local.
///
- private readonly GifColorTableMode colorTableMode;
+ private GifColorTableMode? colorTableMode;
///
/// A flag indicating whether to ingore the metadata when writing the image.
@@ -56,6 +55,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
private int bitDepth;
+ ///
+ /// Gif specific meta data.
+ ///
+ private GifMetaData gifMetaData;
+
///
/// Initializes a new instance of the class.
///
@@ -66,8 +70,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.memoryAllocator = memoryAllocator;
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer;
- this.colorTableMode = options.ColorTableMode;
this.ignoreMetadata = options.IgnoreMetadata;
+ this.colorTableMode = options.ColorTableMode;
}
///
@@ -82,6 +86,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
+ this.gifMetaData = image.MetaData.GetFormatMetaData(GifFormat.Instance);
+ this.colorTableMode = this.colorTableMode ?? this.gifMetaData.ColorTableMode;
+ bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global);
+
// Quantize the image returning a palette.
QuantizedFrame quantized =
this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
@@ -94,7 +102,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Write the LSD.
int index = this.GetTransparentIndex(quantized);
- bool useGlobalTable = this.colorTableMode.Equals(GifColorTableMode.Global);
this.WriteLogicalScreenDescriptor(image, index, useGlobalTable, stream);
if (useGlobalTable)
@@ -108,7 +115,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Write application extension to allow additional frames.
if (image.Frames.Count > 1)
{
- this.WriteApplicationExtension(stream, image.MetaData.RepeatCount);
+ this.WriteApplicationExtension(stream, this.gifMetaData.RepeatCount);
}
if (useGlobalTable)
@@ -136,8 +143,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame frame = image.Frames[i];
-
- this.WriteGraphicalControlExtension(frame.MetaData, transparencyIndex, stream);
+ GifFrameMetaData frameMetaData = frame.MetaData.GetFormatMetaData(GifFormat.Instance);
+ this.WriteGraphicalControlExtension(frameMetaData, transparencyIndex, stream);
this.WriteImageDescriptor(frame, false, stream);
if (i == 0)
@@ -146,7 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
- using (QuantizedFrame paletteQuantized = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
+ using (QuantizedFrame paletteQuantized
+ = palleteQuantizer.CreateFrameQuantizer(() => quantized.Palette).QuantizeFrame(frame))
{
this.WriteImageData(paletteQuantized, stream);
}
@@ -157,20 +165,36 @@ namespace SixLabors.ImageSharp.Formats.Gif
private void EncodeLocal(Image image, QuantizedFrame quantized, Stream stream)
where TPixel : struct, IPixel
{
+ ImageFrame previousFrame = null;
+ GifFrameMetaData previousMeta = null;
foreach (ImageFrame frame in image.Frames)
{
+ GifFrameMetaData meta = frame.MetaData.GetFormatMetaData(GifFormat.Instance);
if (quantized is null)
{
- quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
+ // Allow each frame to be encoded at whatever color depth the frame designates if set.
+ if (previousFrame != null
+ && previousMeta.ColorTableLength != meta.ColorTableLength
+ && meta.ColorTableLength > 0)
+ {
+ quantized = this.quantizer.CreateFrameQuantizer(meta.ColorTableLength).QuantizeFrame(frame);
+ }
+ else
+ {
+ quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(frame);
+ }
}
- this.WriteGraphicalControlExtension(frame.MetaData, this.GetTransparentIndex(quantized), stream);
+ this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ this.WriteGraphicalControlExtension(meta, this.GetTransparentIndex(quantized), stream);
this.WriteImageDescriptor(frame, true, stream);
this.WriteColorTable(quantized, stream);
this.WriteImageData(quantized, stream);
quantized?.Dispose();
quantized = null; // So next frame can regenerate it
+ previousFrame = frame;
+ previousMeta = meta;
}
}
@@ -210,10 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// The stream to write to.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void WriteHeader(Stream stream)
- {
- stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
- }
+ private void WriteHeader(Stream stream) => stream.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
///
/// Writes the logical screen descriptor to the stream.
@@ -279,25 +300,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// Application Extension Header
if (repeatCount != 1)
{
- this.buffer[0] = GifConstants.ExtensionIntroducer;
- this.buffer[1] = GifConstants.ApplicationExtensionLabel;
- this.buffer[2] = GifConstants.ApplicationBlockSize;
-
- // Write NETSCAPE2.0
- GifConstants.ApplicationIdentificationBytes.AsSpan().CopyTo(this.buffer.AsSpan(3, 11));
-
- // Application Data ----
- this.buffer[14] = 3; // Application block length
- this.buffer[15] = 1; // Data sub-block index (always 1)
-
- // 0 means loop indefinitely. Count is set as play n + 1 times.
- repeatCount = (ushort)Math.Max(0, repeatCount - 1);
-
- BinaryPrimitives.WriteUInt16LittleEndian(this.buffer.AsSpan(16, 2), repeatCount); // Repeat count for images.
-
- this.buffer[18] = GifConstants.Terminator; // Terminator
-
- stream.Write(this.buffer, 0, 19);
+ var loopingExtension = new GifNetscapeLoopingApplicationExtension(repeatCount);
+ this.WriteExtension(loopingExtension, stream);
}
}
@@ -337,7 +341,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The metadata of the image or frame.
/// The index of the color in the color palette to make transparent.
/// The stream to write to.
- private void WriteGraphicalControlExtension(ImageFrameMetaData metaData, int transparencyIndex, Stream stream)
+ private void WriteGraphicalControlExtension(GifFrameMetaData metaData, int transparencyIndex, Stream stream)
{
byte packedValue = GifGraphicControlExtension.GetPackedValue(
disposalMethod: metaData.DisposalMethod,
@@ -382,7 +386,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
localColorTableFlag: hasColorTable,
interfaceFlag: false,
sortFlag: false,
- localColorTableSize: (byte)this.bitDepth);
+ localColorTableSize: this.bitDepth - 1);
var descriptor = new GifImageDescriptor(
left: 0,
@@ -407,7 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
int pixelCount = image.Palette.Length;
- int colorTableLength = (int)Math.Pow(2, this.bitDepth) * 3; // The maximium number of colors for the bit depth
+ // The maximium number of colors for the bit depth
+ int colorTableLength = ImageMaths.GetColorCountForBitDepth(this.bitDepth) * 3;
Rgb24 rgb = default;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs
index 6353690f4..f91269ced 100644
--- a/src/ImageSharp/Formats/Gif/GifFormat.cs
+++ b/src/ImageSharp/Formats/Gif/GifFormat.cs
@@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Registers the image encoders, decoders and mime type detectors for the gif format.
///
- internal sealed class GifFormat : IImageFormat
+ internal sealed class GifFormat : IImageFormat
{
+ private GifFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static GifFormat Instance { get; } = new GifFormat();
+
///
public string Name => "GIF";
@@ -21,5 +30,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public IEnumerable FileExtensions => GifConstants.FileExtensions;
+
+ ///
+ public GifMetaData CreateDefaultFormatMetaData() => new GifMetaData();
+
+ ///
+ public GifFrameMetaData CreateDefaultFormatFrameMetaData() => new GifFrameMetaData();
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
new file mode 100644
index 000000000..cc04d4831
--- /dev/null
+++ b/src/ImageSharp/Formats/Gif/GifFrameMetaData.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Gif
+{
+ ///
+ /// Provides Gif specific metadata information for the image frame.
+ ///
+ public class GifFrameMetaData
+ {
+ ///
+ /// Gets or sets the length of the color table for paletted images.
+ /// If not 0, then this field indicates the maximum number of colors to use when quantizing the
+ /// image frame.
+ ///
+ public int ColorTableLength { get; set; }
+
+ ///
+ /// Gets or sets the frame delay for animated images.
+ /// If not 0, when utilized in Gif animation, 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 FrameDelay { get; set; }
+
+ ///
+ /// Gets or sets the disposal method for animated images.
+ /// Primarily used in Gif animation, this field indicates the way in which the graphic is to
+ /// be treated after being displayed.
+ ///
+ public GifDisposalMethod DisposalMethod { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
index bfbd334b0..b8f9a03f1 100644
--- a/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Gif/GifImageFormatDetector.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public IImageFormat DetectFormat(ReadOnlySpan header)
{
- return this.IsSupportedFileFormat(header) ? ImageFormats.Gif : null;
+ return this.IsSupportedFileFormat(header) ? GifFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan header)
diff --git a/src/ImageSharp/Formats/Gif/GifMetaData.cs b/src/ImageSharp/Formats/Gif/GifMetaData.cs
new file mode 100644
index 000000000..f58f5dff3
--- /dev/null
+++ b/src/ImageSharp/Formats/Gif/GifMetaData.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Gif
+{
+ ///
+ /// Provides Gif specific metadata information for the image.
+ ///
+ public class GifMetaData
+ {
+ ///
+ /// Gets or sets the number of times any animation is repeated.
+ ///
+ /// 0 means to repeat indefinitely, count is set as play n + 1 times
+ ///
+ ///
+ public ushort RepeatCount { get; set; }
+
+ ///
+ /// Gets or sets the color table mode.
+ ///
+ public GifColorTableMode ColorTableMode { get; set; }
+
+ ///
+ /// Gets or sets the length of the global color table if present.
+ ///
+ public int GlobalColorTableLength { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
index e99f09add..42c202a3d 100644
--- a/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifDecoderOptions.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Text;
+using SixLabors.ImageSharp.MetaData;
namespace SixLabors.ImageSharp.Formats.Gif
{
diff --git a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
index bad6e0031..7dd558c80 100644
--- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
@@ -29,6 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
/// Gets the color table mode: Global or local.
///
- GifColorTableMode ColorTableMode { get; }
+ GifColorTableMode? ColorTableMode { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/ImageExtensions.cs b/src/ImageSharp/Formats/Gif/ImageExtensions.cs
index 1c41285a9..8ddd4247e 100644
--- a/src/ImageSharp/Formats/Gif/ImageExtensions.cs
+++ b/src/ImageSharp/Formats/Gif/ImageExtensions.cs
@@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is null.
public static void SaveAsGif(this Image source, Stream stream, GifEncoder encoder)
where TPixel : struct, IPixel
- => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Gif));
+ => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(GifFormat.Instance));
}
}
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
index 7ec5f2030..cb548d687 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicControlExtension.cs
@@ -53,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// Gets the disposal method which indicates the way in which the
/// graphic is to be treated after being displayed.
///
- public DisposalMethod DisposalMethod => (DisposalMethod)((this.Packed & 0x1C) >> 2);
+ public GifDisposalMethod DisposalMethod => (GifDisposalMethod)((this.Packed & 0x1C) >> 2);
///
/// Gets a value indicating whether transparency flag is to be set.
@@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return MemoryMarshal.Cast(buffer)[0];
}
- public static byte GetPackedValue(DisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
+ public static byte GetPackedValue(GifDisposalMethod disposalMethod, bool userInputFlag = false, bool transparencyFlag = false)
{
/*
Reserved | 3 Bits
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
index c5360729e..c3504dfe7 100644
--- a/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
+++ b/src/ImageSharp/Formats/Gif/Sections/GifImageDescriptor.cs
@@ -81,7 +81,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return MemoryMarshal.Cast(buffer)[0];
}
- public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, byte localColorTableSize)
+ public static byte GetPackedValue(bool localColorTableFlag, bool interfaceFlag, bool sortFlag, int localColorTableSize)
{
/*
Local Color Table Flag | 1 Bit
@@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
value |= 1 << 5;
}
- value |= (byte)(localColorTableSize - 1);
+ value |= (byte)localColorTableSize;
return value;
}
diff --git a/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
new file mode 100644
index 000000000..49a52edf6
--- /dev/null
+++ b/src/ImageSharp/Formats/Gif/Sections/GifNetscapeLoopingApplicationExtension.cs
@@ -0,0 +1,44 @@
+// 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
+{
+ internal readonly struct GifNetscapeLoopingApplicationExtension : IGifExtension
+ {
+ public GifNetscapeLoopingApplicationExtension(ushort repeatCount) => this.RepeatCount = repeatCount;
+
+ public byte Label => GifConstants.ApplicationExtensionLabel;
+
+ ///
+ /// Gets the repeat count.
+ /// 0 means loop indefinitely. Count is set as play n + 1 times.
+ ///
+ public ushort RepeatCount { get; }
+
+ public static GifNetscapeLoopingApplicationExtension Parse(ReadOnlySpan buffer)
+ {
+ ushort repeatCount = BinaryPrimitives.ReadUInt16LittleEndian(buffer.Slice(0, 2));
+ return new GifNetscapeLoopingApplicationExtension(repeatCount);
+ }
+
+ public int WriteTo(Span buffer)
+ {
+ buffer[0] = GifConstants.ApplicationBlockSize;
+
+ // Write NETSCAPE2.0
+ GifConstants.NetscapeApplicationIdentificationBytes.AsSpan().CopyTo(buffer.Slice(1, 11));
+
+ // Application Data ----
+ buffer[12] = 3; // Application block length (always 3)
+ buffer[13] = 1; // Data sub-block indentity (always 1)
+
+ // 0 means loop indefinitely. Count is set as play n + 1 times.
+ BinaryPrimitives.WriteUInt16LittleEndian(buffer.Slice(14, 2), this.RepeatCount);
+
+ return 16; // Length - Introducer + Label + Terminator.
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/IImageFormat.cs b/src/ImageSharp/Formats/IImageFormat.cs
index 15bdc73a8..94191c149 100644
--- a/src/ImageSharp/Formats/IImageFormat.cs
+++ b/src/ImageSharp/Formats/IImageFormat.cs
@@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats
{
///
- /// Describes an image format.
+ /// Defines the contract for an image format.
///
public interface IImageFormat
{
@@ -30,4 +30,34 @@ namespace SixLabors.ImageSharp.Formats
///
IEnumerable FileExtensions { get; }
}
+
+ ///
+ /// Defines the contract for an image format containing metadata.
+ ///
+ /// The type of format metadata.
+ public interface IImageFormat : IImageFormat
+ where TFormatMetaData : class
+ {
+ ///
+ /// Creates a default instance of the format metadata.
+ ///
+ /// The .
+ TFormatMetaData CreateDefaultFormatMetaData();
+ }
+
+ ///
+ /// Defines the contract for an image format containing metadata with multiple frames.
+ ///
+ /// The type of format metadata.
+ /// The type of format frame metadata.
+ public interface IImageFormat : IImageFormat
+ where TFormatMetaData : class
+ where TFormatFrameMetaData : class
+ {
+ ///
+ /// Creates a default instance of the format frame metadata.
+ ///
+ /// The .
+ TFormatFrameMetaData CreateDefaultFormatFrameMetaData();
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
new file mode 100644
index 000000000..4e11568c8
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
@@ -0,0 +1,140 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
+{
+ ///
+ /// Provides methods to evaluate the quality of an image.
+ /// Ported from
+ ///
+ internal static class QualityEvaluator
+ {
+ private static readonly int[] Hash = new int[101]
+ {
+ 1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
+ 632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
+ 555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
+ 397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
+ 299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
+ 243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
+ 192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
+ 143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
+ 94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
+ 45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
+ 0
+ };
+
+ private static readonly int[] Sums = new int[101]
+ {
+ 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
+ 27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
+ 23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
+ 16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
+ 12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
+ 9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
+ 8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
+ 6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
+ 4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
+ 3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
+ 1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
+ 128, 0
+ };
+
+ private static readonly int[] Hash1 = new int[101]
+ {
+ 510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
+ 300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
+ 279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
+ 211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
+ 158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
+ 129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
+ 102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
+ 76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
+ 50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
+ 24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
+ 0
+ };
+
+ private static readonly int[] Sums1 = new int[101]
+ {
+ 16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
+ 12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
+ 9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
+ 6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
+ 4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
+ 3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
+ 3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
+ 2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
+ 1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
+ 1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
+ 667, 592, 518, 441, 369, 292, 221, 151, 86,
+ 64, 0
+ };
+
+ ///
+ /// Returns an estimated quality of the image based on the quantization tables.
+ ///
+ /// The quantization tables.
+ /// The .
+ public static int EstimateQuality(Block8x8F[] quantizationTables)
+ {
+ int quality = 75;
+ float sum = 0;
+
+ for (int i = 0; i < quantizationTables.Length; i++)
+ {
+ ref Block8x8F qTable = ref quantizationTables[i];
+ for (int j = 0; j < Block8x8F.Size; j++)
+ {
+ sum += qTable[j];
+ }
+ }
+
+ ref Block8x8F qTable0 = ref quantizationTables[0];
+ ref Block8x8F qTable1 = ref quantizationTables[1];
+
+ if (!qTable0.Equals(default))
+ {
+ if (!qTable1.Equals(default))
+ {
+ quality = (int)(qTable0[2]
+ + qTable0[53]
+ + qTable1[0]
+ + qTable1[Block8x8F.Size - 1]);
+
+ for (int i = 0; i < 100; i++)
+ {
+ if (quality < Hash[i] && sum < Sums[i])
+ {
+ continue;
+ }
+
+ if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50))
+ {
+ return i + 1;
+ }
+ }
+ }
+ else
+ {
+ quality = (int)(qTable0[2] + qTable0[53]);
+
+ for (int i = 0; i < 100; i++)
+ {
+ if (quality < Hash1[i] && sum < Sums1[i])
+ {
+ continue;
+ }
+
+ if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50))
+ {
+ return i + 1;
+ }
+ }
+ }
+ }
+
+ return quality;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
index 4076b7da8..53108de93 100644
--- a/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
@@ -8,17 +8,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
internal interface IJpegEncoderOptions
{
- ///
- /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
- ///
- bool IgnoreMetadata { get; }
-
///
/// Gets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
///
/// The quality of the jpg image from 0 to 100.
- int Quality { get; }
+ int? Quality { get; }
///
/// Gets the subsample ration, that will be used to encode the image.
diff --git a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
index 1d3be063d..cb7fc1944 100644
--- a/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
+++ b/src/ImageSharp/Formats/Jpeg/ImageExtensions.cs
@@ -34,6 +34,6 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is null.
public static void SaveAsJpeg(this Image source, Stream stream, JpegEncoder encoder)
where TPixel : struct, IPixel
- => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Jpeg));
+ => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(JpegFormat.Instance));
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs
index c3bf801ac..9840a2ae8 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegConfigurationModule.cs
@@ -9,12 +9,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public sealed class JpegConfigurationModule : IConfigurationModule
{
///
- public void Configure(Configuration config)
+ public void Configure(Configuration configuration)
{
- config.ImageFormatsManager.SetEncoder(ImageFormats.Jpeg, new JpegEncoder());
- config.ImageFormatsManager.SetDecoder(ImageFormats.Jpeg, new JpegDecoder());
-
- config.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector());
+ configuration.ImageFormatsManager.SetEncoder(JpegFormat.Instance, new JpegEncoder());
+ configuration.ImageFormatsManager.SetDecoder(JpegFormat.Instance, new JpegDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new JpegImageFormatDetector());
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index b46b4b604..011b6100d 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -234,6 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InitExifProfile();
this.InitIccProfile();
this.InitDerivedMetaDataProperties();
+
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.MetaData);
}
@@ -258,11 +259,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.InputStream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)this.InputStream.Position - 2);
+ this.QuantizationTables = new Block8x8F[4];
// Only assign what we need
if (!metadataOnly)
{
- this.QuantizationTables = new Block8x8F[4];
this.dcHuffmanTables = new HuffmanTables();
this.acHuffmanTables = new HuffmanTables();
this.fastACTables = new FastACTables(this.configuration.MemoryAllocator);
@@ -313,15 +314,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
break;
case JpegConstants.Markers.DQT:
- if (metadataOnly)
- {
- this.InputStream.Skip(remaining);
- }
- else
- {
- this.ProcessDefineQuantizationTablesMarker(remaining);
- }
-
+ this.ProcessDefineQuantizationTablesMarker(remaining);
break;
case JpegConstants.Markers.DRI:
@@ -707,6 +700,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
throw new ImageFormatException("DQT has wrong length");
}
+
+ this.MetaData.GetFormatMetaData(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 0f389dee0..d649d3041 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
@@ -11,17 +11,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{
- ///
- /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
- ///
- public bool IgnoreMetadata { get; set; }
-
///
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// Defaults to 75.
///
- public int Quality { get; set; } = 75;
+ public int? Quality { get; set; }
///
/// Gets or sets the subsample ration, that will be used to encode the image.
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index f7b6fe996..32c50d2a0 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -123,19 +123,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private readonly byte[] huffmanBuffer = new byte[179];
///
- /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ /// Gets or sets the subsampling method to use.
///
- private readonly bool ignoreMetadata;
+ private JpegSubsample? subsample;
///
/// The quality, that will be used to encode the image.
///
- private readonly int quality;
-
- ///
- /// Gets or sets the subsampling method to use.
- ///
- private readonly JpegSubsample? subsample;
+ private readonly int? quality;
///
/// The accumulated bits to write to the stream.
@@ -168,11 +163,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The options
public JpegEncoderCore(IJpegEncoderOptions options)
{
- // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
- this.quality = options.Quality.Clamp(1, 100);
- this.subsample = options.Subsample ?? (this.quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
-
- this.ignoreMetadata = options.IgnoreMetadata;
+ this.quality = options.Quality;
+ this.subsample = options.Subsample;
}
///
@@ -195,15 +187,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream = stream;
+ // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
+ int qlty = (this.quality ?? image.MetaData.GetFormatMetaData(JpegFormat.Instance).Quality).Clamp(1, 100);
+ this.subsample = this.subsample ?? (qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
+
// Convert from a quality rating to a scaling factor.
int scale;
- if (this.quality < 50)
+ if (qlty < 50)
{
- scale = 5000 / this.quality;
+ scale = 5000 / qlty;
}
else
{
- scale = 200 - (this.quality * 2);
+ scale = 200 - (qlty * 2);
}
// Initialize the quantization tables.
@@ -767,11 +763,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private void WriteProfiles(Image image)
where TPixel : struct, IPixel
{
- if (this.ignoreMetadata)
- {
- return;
- }
-
image.MetaData.SyncProfiles();
this.WriteExifProfile(image.MetaData.ExifProfile);
this.WriteIccProfile(image.MetaData.IccProfile);
diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs
index 9a18f14d3..94c0895b9 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs
@@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Registers the image encoders, decoders and mime type detectors for the jpeg format.
///
- internal sealed class JpegFormat : IImageFormat
+ internal sealed class JpegFormat : IImageFormat
{
+ private JpegFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static JpegFormat Instance { get; } = new JpegFormat();
+
///
public string Name => "JPEG";
@@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public IEnumerable FileExtensions => JpegConstants.FileExtensions;
+
+ ///
+ public JpegMetaData CreateDefaultFormatMetaData() => new JpegMetaData();
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
index e25957efc..7594f4477 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegImageFormatDetector.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public IImageFormat DetectFormat(ReadOnlySpan header)
{
- return this.IsSupportedFileFormat(header) ? ImageFormats.Jpeg : null;
+ return this.IsSupportedFileFormat(header) ? JpegFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan header)
diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs
new file mode 100644
index 000000000..bd7232e60
--- /dev/null
+++ b/src/ImageSharp/Formats/Jpeg/JpegMetaData.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Jpeg
+{
+ ///
+ /// Provides Jpeg specific metadata information for the image.
+ ///
+ public class JpegMetaData
+ {
+ ///
+ /// Gets or sets the encoded quality.
+ ///
+ public int Quality { get; set; } = 75;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
index f3231fa22..77bc9f7a0 100644
--- a/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
@@ -14,12 +14,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all values.
///
- PngBitDepth BitDepth { get; }
+ PngBitDepth? BitDepth { get; }
///
/// Gets the color type
///
- PngColorType ColorType { get; }
+ PngColorType? ColorType { get; }
///
/// Gets the filter method.
@@ -33,15 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Png
int CompressionLevel { get; }
///
- /// Gets the gamma value, that will be written
- /// the the stream, when the property
- /// is set to true. The default value is 2.2F.
+ /// Gets the gamma value, that will be written the the image.
///
/// The gamma value of the image.
- float Gamma { get; }
+ float? Gamma { get; }
///
- /// Gets quantizer for reducing the color count.
+ /// Gets the quantizer for reducing the color count.
///
IQuantizer Quantizer { get; }
@@ -49,11 +47,5 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the transparency threshold.
///
byte Threshold { get; }
-
- ///
- /// Gets a value indicating whether this instance should write
- /// gamma information to the stream. The default value is false.
- ///
- bool WriteGamma { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/ImageExtensions.cs b/src/ImageSharp/Formats/Png/ImageExtensions.cs
index a65845e02..c73ec6f57 100644
--- a/src/ImageSharp/Formats/Png/ImageExtensions.cs
+++ b/src/ImageSharp/Formats/Png/ImageExtensions.cs
@@ -35,6 +35,6 @@ namespace SixLabors.ImageSharp
/// Thrown if the stream is null.
public static void SaveAsPng(this Image source, Stream stream, PngEncoder encoder)
where TPixel : struct, IPixel
- => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(ImageFormats.Png));
+ => source.Save(stream, encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(PngFormat.Instance));
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngBitDepth.cs b/src/ImageSharp/Formats/Png/PngBitDepth.cs
index 0c22a4c91..396f2c160 100644
--- a/src/ImageSharp/Formats/Png/PngBitDepth.cs
+++ b/src/ImageSharp/Formats/Png/PngBitDepth.cs
@@ -9,6 +9,21 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public enum PngBitDepth
{
+ ///
+ /// 1 bit per sample or per palette index (not per pixel).
+ ///
+ Bit1 = 1,
+
+ ///
+ /// 2 bits per sample or per palette index (not per pixel).
+ ///
+ Bit2 = 2,
+
+ ///
+ /// 4 bits per sample or per palette index (not per pixel).
+ ///
+ Bit4 = 4,
+
///
/// 8 bits per sample or per palette index (not per pixel).
///
diff --git a/src/ImageSharp/Formats/Png/PngChunkType.cs b/src/ImageSharp/Formats/Png/PngChunkType.cs
index e0844ca6b..7654c1701 100644
--- a/src/ImageSharp/Formats/Png/PngChunkType.cs
+++ b/src/ImageSharp/Formats/Png/PngChunkType.cs
@@ -8,58 +8,58 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal enum PngChunkType : uint
{
- ///
- /// The first chunk in a png file. Can only exists once. Contains
- /// common information like the width and the height of the image or
- /// the used compression method.
- ///
- Header = 0x49484452U, // IHDR
-
- ///
- /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
- /// series in the RGB format.
- ///
- Palette = 0x504C5445U, // PLTE
-
///
/// The IDAT chunk contains the actual image data. The image can contains more
/// than one chunk of this type. All chunks together are the whole image.
///
- Data = 0x49444154U, // IDAT
+ Data = 0x49444154U,
///
/// This chunk must appear last. It marks the end of the PNG data stream.
/// The chunk's data field is empty.
///
- End = 0x49454E44U, // IEND
+ End = 0x49454E44U,
///
- /// This chunk specifies that the image uses simple transparency:
- /// either alpha values associated with palette entries (for indexed-color images)
- /// or a single transparent color (for grayscale and true color images).
+ /// The first chunk in a png file. Can only exists once. Contains
+ /// common information like the width and the height of the image or
+ /// the used compression method.
///
- PaletteAlpha = 0x74524E53U, // tRNS
+ Header = 0x49484452U,
///
- /// Textual information that the encoder wishes to record with the image can be stored in
- /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
+ /// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
+ /// series in the RGB format.
+ ///
+ Palette = 0x504C5445U,
+
+ ///
+ /// The eXIf data chunk which contains the Exif profile.
///
- Text = 0x74455874U, // tEXt
+ Exif = 0x65584966U,
///
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
///
- Gamma = 0x67414D41U, // gAMA
+ Gamma = 0x67414D41U,
///
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
///
- Physical = 0x70485973U, // pHYs
+ Physical = 0x70485973U,
///
- /// The data chunk which contains the Exif profile.
+ /// Textual information that the encoder wishes to record with the image can be stored in
+ /// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
+ ///
+ Text = 0x74455874U,
+
+ ///
+ /// This chunk specifies that the image uses simple transparency:
+ /// either alpha values associated with palette entries (for indexed-color images)
+ /// or a single transparent color (for grayscale and true color images).
///
- Exif = 0x65584966U // eXIf
+ PaletteAlpha = 0x74524E53U
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs
index 64dad23bc..3c9fddbad 100644
--- a/src/ImageSharp/Formats/Png/PngConfigurationModule.cs
+++ b/src/ImageSharp/Formats/Png/PngConfigurationModule.cs
@@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public void Configure(Configuration configuration)
{
- configuration.ImageFormatsManager.SetEncoder(ImageFormats.Png, new PngEncoder());
- configuration.ImageFormatsManager.SetDecoder(ImageFormats.Png, new PngDecoder());
+ configuration.ImageFormatsManager.SetEncoder(PngFormat.Instance, new PngEncoder());
+ configuration.ImageFormatsManager.SetDecoder(PngFormat.Instance, new PngDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new PngImageFormatDetector());
}
}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index aa96b926c..7837c2da5 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -10,7 +10,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
@@ -217,7 +216,8 @@ namespace SixLabors.ImageSharp.Formats.Png
public Image Decode(Stream stream)
where TPixel : struct, IPixel
{
- var metadata = new ImageMetaData();
+ var metaData = new ImageMetaData();
+ var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
Image image = null;
@@ -232,16 +232,19 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
- this.ReadHeaderChunk(chunk.Data.Array);
+ this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
+ this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Gamma:
+ this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
if (image is null)
{
- this.InitializeImage(metadata, out image);
+ this.InitializeImage(metaData, out image);
}
deframeStream.AllocateNewBytes(chunk.Length);
@@ -260,14 +263,14 @@ namespace SixLabors.ImageSharp.Formats.Png
this.AssignTransparentMarkers(alpha);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
+ this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
byte[] exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
- metadata.ExifProfile = new ExifProfile(exifData);
+ metaData.ExifProfile = new ExifProfile(exifData);
}
break;
@@ -303,7 +306,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The containing image data.
public IImageInfo Identify(Stream stream)
{
- var metadata = new ImageMetaData();
+ var metaData = new ImageMetaData();
+ var pngMetaData = metaData.GetFormatMetaData(PngFormat.Instance);
this.currentStream = stream;
this.currentStream.Skip(8);
try
@@ -315,17 +319,20 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
- this.ReadHeaderChunk(chunk.Data.Array);
+ this.ReadHeaderChunk(pngMetaData, chunk.Data.Array);
this.ValidateHeader();
break;
case PngChunkType.Physical:
- this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
+ this.ReadPhysicalChunk(metaData, chunk.Data.GetSpan());
+ break;
+ case PngChunkType.Gamma:
+ this.ReadGammaChunk(pngMetaData, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
- this.ReadTextChunk(metadata, chunk.Data.Array, chunk.Length);
+ this.ReadTextChunk(metaData, chunk.Data.Array, chunk.Length);
break;
case PngChunkType.End:
this.isEndChunkReached = true;
@@ -349,7 +356,7 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("PNG Image does not contain a header chunk");
}
- return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
+ return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metaData);
}
///
@@ -360,9 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReadByteLittleEndian(ReadOnlySpan buffer, int offset)
- {
- return (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
- }
+ => (byte)(((buffer[offset] & 0xFF) << 16) | (buffer[offset + 1] & 0xFF));
///
/// Attempts to convert a byte array to a new array where each value in the original array is represented by the
@@ -430,6 +435,18 @@ namespace SixLabors.ImageSharp.Formats.Png
metadata.VerticalResolution = vResolution;
}
+ ///
+ /// Reads the data chunk containing gamma data.
+ ///
+ /// The metadata to read to.
+ /// The data containing physical data.
+ private void ReadGammaChunk(PngMetaData pngMetadata, ReadOnlySpan data)
+ {
+ // The value is encoded as a 4-byte unsigned integer, representing gamma times 100000.
+ // For example, a gamma of 1/2.2 would be stored as 45455.
+ pngMetadata.Gamma = BinaryPrimitives.ReadUInt32BigEndian(data) / 100_000F;
+ }
+
///
/// Initializes the image and various buffers needed for processing
///
@@ -711,7 +728,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
- int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
+ int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
if (!this.hasTrans)
{
@@ -933,7 +950,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
case PngColorType.Grayscale:
- int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1);
+ int factor = 255 / (ImageMaths.GetColorCountForBitDepth(this.header.BitDepth) - 1);
if (!this.hasTrans)
{
@@ -1270,17 +1287,22 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Reads a header chunk from the data.
///
+ /// The png metadata.
/// The containing data.
- private void ReadHeaderChunk(ReadOnlySpan data)
+ private void ReadHeaderChunk(PngMetaData pngMetaData, ReadOnlySpan data)
{
+ byte bitDepth = data[8];
this.header = new PngHeader(
width: BinaryPrimitives.ReadInt32BigEndian(data.Slice(0, 4)),
height: BinaryPrimitives.ReadInt32BigEndian(data.Slice(4, 4)),
- bitDepth: data[8],
+ bitDepth: bitDepth,
colorType: (PngColorType)data[9],
compressionMethod: data[10],
filterMethod: data[11],
interlaceMethod: (PngInterlaceMode)data[12]);
+
+ pngMetaData.BitDepth = (PngBitDepth)bitDepth;
+ pngMetaData.ColorType = this.header.ColorType;
}
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoder.cs b/src/ImageSharp/Formats/Png/PngEncoder.cs
index 109e6ad77..05d687a88 100644
--- a/src/ImageSharp/Formats/Png/PngEncoder.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoder.cs
@@ -17,12 +17,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets or sets the number of bits per sample or per palette index (not per pixel).
/// Not all values are allowed for all values.
///
- public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
+ public PngBitDepth? BitDepth { get; set; }
///
/// Gets or sets the color type.
///
- public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
+ public PngColorType? ColorType { get; set; }
///
/// Gets or sets the filter method.
@@ -36,18 +36,15 @@ namespace SixLabors.ImageSharp.Formats.Png
public int CompressionLevel { get; set; } = 6;
///
- /// Gets or sets the gamma value, that will be written
- /// the the stream, when the property
- /// is set to true. The default value is 2.2F.
+ /// Gets or sets the gamma value, that will be written the the image.
///
- /// The gamma value of the image.
- public float Gamma { get; set; } = 2.2F;
+ public float? Gamma { get; set; }
///
/// Gets or sets quantizer for reducing the color count.
/// Defaults to the
///
- public IQuantizer Quantizer { get; set; } = new WuQuantizer();
+ public IQuantizer Quantizer { get; set; }
///
/// Gets or sets the transparency threshold.
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index ffe29aeca..906e6d000 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -4,7 +4,6 @@
using System;
using System.Buffers.Binary;
using System.IO;
-using System.Linq;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Png.Filters;
@@ -45,49 +44,49 @@ namespace SixLabors.ImageSharp.Formats.Png
private readonly Crc32 crc = new Crc32();
///
- /// The png bit depth
+ /// The png filter method.
///
- private readonly PngBitDepth pngBitDepth;
+ private readonly PngFilterMethod pngFilterMethod;
///
- /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
+ /// Gets or sets the CompressionLevel value
///
- private readonly bool use16Bit;
+ private readonly int compressionLevel;
///
- /// The png color type.
+ /// Gets or sets the alpha threshold value
///
- private readonly PngColorType pngColorType;
+ private readonly byte threshold;
///
- /// The png filter method.
+ /// The quantizer for reducing the color count.
///
- private readonly PngFilterMethod pngFilterMethod;
+ private IQuantizer quantizer;
///
- /// The quantizer for reducing the color count.
+ /// Gets or sets a value indicating whether to write the gamma chunk
///
- private readonly IQuantizer quantizer;
+ private bool writeGamma;
///
- /// Gets or sets the CompressionLevel value
+ /// The png bit depth
///
- private readonly int compressionLevel;
+ private PngBitDepth? pngBitDepth;
///
- /// Gets or sets the Gamma value
+ /// Gets or sets a value indicating whether to use 16 bit encoding for supported color types.
///
- private readonly float gamma;
+ private bool use16Bit;
///
- /// Gets or sets the Threshold value
+ /// The png color type.
///
- private readonly byte threshold;
+ private PngColorType? pngColorType;
///
- /// Gets or sets a value indicating whether to Write Gamma
+ /// Gets or sets the Gamma value
///
- private readonly bool writeGamma;
+ private float? gamma;
///
/// The image width.
@@ -158,14 +157,12 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.memoryAllocator = memoryAllocator;
this.pngBitDepth = options.BitDepth;
- this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
this.pngColorType = options.ColorType;
this.pngFilterMethod = options.FilterMethod;
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
this.threshold = options.Threshold;
- this.writeGamma = options.WriteGamma;
}
///
@@ -183,23 +180,41 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width;
this.height = image.Height;
+ // Always take the encoder options over the metadata values.
+ PngMetaData pngMetaData = image.MetaData.GetFormatMetaData(PngFormat.Instance);
+ this.gamma = this.gamma ?? pngMetaData.Gamma;
+ this.writeGamma = this.gamma > 0;
+ this.pngColorType = this.pngColorType ?? pngMetaData.ColorType;
+ this.pngBitDepth = this.pngBitDepth ?? pngMetaData.BitDepth;
+ this.use16Bit = this.pngBitDepth.Equals(PngBitDepth.Bit16);
+
stream.Write(PngConstants.HeaderBytes, 0, PngConstants.HeaderBytes.Length);
QuantizedFrame quantized = null;
ReadOnlySpan quantizedPixelsSpan = default;
if (this.pngColorType == PngColorType.Palette)
{
+ byte bits;
+
+ // Use the metadata to determine what quantization depth to use if no quantizer has been set.
+ if (this.quantizer == null)
+ {
+ bits = (byte)Math.Min(8u, (short)this.pngBitDepth);
+ int colorSize = ImageMaths.GetColorCountForBitDepth(bits);
+ this.quantizer = new WuQuantizer(colorSize);
+ }
+
// Create quantized frame returning the palette and set the bit depth.
quantized = this.quantizer.CreateFrameQuantizer().QuantizeFrame(image.Frames.RootFrame);
quantizedPixelsSpan = quantized.GetPixelSpan();
- byte bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
+ bits = (byte)ImageMaths.GetBitsNeededForColorDepth(quantized.Palette.Length).Clamp(1, 8);
// Png only supports in four pixel depths: 1, 2, 4, and 8 bits when using the PLTE chunk
if (bits == 3)
{
bits = 4;
}
- else if (bits >= 5 || bits <= 7)
+ else if (bits >= 5 && bits <= 7)
{
bits = 8;
}
@@ -217,7 +232,7 @@ namespace SixLabors.ImageSharp.Formats.Png
width: image.Width,
height: image.Height,
bitDepth: this.bitDepth,
- colorType: this.pngColorType,
+ colorType: this.pngColorType.Value,
compressionMethod: 0, // None
filterMethod: 0,
interlaceMethod: 0); // TODO: Can't write interlaced yet.
@@ -549,7 +564,7 @@ namespace SixLabors.ImageSharp.Formats.Png
byte pixelCount = palette.Length.ToByte();
// Get max colors for bit depth.
- int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
+ int colorTableLength = ImageMaths.GetColorCountForBitDepth(header.BitDepth) * 3;
Rgba32 rgba = default;
bool anyAlpha = false;
@@ -693,7 +708,7 @@ namespace SixLabors.ImageSharp.Formats.Png
private void WriteDataChunks(ImageFrame pixels, ReadOnlySpan quantizedPixelsSpan, Stream stream)
where TPixel : struct, IPixel
{
- this.bytesPerScanline = this.width * this.bytesPerPixel;
+ this.bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = this.bytesPerScanline + 1;
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
@@ -781,10 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Writes the chunk end to the stream.
///
/// The containing image data.
- private void WriteEndChunk(Stream stream)
- {
- this.WriteChunk(stream, PngChunkType.End, null);
- }
+ private void WriteEndChunk(Stream stream) => this.WriteChunk(stream, PngChunkType.End, null);
///
/// Writes a chunk to the stream.
@@ -792,10 +804,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The to write to.
/// The type of chunk to write.
/// The containing data.
- private void WriteChunk(Stream stream, PngChunkType type, byte[] data)
- {
- this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
- }
+ private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
///
/// Writes a chunk of a specified length to the stream at the given offset.
@@ -827,5 +836,26 @@ namespace SixLabors.ImageSharp.Formats.Png
stream.Write(this.buffer, 0, 4); // write the crc
}
+
+ ///
+ /// Calculates the scanline length.
+ ///
+ /// The width of the row.
+ ///
+ /// The representing the length.
+ ///
+ private int CalculateScanlineLength(int width)
+ {
+ int mod = this.bitDepth == 16 ? 16 : 8;
+ int scanlineLength = width * this.bitDepth * this.bytesPerPixel;
+
+ int amount = scanlineLength % mod;
+ if (amount != 0)
+ {
+ scanlineLength += mod - amount;
+ }
+
+ return scanlineLength / mod;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs
index 142f66071..b5223cb5a 100644
--- a/src/ImageSharp/Formats/Png/PngFormat.cs
+++ b/src/ImageSharp/Formats/Png/PngFormat.cs
@@ -8,8 +8,17 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Registers the image encoders, decoders and mime type detectors for the png format.
///
- internal sealed class PngFormat : IImageFormat
+ internal sealed class PngFormat : IImageFormat
{
+ private PngFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static PngFormat Instance { get; } = new PngFormat();
+
///
public string Name => "PNG";
@@ -21,5 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public IEnumerable FileExtensions => PngConstants.FileExtensions;
+
+ ///
+ public PngMetaData CreateDefaultFormatMetaData() => new PngMetaData();
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
index c1c039a1b..5deed86e3 100644
--- a/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Png/PngImageFormatDetector.cs
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public IImageFormat DetectFormat(ReadOnlySpan header)
{
- return this.IsSupportedFileFormat(header) ? ImageFormats.Png : null;
+ return this.IsSupportedFileFormat(header) ? PngFormat.Instance : null;
}
private bool IsSupportedFileFormat(ReadOnlySpan header)
diff --git a/src/ImageSharp/Formats/Png/PngMetaData.cs b/src/ImageSharp/Formats/Png/PngMetaData.cs
new file mode 100644
index 000000000..1eb3cdad6
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/PngMetaData.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// Provides Png specific metadata information for the image.
+ ///
+ public class PngMetaData
+ {
+ ///
+ /// Gets or sets the number of bits per sample or per palette index (not per pixel).
+ /// Not all values are allowed for all values.
+ ///
+ public PngBitDepth BitDepth { get; set; } = PngBitDepth.Bit8;
+
+ ///
+ /// Gets or sets the color type.
+ ///
+ public PngColorType ColorType { get; set; } = PngColorType.RgbWithAlpha;
+
+ ///
+ /// Gets or sets the gamma value for the image.
+ ///
+ public float Gamma { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/ImageFormats.cs b/src/ImageSharp/ImageFormats.cs
deleted file mode 100644
index bc437e5a5..000000000
--- a/src/ImageSharp/ImageFormats.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using SixLabors.ImageSharp.Formats;
-using SixLabors.ImageSharp.Formats.Bmp;
-using SixLabors.ImageSharp.Formats.Gif;
-using SixLabors.ImageSharp.Formats.Jpeg;
-using SixLabors.ImageSharp.Formats.Png;
-
-namespace SixLabors.ImageSharp
-{
- ///
- /// The static collection of all the default image formats
- ///
- public static class ImageFormats
- {
- ///
- /// The format details for the jpegs.
- ///
- public static readonly IImageFormat Jpeg = new JpegFormat();
-
- ///
- /// The format details for the pngs.
- ///
- public static readonly IImageFormat Png = new PngFormat();
-
- ///
- /// The format details for the gifs.
- ///
- public static readonly IImageFormat Gif = new GifFormat();
-
- ///
- /// The format details for the bitmaps.
- ///
- public static readonly IImageFormat Bmp = new BmpFormat();
- }
-}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs b/src/ImageSharp/MetaData/FrameDecodingMode.cs
similarity index 91%
rename from src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
rename to src/ImageSharp/MetaData/FrameDecodingMode.cs
index 05791c92e..2863fbf8f 100644
--- a/src/ImageSharp/Formats/Gif/FrameDecodingMode.cs
+++ b/src/ImageSharp/MetaData/FrameDecodingMode.cs
@@ -1,7 +1,7 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-namespace SixLabors.ImageSharp.Formats.Gif
+namespace SixLabors.ImageSharp.MetaData
{
///
/// Enumerated frame process modes to apply to multi-frame images.
diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs
index 47a2fb775..4b819e201 100644
--- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs
+++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.Formats.Gif;
+using System;
+using System.Collections.Generic;
+using SixLabors.ImageSharp.Formats;
namespace SixLabors.ImageSharp.MetaData
{
@@ -10,6 +12,8 @@ namespace SixLabors.ImageSharp.MetaData
///
public sealed class ImageFrameMetaData
{
+ private readonly Dictionary formatMetaData = new Dictionary();
+
///
/// Initializes a new instance of the class.
///
@@ -28,32 +32,39 @@ namespace SixLabors.ImageSharp.MetaData
{
DebugGuard.NotNull(other, nameof(other));
- this.FrameDelay = other.FrameDelay;
- this.DisposalMethod = other.DisposalMethod;
+ foreach (KeyValuePair meta in other.formatMetaData)
+ {
+ this.formatMetaData.Add(meta.Key, meta.Value);
+ }
}
///
- /// Gets or sets the frame delay for animated images.
- /// If not 0, when utilized in Gif animation, 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 FrameDelay { get; set; }
-
- ///
- /// Gets or sets the disposal method for animated images.
- /// Primarily used in Gif animation, this field indicates the way in which the graphic is to
- /// be treated after being displayed.
+ /// Clones this ImageFrameMetaData.
///
- public DisposalMethod DisposalMethod { get; set; }
+ /// The cloned instance.
+ public ImageFrameMetaData Clone() => new ImageFrameMetaData(this);
///
- /// Clones this ImageFrameMetaData.
+ /// Gets the metadata value associated with the specified key.
///
- /// The cloned instance.
- public ImageFrameMetaData Clone()
+ /// The type of format metadata.
+ /// The type of format frame metadata.
+ /// The key of the value to get.
+ ///
+ /// The .
+ ///
+ public TFormatFrameMetaData GetFormatMetaData(IImageFormat key)
+ where TFormatMetaData : class
+ where TFormatFrameMetaData : class
{
- return new ImageFrameMetaData(this);
+ if (this.formatMetaData.TryGetValue(key, out object meta))
+ {
+ return (TFormatFrameMetaData)meta;
+ }
+
+ TFormatFrameMetaData newMeta = key.CreateDefaultFormatFrameMetaData();
+ this.formatMetaData[key] = newMeta;
+ return newMeta;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs
index 40880bd08..7e74157e7 100644
--- a/src/ImageSharp/MetaData/ImageMetaData.cs
+++ b/src/ImageSharp/MetaData/ImageMetaData.cs
@@ -1,7 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.Collections.Generic;
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.MetaData.Profiles.Icc;
@@ -24,6 +26,7 @@ namespace SixLabors.ImageSharp.MetaData
///
public const double DefaultVerticalResolution = 96;
+ private readonly Dictionary formatMetaData = new Dictionary();
private double horizontalResolution;
private double verticalResolution;
@@ -48,7 +51,11 @@ namespace SixLabors.ImageSharp.MetaData
this.HorizontalResolution = other.HorizontalResolution;
this.VerticalResolution = other.VerticalResolution;
this.ResolutionUnits = other.ResolutionUnits;
- this.RepeatCount = other.RepeatCount;
+
+ foreach (KeyValuePair meta in other.formatMetaData)
+ {
+ this.formatMetaData.Add(meta.Key, meta.Value);
+ }
foreach (ImageProperty property in other.Properties)
{
@@ -125,10 +132,31 @@ namespace SixLabors.ImageSharp.MetaData
public IList Properties { get; } = new List();
///
- /// Gets or sets the number of times any animation is repeated.
- /// 0 means to repeat indefinitely.
+ /// Gets the metadata value associated with the specified key.
+ ///
+ /// The type of metadata.
+ /// The key of the value to get.
+ ///
+ /// The .
+ ///
+ public TFormatMetaData GetFormatMetaData(IImageFormat key)
+ where TFormatMetaData : class
+ {
+ if (this.formatMetaData.TryGetValue(key, out object meta))
+ {
+ return (TFormatMetaData)meta;
+ }
+
+ TFormatMetaData newMeta = key.CreateDefaultFormatMetaData();
+ this.formatMetaData[key] = newMeta;
+ return newMeta;
+ }
+
+ ///
+ /// Clones this into a new instance
///
- public ushort RepeatCount { get; set; }
+ /// The cloned metadata instance
+ public ImageMetaData Clone() => new ImageMetaData(this);
///
/// Looks up a property with the provided name.
@@ -153,21 +181,9 @@ namespace SixLabors.ImageSharp.MetaData
return false;
}
- ///
- /// Clones this into a new instance
- ///
- /// The cloned metadata instance
- public ImageMetaData Clone()
- {
- return new ImageMetaData(this);
- }
-
///
/// Synchronizes the profiles with the current meta data.
///
- internal void SyncProfiles()
- {
- this.ExifProfile?.Sync(this);
- }
+ internal void SyncProfiles() => this.ExifProfile?.Sync(this);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
index 0f6846d1b..3da09cde0 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/IQuantizer.cs
@@ -23,5 +23,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The
IFrameQuantizer CreateFrameQuantizer()
where TPixel : struct, IPixel;
+
+ ///
+ /// Creates the generic frame quantizer
+ ///
+ /// The pixel format.
+ /// The maximum number of colors to hold in the color palette.
+ /// The
+ IFrameQuantizer CreateFrameQuantizer(int maxColors)
+ where TPixel : struct, IPixel;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
index 3eac70eea..39546d63f 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeFrameQuantizer{TPixel}.cs
@@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// Maximum allowed color depth
///
- private readonly byte colors;
+ private readonly int colors;
///
/// Stores the tree
@@ -43,9 +43,23 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// the second pass quantizes a color based on the nodes in the tree
///
public OctreeFrameQuantizer(OctreeQuantizer quantizer)
+ : this(quantizer, quantizer.MaxColors)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The octree quantizer.
+ /// The maximum number of colors to hold in the color palette.
+ ///
+ /// The Octree quantizer is a two pass algorithm. The initial pass sets up the Octree,
+ /// the second pass quantizes a color based on the nodes in the tree
+ ///
+ public OctreeFrameQuantizer(OctreeQuantizer quantizer, int maxColors)
: base(quantizer, false)
{
- this.colors = (byte)quantizer.MaxColors;
+ this.colors = maxColors;
this.octree = new Octree(ImageMaths.GetBitsNeededForColorDepth(this.colors).Clamp(1, 8));
}
@@ -261,13 +275,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPixel[] Palletize(int colorCount)
{
- while (this.Leaves > colorCount)
+ while (this.Leaves > colorCount - 1)
{
this.Reduce();
}
// Now palletize the nodes
- var palette = new TPixel[colorCount + 1];
+ var palette = new TPixel[colorCount];
int paletteIndex = 0;
this.root.ConstructPalette(palette, ref paletteIndex);
@@ -285,10 +299,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The .
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba)
- {
- return this.root.GetPaletteIndex(ref pixel, 0, ref rgba);
- }
+ public int GetPaletteIndex(ref TPixel pixel, ref Rgba32 rgba) => this.root.GetPaletteIndex(ref pixel, 0, ref rgba);
///
/// Keep track of the previous node that was quantized
@@ -297,10 +308,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The node last quantized
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- protected void TrackPrevious(OctreeNode node)
- {
- this.previousNode = node;
- }
+ protected void TrackPrevious(OctreeNode node) => this.previousNode = node;
///
/// Reduce the depth of the tree
diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
index 385f6246f..22bb5223f 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs
@@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
public class OctreeQuantizer : IQuantizer
{
+ ///
+ /// The default maximum number of colors to use when quantizing the image.
+ ///
+ public const int DefaultMaxColors = 256;
+
///
/// Initializes a new instance of the class.
///
@@ -26,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// Initializes a new instance of the class.
///
- /// The maximum number of colors to hold in the color palette
+ /// The maximum number of colors to hold in the color palette.
public OctreeQuantizer(int maxColors)
: this(GetDiffuser(true), maxColors)
{
@@ -37,7 +42,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// Whether to apply dithering to the output image
public OctreeQuantizer(bool dither)
- : this(GetDiffuser(dither), 255)
+ : this(GetDiffuser(dither), DefaultMaxColors)
{
}
@@ -46,7 +51,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// The error diffusion algorithm, if any, to apply to the output image
public OctreeQuantizer(IErrorDiffuser diffuser)
- : this(diffuser, 255)
+ : this(diffuser, DefaultMaxColors)
{
}
@@ -57,10 +62,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The maximum number of colors to hold in the color palette
public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors)
{
- Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors));
-
this.Diffuser = diffuser;
- this.MaxColors = maxColors;
+ this.MaxColors = maxColors.Clamp(1, DefaultMaxColors);
}
///
@@ -76,6 +79,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel
=> new OctreeFrameQuantizer(this);
+ ///
+ public IFrameQuantizer CreateFrameQuantizer(int maxColors)
+ where TPixel : struct, IPixel
+ {
+ maxColors = maxColors.Clamp(1, DefaultMaxColors);
+ return new OctreeFrameQuantizer(this, maxColors);
+ }
+
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
index 8df81b426..cdf3514e2 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteFrameQuantizer{TPixel}.cs
@@ -36,6 +36,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public PaletteFrameQuantizer(PaletteQuantizer quantizer, TPixel[] colors)
: base(quantizer, true)
{
+ // TODO: Why is this value constrained? Gif has limitations but theoretically
+ // we might want to reduce the palette of an image to greater than that limitation.
Guard.MustBeBetweenOrEqualTo(colors.Length, 1, 256, nameof(colors));
this.palette = colors;
this.paletteVector = new Vector4[this.palette.Length];
diff --git a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
index 8ae917718..27ef05dfe 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
@@ -37,10 +37,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Initializes a new instance of the class.
///
/// The error diffusion algorithm, if any, to apply to the output image
- public PaletteQuantizer(IErrorDiffuser diffuser)
- {
- this.Diffuser = diffuser;
- }
+ public PaletteQuantizer(IErrorDiffuser diffuser) => this.Diffuser = diffuser;
///
public IErrorDiffuser Diffuser { get; }
@@ -50,6 +47,21 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel
=> this.CreateFrameQuantizer(() => NamedColors.WebSafePalette);
+ ///
+ public IFrameQuantizer CreateFrameQuantizer(int maxColors)
+ where TPixel : struct, IPixel
+ {
+ TPixel[] websafe = NamedColors.WebSafePalette;
+ int max = Math.Min(maxColors, websafe.Length);
+
+ if (max != websafe.Length)
+ {
+ return this.CreateFrameQuantizer(() => NamedColors.WebSafePalette.AsSpan(0, max).ToArray());
+ }
+
+ return this.CreateFrameQuantizer(() => websafe);
+ }
+
///
/// Gets the palette to use to quantize the image.
///
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
index 021dc62fb..d71221b9d 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WuFrameQuantizer{TPixel}.cs
@@ -3,7 +3,6 @@
using System;
using System.Buffers;
-using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -128,11 +127,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// the second pass quantizes a color based on the position in the histogram.
///
public WuFrameQuantizer(WuQuantizer quantizer)
- : base(quantizer, false)
+ : this(quantizer, quantizer.MaxColors)
{
- this.colors = quantizer.MaxColors;
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The wu quantizer.
+ /// The maximum number of colors to hold in the color palette.
+ ///
+ /// The Wu quantizer is a two pass algorithm. The initial pass sets up the 3-D color histogram,
+ /// the second pass quantizes a color based on the position in the histogram.
+ ///
+ public WuFrameQuantizer(WuQuantizer quantizer, int maxColors)
+ : base(quantizer, false) => this.colors = maxColors;
+
///
public override QuantizedFrame QuantizeFrame(ImageFrame image)
{
diff --git a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
index 3aa1f4c5e..5123e737d 100644
--- a/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
+++ b/src/ImageSharp/Processing/Processors/Quantization/WuQuantizer.cs
@@ -14,6 +14,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
public class WuQuantizer : IQuantizer
{
+ ///
+ /// The default maximum number of colors to use when quantizing the image.
+ ///
+ public const int DefaultMaxColors = 256;
+
///
/// Initializes a new instance of the class.
///
@@ -36,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// Whether to apply dithering to the output image
public WuQuantizer(bool dither)
- : this(GetDiffuser(dither), 255)
+ : this(GetDiffuser(dither), DefaultMaxColors)
{
}
@@ -45,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
///
/// The error diffusion algorithm, if any, to apply to the output image
public WuQuantizer(IErrorDiffuser diffuser)
- : this(diffuser, 255)
+ : this(diffuser, DefaultMaxColors)
{
}
@@ -56,10 +61,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// The maximum number of colors to hold in the color palette
public WuQuantizer(IErrorDiffuser diffuser, int maxColors)
{
- Guard.MustBeBetweenOrEqualTo(maxColors, 1, 255, nameof(maxColors));
-
this.Diffuser = diffuser;
- this.MaxColors = maxColors;
+ this.MaxColors = maxColors.Clamp(1, DefaultMaxColors);
}
///
@@ -75,6 +78,14 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : struct, IPixel
=> new WuFrameQuantizer(this);
+ ///
+ public IFrameQuantizer CreateFrameQuantizer(int maxColors)
+ where TPixel : struct, IPixel
+ {
+ maxColors = maxColors.Clamp(1, DefaultMaxColors);
+ return new WuFrameQuantizer(this, maxColors);
+ }
+
private static IErrorDiffuser GetDiffuser(bool dither) => dither ? KnownDiffusers.FloydSteinberg : null;
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs
index 4bec25f7a..6a8479b2b 100644
--- a/tests/ImageSharp.Tests/ConfigurationTests.cs
+++ b/tests/ImageSharp.Tests/ConfigurationTests.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
using Moq;
+using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.IO;
using Xunit;
// ReSharper disable InconsistentNaming
@@ -37,19 +38,13 @@ namespace SixLabors.ImageSharp.Tests
/// Test that the default configuration is not null.
///
[Fact]
- public void TestDefaultConfigurationIsNotNull()
- {
- Assert.True(Configuration.Default != null);
- }
+ public void TestDefaultConfigurationIsNotNull() => Assert.True(Configuration.Default != null);
///
/// Test that the default configuration read origin options is set to begin.
///
[Fact]
- public void TestDefaultConfigurationReadOriginIsCurrent()
- {
- Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current);
- }
+ public void TestDefaultConfigurationReadOriginIsCurrent() => Assert.True(Configuration.Default.ReadOrigin == ReadOrigin.Current);
///
/// Test that the default configuration parallel options max degrees of parallelism matches the
@@ -101,7 +96,7 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(count, config.ImageFormats.Count());
- config.ImageFormatsManager.AddImageFormat(ImageFormats.Bmp);
+ config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance);
Assert.Equal(count, config.ImageFormats.Count());
}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index d887d23ad..b9f855cf1 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -28,10 +28,14 @@ namespace SixLabors.ImageSharp.Tests
{ TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
- public BmpEncoderTests(ITestOutputHelper output)
+ public static readonly TheoryData BmpBitsPerPixelFiles =
+ new TheoryData
{
- this.Output = output;
- }
+ { TestImages.Bmp.Car, BmpBitsPerPixel.Pixel24 },
+ { TestImages.Bmp.Bit32Rgb, BmpBitsPerPixel.Pixel32 }
+ };
+
+ public BmpEncoderTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@@ -61,13 +65,34 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
- [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
- public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
- where TPixel : struct, IPixel
+ [MemberData(nameof(BmpBitsPerPixelFiles))]
+ public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel)
{
- TestBmpEncoderCore(provider, bitsPerPixel);
+ var options = new BmpEncoder();
+
+ var testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateImage())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+
+ memStream.Position = 0;
+ using (var output = Image.Load(memStream))
+ {
+ BmpMetaData meta = output.MetaData.GetFormatMetaData(BmpFormat.Instance);
+
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
+ }
+ }
+ }
}
+ [Theory]
+ [WithTestPatternImages(nameof(BitsPerPixel), 24, 24, PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.Rgb24)]
+ public void Encode_IsNotBoundToSinglePixelType(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
+ where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel);
+
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel), 48, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel), 47, 8, PixelTypes.Rgba32)]
@@ -75,10 +100,7 @@ namespace SixLabors.ImageSharp.Tests
[WithSolidFilledImages(nameof(BitsPerPixel), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel), 7, 5, PixelTypes.Rgba32)]
public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
- where TPixel : struct, IPixel
- {
- TestBmpEncoderCore(provider, bitsPerPixel);
- }
+ where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel);
private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel
diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
index 23b806767..f8b035ca8 100644
--- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
+++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs
@@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Tests
using (Image image = file.CreateImage())
{
string filename = path + "/" + file.FileNameWithoutExtension + ".txt";
- File.WriteAllText(filename, image.ToBase64String(ImageFormats.Png));
+ File.WriteAllText(filename, image.ToBase64String(PngFormat.Instance));
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
index e9104ef8d..4a17f867f 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs
@@ -179,5 +179,49 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
Assert.True(fileInfoGlobal.Length < fileInfoLocal.Length);
}
}
+
+ [Fact]
+ public void NonMutatingEncodePreservesPaletteCount()
+ {
+ using (var inStream = new MemoryStream(TestFile.Create(TestImages.Gif.Leo).Bytes))
+ using (var outStream = new MemoryStream())
+ {
+ inStream.Position = 0;
+
+ var image = Image.Load(inStream);
+ GifMetaData metaData = image.MetaData.GetFormatMetaData(GifFormat.Instance);
+ GifFrameMetaData frameMetaData = image.Frames.RootFrame.MetaData.GetFormatMetaData(GifFormat.Instance);
+ GifColorTableMode colorMode = metaData.ColorTableMode;
+ var encoder = new GifEncoder()
+ {
+ ColorTableMode = colorMode,
+ Quantizer = new OctreeQuantizer(frameMetaData.ColorTableLength)
+ };
+
+ image.Save(outStream, encoder);
+ outStream.Position = 0;
+
+ outStream.Position = 0;
+ var clone = Image.Load(outStream);
+
+ GifMetaData cloneMetaData = clone.MetaData.GetFormatMetaData(GifFormat.Instance);
+ Assert.Equal(metaData.ColorTableMode, cloneMetaData.ColorTableMode);
+
+ // Gifiddle and Cyotek GifInfo say this image has 64 colors.
+ Assert.Equal(64, frameMetaData.ColorTableLength);
+
+ for (int i = 0; i < image.Frames.Count; i++)
+ {
+ GifFrameMetaData ifm = image.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance);
+ GifFrameMetaData cifm = clone.Frames[i].MetaData.GetFormatMetaData(GifFormat.Instance);
+
+ Assert.Equal(ifm.ColorTableLength, cifm.ColorTableLength);
+ Assert.Equal(ifm.FrameDelay, cifm.FrameDelay);
+ }
+
+ image.Dispose();
+ clone.Dispose();
+ }
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
index 2790b1a57..dc0da5e2d 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifGraphicControlExtensionTests.cs
@@ -12,10 +12,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[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));
+ Assert.Equal(0, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.Unspecified, false, false));
+ Assert.Equal(11, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToBackground, true, true));
+ Assert.Equal(4, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.NotDispose, false, false));
+ Assert.Equal(14, GifGraphicControlExtension.GetPackedValue(GifDisposalMethod.RestoreToPrevious, true, false));
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs
index 4ef4c12d9..6a90c0c27 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Gif/Sections/GifImageDescriptorTests.cs
@@ -12,13 +12,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Gif
[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));
+ Assert.Equal(129, GifImageDescriptor.GetPackedValue(true, false, false, 1)); // localColorTable
+ Assert.Equal(65, GifImageDescriptor.GetPackedValue(false, true, false, 1)); // interfaceFlag
+ Assert.Equal(33, GifImageDescriptor.GetPackedValue(false, false, true, 1)); // sortFlag
+ Assert.Equal(225, GifImageDescriptor.GetPackedValue(true, true, true, 1)); // all
+ Assert.Equal(8, GifImageDescriptor.GetPackedValue(false, false, false, 8));
+ Assert.Equal(228, GifImageDescriptor.GetPackedValue(true, true, true, 4));
+ Assert.Equal(232, GifImageDescriptor.GetPackedValue(true, true, true, 8));
}
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
index f10a4ce84..464b1564b 100644
--- a/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
+++ b/tests/ImageSharp.Tests/Formats/ImageFormatManagerTests.cs
@@ -61,7 +61,7 @@ namespace SixLabors.ImageSharp.Tests
});
Assert.Throws(() =>
{
- this.DefaultFormatsManager.SetEncoder(ImageFormats.Bmp, null);
+ this.DefaultFormatsManager.SetEncoder(BmpFormat.Instance, null);
});
Assert.Throws(() =>
{
@@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Tests
});
Assert.Throws(() =>
{
- this.DefaultFormatsManager.SetDecoder(ImageFormats.Bmp, null);
+ this.DefaultFormatsManager.SetDecoder(BmpFormat.Instance, null);
});
Assert.Throws(() =>
{
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
index 2e67c06c1..4810985f1 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.MetaData.cs
@@ -49,6 +49,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
+ public static readonly TheoryData QualityFiles =
+ new TheoryData
+ {
+ { TestImages.Jpeg.Baseline.Calliphora, 80},
+ { TestImages.Jpeg.Progressive.Fb, 75 }
+ };
+
[Theory]
[MemberData(nameof(MetaDataTestData))]
public void MetaDataIsParsedCorrectly(
@@ -101,6 +108,36 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
+ [Theory]
+ [MemberData(nameof(QualityFiles))]
+ public void Identify_VerifyQuality(string imagePath, int quality)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new JpegDecoder();
+ IImageInfo image = decoder.Identify(Configuration.Default, stream);
+ JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance);
+ Assert.Equal(quality, meta.Quality);
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(QualityFiles))]
+ public void Decode_VerifyQuality(string imagePath, int quality)
+ {
+ var testFile = TestFile.Create(imagePath);
+ using (var stream = new MemoryStream(testFile.Bytes, false))
+ {
+ var decoder = new JpegDecoder();
+ using (Image image = decoder.Decode(Configuration.Default, stream))
+ {
+ JpegMetaData meta = image.MetaData.GetFormatMetaData(JpegFormat.Instance);
+ Assert.Equal(quality, meta.Quality);
+ }
+ }
+ }
+
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action test)
{
var testFile = TestFile.Create(imagePath);
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index a31ae37b7..598d99274 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
@@ -14,6 +14,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
public class JpegEncoderTests
{
+ public static readonly TheoryData QualityFiles =
+ new TheoryData
+ {
+ { TestImages.Jpeg.Baseline.Calliphora, 80},
+ { TestImages.Jpeg.Progressive.Fb, 75 }
+ };
+
public static readonly TheoryData BitsPerPixel_Quality =
new TheoryData
{
@@ -34,6 +41,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ TestImages.Jpeg.Baseline.GammaDalaiLamaGray, 72, 72, PixelResolutionUnit.PixelsPerInch }
};
+ [Theory]
+ [MemberData(nameof(QualityFiles))]
+ public void Encode_PreserveQuality(string imagePath, int quality)
+ {
+ var options = new JpegEncoder();
+
+ var testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateImage())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+
+ memStream.Position = 0;
+ using (var output = Image.Load(memStream))
+ {
+ JpegMetaData meta = output.MetaData.GetFormatMetaData(JpegFormat.Instance);
+ Assert.Equal(quality, meta.Quality);
+ }
+ }
+ }
+ }
+
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
@@ -43,18 +73,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
public void EncodeBaseline_WorksWithDifferentSizes(TestImageProvider provider, JpegSubsample subsample, int quality)
- where TPixel : struct, IPixel
- {
- TestJpegEncoderCore(provider, subsample, quality);
- }
+ where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality);
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType(TestImageProvider provider, JpegSubsample subsample, int quality)
- where TPixel : struct, IPixel
- {
- TestJpegEncoderCore(provider, subsample, quality);
- }
+ where TPixel : struct, IPixel => TestJpegEncoderCore(provider, subsample, quality);
///
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
@@ -103,38 +127,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
}
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void IgnoreMetadata_ControlsIfExifProfileIsWritten(bool ignoreMetaData)
- {
- var encoder = new JpegEncoder()
- {
- IgnoreMetadata = ignoreMetaData
- };
-
- using (Image input = TestFile.Create(TestImages.Jpeg.Baseline.Floorplan).CreateImage())
- {
- using (var memStream = new MemoryStream())
- {
- input.Save(memStream, encoder);
-
- memStream.Position = 0;
- using (var output = Image.Load(memStream))
- {
- if (ignoreMetaData)
- {
- Assert.Null(output.MetaData.ExifProfile);
- }
- else
- {
- Assert.NotNull(output.MetaData.ExifProfile);
- }
- }
- }
- }
- }
-
[Fact]
public void Quality_0_And_1_Are_Identical()
{
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 62de45064..0508ac8c2 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -23,6 +23,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
// The images are an exact match. Maybe the submodule isn't updating?
private const float ToleranceThresholdForPaletteEncoder = 1.3F / 100;
+ public static readonly TheoryData PngBitDepthFiles =
+ new TheoryData
+ {
+ { TestImages.Png.Rgb48Bpp, PngBitDepth.Bit16 },
+ { TestImages.Png.Bpp1, PngBitDepth.Bit1 }
+ };
+
///
/// All types except Palette
///
@@ -221,7 +228,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
: image;
float paletteToleranceHack = 80f / paletteSize;
- paletteToleranceHack = paletteToleranceHack * paletteToleranceHack;
+ paletteToleranceHack *= paletteToleranceHack;
ImageComparer comparer = pngColorType == PngColorType.Palette
? ImageComparer.Tolerant(ToleranceThresholdForPaletteEncoder * paletteToleranceHack)
: ImageComparer.Exact;
@@ -290,5 +297,29 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
}
+
+ [Theory]
+ [MemberData(nameof(PngBitDepthFiles))]
+ public void Encode_PreserveBits(string imagePath, PngBitDepth pngBitDepth)
+ {
+ var options = new PngEncoder();
+
+ var testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateImage())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+
+ memStream.Position = 0;
+ using (var output = Image.Load(memStream))
+ {
+ PngMetaData meta = output.MetaData.GetFormatMetaData(PngFormat.Instance);
+
+ Assert.Equal(pngBitDepth, meta.BitDepth);
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs
index 507401398..0a0ca1efa 100644
--- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs
@@ -1,7 +1,6 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
-using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.MetaData;
using Xunit;
@@ -16,14 +15,22 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConstructorImageFrameMetaData()
{
- ImageFrameMetaData metaData = new ImageFrameMetaData();
- metaData.FrameDelay = 42;
- metaData.DisposalMethod = DisposalMethod.RestoreToBackground;
+ const int frameDelay = 42;
+ const int colorTableLength = 128;
+ const GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground;
- ImageFrameMetaData clone = new ImageFrameMetaData(metaData);
+ var metaData = new ImageFrameMetaData();
+ GifFrameMetaData gifFrameMetaData = metaData.GetFormatMetaData(GifFormat.Instance);
+ gifFrameMetaData.FrameDelay = frameDelay;
+ gifFrameMetaData.ColorTableLength = colorTableLength;
+ gifFrameMetaData.DisposalMethod = disposalMethod;
- Assert.Equal(42, clone.FrameDelay);
- Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod);
+ var clone = new ImageFrameMetaData(metaData);
+ GifFrameMetaData cloneGifFrameMetaData = clone.GetFormatMetaData(GifFormat.Instance);
+
+ Assert.Equal(frameDelay, cloneGifFrameMetaData.FrameDelay);
+ Assert.Equal(colorTableLength, cloneGifFrameMetaData.ColorTableLength);
+ Assert.Equal(disposalMethod, cloneGifFrameMetaData.DisposalMethod);
}
}
}
diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
index 8934ebc36..d681c90ba 100644
--- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
+++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs
@@ -28,7 +28,6 @@ namespace SixLabors.ImageSharp.Tests
metaData.HorizontalResolution = 4;
metaData.VerticalResolution = 2;
metaData.Properties.Add(imageProperty);
- metaData.RepeatCount = 1;
ImageMetaData clone = metaData.Clone();
@@ -36,7 +35,6 @@ namespace SixLabors.ImageSharp.Tests
Assert.Equal(4, clone.HorizontalResolution);
Assert.Equal(2, clone.VerticalResolution);
Assert.Equal(imageProperty, clone.Properties[0]);
- Assert.Equal(1, clone.RepeatCount);
}
[Fact]
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 0ad3edda8..acfad042e 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -178,6 +178,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Bit8Inverted = "Bmp/test8-inverted.bmp";
public const string Bit16 = "Bmp/test16.bmp";
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
+ public const string Bit32Rgb = "Bmp/rgb32.bmp";
public static readonly string[] All
= {
@@ -204,6 +205,7 @@ namespace SixLabors.ImageSharp.Tests
public const string Cheers = "Gif/cheers.gif";
public const string Trans = "Gif/trans.gif";
public const string Kumin = "Gif/kumin.gif";
+ public const string Leo = "Gif/leo.gif";
public const string Ratio4x1 = "Gif/base_4x1.gif";
public const string Ratio1x4 = "Gif/base_1x4.gif";
@@ -214,7 +216,7 @@ namespace SixLabors.ImageSharp.Tests
public const string BadDescriptorWidth = "Gif/issues/issue403_baddescriptorwidth.gif";
}
- public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Ratio4x1, Ratio1x4 };
+ public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
}
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
index 6475547a0..3ed696c47 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
@@ -55,9 +55,20 @@ namespace SixLabors.ImageSharp.Tests
public bool Equals(Key other)
{
- if (other is null) return false;
- if (ReferenceEquals(this, other)) return true;
- if (!this.commonValues.Equals(other.commonValues)) return false;
+ if (other is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, other))
+ {
+ return true;
+ }
+
+ if (!this.commonValues.Equals(other.commonValues))
+ {
+ return false;
+ }
if (this.decoderParameters.Count != other.decoderParameters.Count)
{
@@ -66,8 +77,7 @@ namespace SixLabors.ImageSharp.Tests
foreach (KeyValuePair kv in this.decoderParameters)
{
- object otherVal;
- if (!other.decoderParameters.TryGetValue(kv.Key, out otherVal))
+ if (!other.decoderParameters.TryGetValue(kv.Key, out object otherVal))
{
return false;
}
@@ -81,26 +91,29 @@ namespace SixLabors.ImageSharp.Tests
public override bool Equals(object obj)
{
- if (obj is null) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != this.GetType()) return false;
+ if (obj is null)
+ {
+ return false;
+ }
+
+ if (ReferenceEquals(this, obj))
+ {
+ return true;
+ }
+
+ if (obj.GetType() != this.GetType())
+ {
+ return false;
+ }
+
return this.Equals((Key)obj);
}
- public override int GetHashCode()
- {
- return this.commonValues.GetHashCode();
- }
+ public override int GetHashCode() => this.commonValues.GetHashCode();
- public static bool operator ==(Key left, Key right)
- {
- return Equals(left, right);
- }
+ public static bool operator ==(Key left, Key right) => Equals(left, right);
- public static bool operator !=(Key left, Key right)
- {
- return !Equals(left, right);
- }
+ public static bool operator !=(Key left, Key right) => !Equals(left, right);
}
private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>();
@@ -111,10 +124,7 @@ namespace SixLabors.ImageSharp.Tests
{
}
- public FileProvider(string filePath)
- {
- this.FilePath = filePath;
- }
+ public FileProvider(string filePath) => this.FilePath = filePath;
///
/// Gets the file path relative to the "~/tests/images" folder
@@ -135,12 +145,12 @@ namespace SixLabors.ImageSharp.Tests
if (!TestEnvironment.Is64BitProcess)
{
- return LoadImage(decoder);
+ return this.LoadImage(decoder);
}
var key = new Key(this.PixelType, this.FilePath, decoder);
- Image cachedImage = cache.GetOrAdd(key, fn => { return LoadImage(decoder); });
+ Image cachedImage = cache.GetOrAdd(key, _ => this.LoadImage(decoder));
return cachedImage.Clone();
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
index 90c999f7c..334b6552a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@@ -62,13 +62,13 @@ namespace SixLabors.ImageSharp.Tests
IImageEncoder bmpEncoder = IsWindows ? (IImageEncoder)SystemDrawingReferenceEncoder.Bmp : new BmpEncoder();
cfg.ConfigureCodecs(
- ImageFormats.Png,
+ PngFormat.Instance,
MagickReferenceDecoder.Instance,
pngEncoder,
new PngImageFormatDetector());
cfg.ConfigureCodecs(
- ImageFormats.Bmp,
+ BmpFormat.Instance,
SystemDrawingReferenceDecoder.Instance,
bmpEncoder,
new BmpImageFormatDetector());
diff --git a/tests/Images/Input/Bmp/rgb32.bmp b/tests/Images/Input/Bmp/rgb32.bmp
new file mode 100644
index 000000000..5d57eaaea
Binary files /dev/null and b/tests/Images/Input/Bmp/rgb32.bmp differ
diff --git a/tests/Images/Input/Gif/leo.gif b/tests/Images/Input/Gif/leo.gif
new file mode 100644
index 000000000..691842f42
Binary files /dev/null and b/tests/Images/Input/Gif/leo.gif differ