diff --git a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
index 8ccbe22acb..a8ee4d90bc 100644
--- a/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
@@ -1,121 +1,137 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Processing.Processors.Drawing;
-using SixLabors.Primitives;
-
-namespace SixLabors.ImageSharp.Processing
-{
- ///
- /// Adds extensions that allow the drawing of images to the type.
- ///
- public static class DrawImageExtensions
- {
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
-
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
- /// The blending mode.
- /// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
- /// The image this method extends.
- /// The image to blend with the currently processing image.
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Drawing;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// Adds extensions that allow the drawing of images to the type.
+ ///
+ public static class DrawImageExtensions
+ {
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The opacity of the image to blend. Must be between 0 and 1.
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
+ /// The blending mode.
+ /// The opacity of the image to blend. Must be between 0 and 1.
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
+
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
+ /// The image to blend with the currently processing image.
/// The color blending mode.
- /// The alpha composition mode.
- /// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity));
+ /// The alpha composition mode.
+ /// The opacity of the image to blend. Must be between 0 and 1.
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, colorBlending, alphaComposition, opacity));
- ///
- /// Draws the given image together with the current one by blending their pixels.
- ///
- /// The pixel format.
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
/// The image this method extends.
- /// The image to blend with the currently processing image.
+ /// The image to blend with the currently processing image.
/// The options, including the blending type and blending amount.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, GraphicsOptions options)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, GraphicsOptions options)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
- ///
- /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
///
- /// The pixel format.
- /// The image this method extends.
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
/// The image to blend with the currently processing image.
- /// The location to draw the blended image.
+ /// The location to draw the blended image.
/// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
- ///
- /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
///
- /// The pixel format.
- /// The image this method extends.
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
/// The image to blend with the currently processing image.
- /// The location to draw the blended image.
- /// The color blending to apply.
+ /// The location to draw the blended image.
+ /// The color blending to apply.
/// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
- ///
- /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
///
- /// The pixel format.
- /// The image this method extends.
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
+ /// The image this method extends.
/// The image to blend with the currently processing image.
- /// The location to draw the blended image.
+ /// The location to draw the blended image.
/// The color blending to apply.
- /// The alpha composition mode.
+ /// The alpha composition mode.
/// The opacity of the image to blend. Must be between 0 and 1.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity));
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, location, colorBlending, alphaComposition, opacity));
- ///
- /// Draws the given image together with the current one by blending their pixels.
+ ///
+ /// Draws the given image together with the current one by blending their pixels.
///
- /// The pixel format.
+ /// The pixel format of the destination image.
+ /// The pixel format of the source image.
/// The image this method extends.
/// The image to blend with the currently processing image.
/// The location to draw the blended image.
- /// The options containing the blend mode and opacity.
- /// The .
- public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options)
- where TPixel : struct, IPixel
- => source.ApplyProcessor(new DrawImageProcessor(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
- }
+ /// The options containing the blend mode and opacity.
+ /// The .
+ public static IImageProcessingContext DrawImage(this IImageProcessingContext source, Image image, Point location, GraphicsOptions options)
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
+ => source.ApplyProcessor(new DrawImageProcessor(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
+ }
}
\ No newline at end of file
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
index 324d25e097..4e6018e07a 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
@@ -15,32 +15,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
///
/// Combines two images together by blending the pixels.
///
- /// The pixel format.
- internal class DrawImageProcessor : ImageProcessor
- where TPixel : struct, IPixel
+ /// The pixel format of destination image.
+ /// The pixel format os source image.
+ internal class DrawImageProcessor : ImageProcessor
+ where TPixelDst : struct, IPixel
+ where TPixelSrc : struct, IPixel
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The image to blend with the currently processing image.
/// The location to draw the blended image.
/// The blending mode to use when drawing the image.
/// The Alpha blending mode to use when drawing the image.
/// The opacity of the image to blend. Must be between 0 and 1.
- public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
+ public DrawImageProcessor(Image image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.Image = image;
this.Opacity = opacity;
- this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
+ this.Blender = PixelOperations.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Location = location;
}
///
/// Gets the image to blend
///
- public Image Image { get; }
+ public Image Image { get; }
///
/// Gets the opacity of the image to blend
@@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
///
/// Gets the pixel blender
///
- public PixelBlender Blender { get; }
+ public PixelBlender Blender { get; }
///
/// Gets the location to draw the blended image
@@ -58,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public Point Location { get; }
///
- protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
{
- Image targetImage = this.Image;
- PixelBlender blender = this.Blender;
+ Image targetImage = this.Image;
+ PixelBlender blender = this.Blender;
int locationY = this.Location.Y;
// Align start/end positions.
@@ -76,23 +78,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX;
- MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
-
- using (IMemoryOwner amount = memoryAllocator.Allocate(width))
- {
- amount.GetSpan().Fill(this.Opacity);
-
- ParallelFor.WithConfiguration(
- minY,
- maxY,
- configuration,
- y =>
- {
- Span background = source.GetPixelRowSpan(y).Slice(minX, width);
- Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
- blender.Blend(memoryAllocator, background, background, foreground, amount.GetSpan());
- });
- }
+ MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
+
+ ParallelFor.WithConfiguration(
+ minY,
+ maxY,
+ configuration,
+ y =>
+ {
+ Span background = source.GetPixelRowSpan(y).Slice(minX, width);
+ Span foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
+ blender.Blend(memoryAllocator, background, background, foreground, this.Opacity);
+ });
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
index a44541bdb5..49c25462ea 100644
--- a/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
+++ b/src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.CieLchuv.cs
@@ -144,7 +144,7 @@ namespace SixLabors.ImageSharp.ColorSpaces.Conversion
///
/// The color to convert.
/// The
- public CieLchuv ToCieLchuv(Rgb color)
+ public CieLchuv ToCieLchuv(in Rgb color)
{
CieXyz xyzColor = this.ToCieXyz(color);
return this.ToCieLchuv(xyzColor);
diff --git a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
index d6dade7703..1b0f8ad095 100644
--- a/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
+++ b/src/ImageSharp/Common/Extensions/ComparableExtensions.cs
@@ -137,17 +137,6 @@ namespace SixLabors.ImageSharp
return value;
}
- ///
- /// Converts an to a first restricting the value between the
- /// minimum and maximum allowable ranges.
- ///
- /// The this method extends.
- /// The
- public static byte ToByte(this int value)
- {
- return (byte)value.Clamp(0, 255);
- }
-
///
/// Converts an to a first restricting the value between the
/// minimum and maximum allowable ranges.
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 3c48488ecc..c15e0a7329 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 0029a6b68d..618999c87d 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 956acc1578..57117cc075 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 d67beb0368..71852acddd 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 23b01ae9e8..b1a66accec 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 b49b8a8959..44e42528cf 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,37 +48,39 @@ 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);
+ ImageMetaData metaData = image.MetaData;
+ BmpMetaData bmpMetaData = 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;
int hResolution = 0;
int vResolution = 0;
- if (meta.ResolutionUnits != PixelResolutionUnit.AspectRatio)
+ if (metaData.ResolutionUnits != PixelResolutionUnit.AspectRatio)
{
- if (meta.HorizontalResolution > 0 && meta.VerticalResolution > 0)
+ if (metaData.HorizontalResolution > 0 && metaData.VerticalResolution > 0)
{
- switch (meta.ResolutionUnits)
+ switch (metaData.ResolutionUnits)
{
case PixelResolutionUnit.PixelsPerInch:
- hResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution));
- vResolution = (int)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution));
+ hResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.HorizontalResolution));
+ vResolution = (int)Math.Round(UnitConverter.InchToMeter(metaData.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerCentimeter:
- hResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution));
- vResolution = (int)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution));
+ hResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.HorizontalResolution));
+ vResolution = (int)Math.Round(UnitConverter.CmToMeter(metaData.VerticalResolution));
break;
case PixelResolutionUnit.PixelsPerMeter:
- hResolution = (int)Math.Round(meta.HorizontalResolution);
- vResolution = (int)Math.Round(meta.VerticalResolution);
+ hResolution = (int)Math.Round(metaData.HorizontalResolution);
+ vResolution = (int)Math.Round(metaData.VerticalResolution);
break;
}
@@ -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 64c6574c1e..a5eaab8ebf 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
+ public 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 bb884019b7..6a740d47d1 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 0000000000..3d678c13e1
--- /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 56952f0356..f62504d08f 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 57e4615bad..aa1c353db2 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 aa41928633..95b3335626 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 0bb62779eb..861d3e0368 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 0dbd39b992..8167d0d2e0 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 ac451a3550..42c76d6400 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 2a4d981ebb..207f126f9e 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 5d3e1b4d89..982340db66 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 e8e28ccdd8..4210b08765 100644
--- a/src/ImageSharp/Formats/Gif/GifEncoder.cs
+++ b/src/ImageSharp/Formats/Gif/GifEncoder.cs
@@ -14,11 +14,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
public sealed class GifEncoder : IImageEncoder, IGifEncoderOptions
{
- ///
- /// Gets or sets a value indicating whether the metadata should be ignored when the image is being encoded.
- ///
- public bool IgnoreMetadata { get; set; } = false;
-
///
/// Gets or sets the encoding that should be used when writing comments.
///
@@ -33,7 +28,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 5532900355..ae0366e6e6 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,17 +43,17 @@ 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.
+ /// The number of bits requires to store the color palette.
///
- private readonly bool ignoreMetadata;
+ private int bitDepth;
///
- /// The number of bits requires to store the color palette.
+ /// Gif specific meta data.
///
- private int bitDepth;
+ private GifMetaData gifMetaData;
///
/// Initializes a new instance of the class.
@@ -67,7 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
this.textEncoding = options.TextEncoding ?? GifConstants.DefaultEncoding;
this.quantizer = options.Quantizer;
this.colorTableMode = options.ColorTableMode;
- this.ignoreMetadata = options.IgnoreMetadata;
}
///
@@ -82,6 +80,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
+ ImageMetaData metaData = image.MetaData;
+ this.gifMetaData = 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,8 +97,7 @@ 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);
+ this.WriteLogicalScreenDescriptor(metaData, image.Width, image.Height, index, useGlobalTable, stream);
if (useGlobalTable)
{
@@ -103,12 +105,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
// Write the comments.
- this.WriteComments(image.MetaData, stream);
+ this.WriteComments(metaData, stream);
// 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 +138,9 @@ 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);
+ ImageFrameMetaData metaData = frame.MetaData;
+ GifFrameMetaData frameMetaData = metaData.GetFormatMetaData(GifFormat.Instance);
+ this.WriteGraphicalControlExtension(frameMetaData, transparencyIndex, stream);
this.WriteImageDescriptor(frame, false, stream);
if (i == 0)
@@ -146,7 +149,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 +161,37 @@ 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)
{
+ ImageFrameMetaData metaData = frame.MetaData;
+ GifFrameMetaData frameMetaData = 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 != frameMetaData.ColorTableLength
+ && frameMetaData.ColorTableLength > 0)
+ {
+ quantized = this.quantizer.CreateFrameQuantizer(frameMetaData.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(frameMetaData, 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 = frameMetaData;
}
}
@@ -210,21 +231,24 @@ 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.
///
- /// The pixel format.
- /// The image to encode.
+ /// The image metadata.
+ /// The image width.
+ /// The image height.
/// The transparency index to set the default background index to.
/// Whether to use a global or local color table.
/// The stream to write to.
- private void WriteLogicalScreenDescriptor(Image image, int transparencyIndex, bool useGlobalTable, Stream stream)
- where TPixel : struct, IPixel
+ private void WriteLogicalScreenDescriptor(
+ ImageMetaData metaData,
+ int width,
+ int height,
+ int transparencyIndex,
+ bool useGlobalTable,
+ Stream stream)
{
byte packedValue = GifLogicalScreenDescriptor.GetPackedValue(useGlobalTable, this.bitDepth - 1, false, this.bitDepth - 1);
@@ -237,13 +261,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
// 1..255 - Value used in the computation.
//
// Aspect Ratio = (Pixel Aspect Ratio + 15) / 64
- ImageMetaData meta = image.MetaData;
byte ratio = 0;
- if (meta.ResolutionUnits == PixelResolutionUnit.AspectRatio)
+ if (metaData.ResolutionUnits == PixelResolutionUnit.AspectRatio)
{
- double hr = meta.HorizontalResolution;
- double vr = meta.VerticalResolution;
+ double hr = metaData.HorizontalResolution;
+ double vr = metaData.VerticalResolution;
if (hr != vr)
{
if (hr > vr)
@@ -258,8 +281,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
var descriptor = new GifLogicalScreenDescriptor(
- width: (ushort)image.Width,
- height: (ushort)image.Height,
+ width: (ushort)width,
+ height: (ushort)height,
packed: packedValue,
backgroundColorIndex: unchecked((byte)transparencyIndex),
ratio);
@@ -279,25 +302,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);
}
}
@@ -308,11 +314,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// The stream to write to.
private void WriteComments(ImageMetaData metadata, Stream stream)
{
- if (this.ignoreMetadata)
- {
- return;
- }
-
if (!metadata.TryGetProperty(GifConstants.Comments, out ImageProperty property) || string.IsNullOrEmpty(property.Value))
{
return;
@@ -337,7 +338,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 +383,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 +408,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 6353690f47..07d4a54547 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
+ public 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 0000000000..cc04d48314
--- /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 bfbd334b01..b8f9a03f1a 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 0000000000..f58f5dff3e
--- /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 e99f09add3..42c202a3d9 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 bad6e0031b..4b3c28a92c 100644
--- a/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Gif/IGifEncoderOptions.cs
@@ -11,11 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
internal interface IGifEncoderOptions
{
- ///
- /// Gets a value indicating whether the metadata should be ignored when the image is being encoded.
- ///
- bool IgnoreMetadata { get; }
-
///
/// Gets the text encoding used to write comments.
///
@@ -29,6 +24,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 1c41285a97..8ddd4247e1 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 7ec5f20309..cb548d687d 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 c5360729e8..c3504dfe7b 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 0000000000..49a52edf6a
--- /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 15bdc73a84..94191c1493 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/Block8x8F.CopyTo.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
index bebc13f6de..b7dd125a88 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.CopyTo.cs
@@ -5,7 +5,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@@ -15,7 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
/// Copy block data into the destination color buffer pixel area with the provided horizontal and vertical.
///
- public void CopyTo(BufferArea area, int horizontalScale, int verticalScale)
+ public void CopyTo(in BufferArea area, int horizontalScale, int verticalScale)
{
if (horizontalScale == 1 && verticalScale == 1)
{
@@ -57,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void CopyTo(BufferArea area)
+ public void CopyTo(in BufferArea area)
{
ref byte selfBase = ref Unsafe.As(ref this);
ref byte destBase = ref Unsafe.As(ref area.GetReferenceToOrigin());
@@ -81,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.CopyBlock(ref d, ref s, 8 * sizeof(float));
}
- private void CopyTo2x2(BufferArea area)
+ private void CopyTo2x2(in BufferArea area)
{
ref float destBase = ref area.GetReferenceToOrigin();
int destStride = area.Stride;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
index bac77f905e..7a14d072e6 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmyk.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan cVals = values.Component0;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
index b07e57e170..5d7a31a12b 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScale.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan yVals = values.Component0;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
index 6b7e77e148..7cd97c4140 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgb.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan rVals = values.Component0;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
index 35700ea312..cb71889bc5 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
@@ -15,12 +15,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
ConvertCore(values, result);
}
- internal static void ConvertCore(ComponentValues values, Span result)
+ internal static void ConvertCore(in ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan yVals = values.Component0;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
index fd2f17da9e..4b2626c582 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimd.cs
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
///
/// SIMD convert using buffers of sizes divisable by 8.
///
- internal static void ConvertCore(ComponentValues values, Span result)
+ internal static void ConvertCore(in ComponentValues values, Span result)
{
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisable by 8!");
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
index 25342f4d67..ab4947e65c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrSimdAvx2.cs
@@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public static bool IsAvailable => Vector.IsHardwareAccelerated && SimdUtils.IsAvx2CompatibleArchitecture;
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
int remainder = result.Length % 8;
int simdCount = result.Length - remainder;
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
///
/// SIMD convert using buffers of sizes divisable by 8.
///
- internal static void ConvertCore(ComponentValues values, Span result)
+ internal static void ConvertCore(in ComponentValues values, Span result)
{
// This implementation is actually AVX specific.
// An AVX register is capable of storing 8 float-s.
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
index 83feefa94a..6f940f62f9 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccK.cs
@@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{
}
- public override void ConvertToRgba(ComponentValues values, Span result)
+ public override void ConvertToRgba(in ComponentValues values, Span result)
{
// TODO: We can optimize a lot here with Vector and SRCS.Unsafe()!
ReadOnlySpan yVals = values.Component0;
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
index 8aeb01d7f0..60abb7fb2c 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
@@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
///
/// The input as a stack-only struct
/// The destination buffer of values
- public abstract void ConvertToRgba(ComponentValues values, Span result);
+ public abstract void ConvertToRgba(in ComponentValues values, Span result);
///
/// Returns the for the YCbCr colorspace that matches the current CPU architecture.
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
index 900dd3bc89..0108e30815 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
@@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Runtime.InteropServices;
-
using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
@@ -43,6 +41,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
///
/// Initializes a new instance of the struct.
///
+ /// The raw jpeg data.
+ /// The raw component.
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{
int qtIndex = component.QuantizationTableIndex;
@@ -61,9 +61,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// - Level shift by +128, clip to [0, 255]
/// - Copy the resultin color values into 'destArea' scaling up the block by amount defined in
///
+ /// The source block.
+ /// The destination buffer area.
public void ProcessBlockColorsInto(
ref Block8x8 sourceBlock,
- BufferArea destArea)
+ in BufferArea destArea)
{
ref Block8x8F b = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock);
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 0000000000..4e11568c8d
--- /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 4076b7da82..53108de934 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 1d3be063dd..cb7fc19446 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 c3bf801ac8..9840a2ae88 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 7561afa1ef..011b6100dc 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,17 +259,20 @@ 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);
}
- while (fileMarker.Marker != JpegConstants.Markers.EOI)
+ // Break only when we discover a valid EOI marker.
+ // https://github.com/SixLabors/ImageSharp/issues/695
+ while (fileMarker.Marker != JpegConstants.Markers.EOI
+ || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
@@ -310,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:
@@ -463,13 +459,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else if (this.isExif)
{
- double horizontalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.XResolution, out ExifValue horizontalTag)
- ? ((Rational)horizontalTag.Value).ToDouble()
- : 0;
-
- double verticalValue = this.MetaData.ExifProfile.TryGetValue(ExifTag.YResolution, out ExifValue verticalTag)
- ? ((Rational)verticalTag.Value).ToDouble()
- : 0;
+ double horizontalValue = this.GetExifResolutionValue(ExifTag.XResolution);
+ double verticalValue = this.GetExifResolutionValue(ExifTag.YResolution);
if (horizontalValue > 0 && verticalValue > 0)
{
@@ -480,6 +471,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
+ private double GetExifResolutionValue(ExifTag tag)
+ {
+ if (!this.MetaData.ExifProfile.TryGetValue(tag, out ExifValue exifValue))
+ {
+ return 0;
+ }
+
+ switch (exifValue.DataType)
+ {
+ case ExifDataType.Rational:
+ return ((Rational)exifValue.Value).ToDouble();
+ case ExifDataType.Long:
+ return (uint)exifValue.Value;
+ case ExifDataType.DoubleFloat:
+ return (double)exifValue.Value;
+ default:
+ return 0;
+ }
+ }
+
///
/// Extends the profile with additional data.
///
@@ -689,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);
}
///
@@ -899,9 +912,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The values
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildHuffmanTable(HuffmanTables tables, int index, ReadOnlySpan codeLengths, ReadOnlySpan values)
- {
- tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
- }
+ => tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
///
/// Reads a from the stream advancing it by two bytes
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
index 0f389dee0f..d649d30418 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 f7b6fe9967..a4677ba2b7 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;
}
///
@@ -194,16 +186,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
this.outputStream = stream;
+ ImageMetaData metaData = image.MetaData;
+
+ // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1.
+ int qlty = (this.quality ?? 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.
@@ -214,10 +211,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int componentCount = 3;
// Write the Start Of Image marker.
- this.WriteApplicationHeader(image.MetaData);
+ this.WriteApplicationHeader(metaData);
// Write Exif and ICC profiles
- this.WriteProfiles(image);
+ this.WriteProfiles(metaData);
// Write the quantization tables.
this.WriteDefineQuantizationTables();
@@ -624,6 +621,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
private void WriteExifProfile(ExifProfile exifProfile)
{
+ if (exifProfile is null)
+ {
+ return;
+ }
+
const int MaxBytesApp1 = 65533;
const int MaxBytesWithExifId = 65527;
@@ -762,19 +764,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// Writes the metadata profiles to the image.
///
- /// The image.
- /// The pixel format.
- private void WriteProfiles(Image image)
- where TPixel : struct, IPixel
+ /// The image meta data.
+ private void WriteProfiles(ImageMetaData metaData)
{
- if (this.ignoreMetadata)
+ if (metaData is null)
{
return;
}
- image.MetaData.SyncProfiles();
- this.WriteExifProfile(image.MetaData.ExifProfile);
- this.WriteIccProfile(image.MetaData.IccProfile);
+ metaData.SyncProfiles();
+ this.WriteExifProfile(metaData.ExifProfile);
+ this.WriteIccProfile(metaData.IccProfile);
}
///
diff --git a/src/ImageSharp/Formats/Jpeg/JpegFormat.cs b/src/ImageSharp/Formats/Jpeg/JpegFormat.cs
index 9a18f14d30..6b23ceac7a 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
+ public 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 e25957efcf..7594f44770 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 0000000000..bd7232e602
--- /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 f3231fa22a..77bc9f7a05 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 a65845e02d..c73ec6f57e 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 0c22a4c913..396f2c1608 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 e0844ca6b8..7654c17014 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 64dad23bca..3c9fddbad4 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 aa96b926ca..9281927012 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();
+ PngMetaData 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();
+ PngMetaData 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 109e6ad770..f47a6518f0 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,30 +36,21 @@ 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.
///
public byte Threshold { get; set; } = 255;
- ///
- /// Gets or sets a value indicating whether this instance should write
- /// gamma information to the stream. The default value is false.
- ///
- public bool WriteGamma { get; set; }
-
///
/// Encodes the image to the specified stream from the .
///
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index ffe29aecae..e4d2fc510d 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,42 @@ namespace SixLabors.ImageSharp.Formats.Png
this.width = image.Width;
this.height = image.Height;
+ // Always take the encoder options over the metadata values.
+ ImageMetaData metaData = image.MetaData;
+ PngMetaData pngMetaData = 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 +233,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.
@@ -227,12 +243,12 @@ namespace SixLabors.ImageSharp.Formats.Png
// Collect the indexed pixel data
if (quantized != null)
{
- this.WritePaletteChunk(stream, header, quantized);
+ this.WritePaletteChunk(stream, quantized);
}
- this.WritePhysicalChunk(stream, image);
+ this.WritePhysicalChunk(stream, metaData);
this.WriteGammaChunk(stream);
- this.WriteExifChunk(stream, image);
+ this.WriteExifChunk(stream, metaData);
this.WriteDataChunks(image.Frames.RootFrame, quantizedPixelsSpan, stream);
this.WriteEndChunk(stream);
stream.Flush();
@@ -539,30 +555,27 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// The pixel format.
/// The containing image data.
- /// The .
/// The quantized frame.
- private void WritePaletteChunk(Stream stream, in PngHeader header, QuantizedFrame quantized)
+ private void WritePaletteChunk(Stream stream, QuantizedFrame quantized)
where TPixel : struct, IPixel
{
// Grab the palette and write it to the stream.
TPixel[] palette = quantized.Palette;
- byte pixelCount = palette.Length.ToByte();
-
- // Get max colors for bit depth.
- int colorTableLength = (int)Math.Pow(2, header.BitDepth) * 3;
+ int paletteLength = Math.Min(palette.Length, 256);
+ int colorTableLength = paletteLength * 3;
Rgba32 rgba = default;
bool anyAlpha = false;
using (IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength))
- using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(pixelCount))
+ using (IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength))
{
Span colorTableSpan = colorTable.GetSpan();
Span alphaTableSpan = alphaTable.GetSpan();
Span quantizedSpan = quantized.GetPixelSpan();
- for (byte i = 0; i < pixelCount; i++)
+ for (int i = 0; i < paletteLength; i++)
{
- if (quantizedSpan.IndexOf(i) > -1)
+ if (quantizedSpan.IndexOf((byte)i) > -1)
{
int offset = i * 3;
palette[i].ToRgba32(ref rgba);
@@ -588,7 +601,7 @@ namespace SixLabors.ImageSharp.Formats.Png
// Write the transparency data
if (anyAlpha)
{
- this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, pixelCount);
+ this.WriteChunk(stream, PngChunkType.PaletteAlpha, alphaTable.Array, 0, paletteLength);
}
}
}
@@ -596,11 +609,9 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Writes the physical dimension information to the stream.
///
- /// The pixel format.
/// The containing image data.
- /// The image.
- private void WritePhysicalChunk(Stream stream, Image image)
- where TPixel : struct, IPixel
+ /// The image meta data.
+ private void WritePhysicalChunk(Stream stream, ImageMetaData meta)
{
// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image. It contains:
// Pixels per unit, X axis: 4 bytes (unsigned integer)
@@ -612,7 +623,6 @@ namespace SixLabors.ImageSharp.Formats.Png
// 1: unit is the meter
//
// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
- ImageMetaData meta = image.MetaData;
Span hResolution = this.chunkDataBuffer.AsSpan(0, 4);
Span vResolution = this.chunkDataBuffer.AsSpan(4, 4);
@@ -653,16 +663,14 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Writes the eXIf chunk to the stream, if any EXIF Profile values are present in the meta data.
///
- /// The pixel format.
/// The containing image data.
- /// The image.
- private void WriteExifChunk(Stream stream, Image image)
- where TPixel : struct, IPixel
+ /// The image meta data.
+ private void WriteExifChunk(Stream stream, ImageMetaData meta)
{
- if (image.MetaData.ExifProfile?.Values.Count > 0)
+ if (meta.ExifProfile?.Values.Count > 0)
{
- image.MetaData.SyncProfiles();
- this.WriteChunk(stream, PngChunkType.Exif, image.MetaData.ExifProfile.ToByteArray());
+ meta.SyncProfiles();
+ this.WriteChunk(stream, PngChunkType.Exif, meta.ExifProfile.ToByteArray());
}
}
@@ -693,7 +701,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 +789,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 +797,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 +829,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 142f660712..210e2a837d 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
+ public 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 c1c039a1be..5deed86e30 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 0000000000..1eb3cdad6a
--- /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 bc437e5a5e..0000000000
--- 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/ImageFrame{TPixel}.cs b/src/ImageSharp/ImageFrame{TPixel}.cs
index a3971fe9ce..132ab598e5 100644
--- a/src/ImageSharp/ImageFrame{TPixel}.cs
+++ b/src/ImageSharp/ImageFrame{TPixel}.cs
@@ -84,12 +84,11 @@ namespace SixLabors.ImageSharp
Guard.NotNull(configuration, nameof(configuration));
Guard.MustBeGreaterThan(width, 0, nameof(width));
Guard.MustBeGreaterThan(height, 0, nameof(height));
- Guard.NotNull(metaData, nameof(metaData));
this.configuration = configuration;
this.MemoryAllocator = configuration.MemoryAllocator;
this.PixelBuffer = this.MemoryAllocator.Allocate2D(width, height);
- this.MetaData = metaData;
+ this.MetaData = metaData ?? new ImageFrameMetaData();
this.Clear(configuration.GetParallelOptions(), backgroundColor);
}
@@ -201,10 +200,7 @@ namespace SixLabors.ImageSharp
/// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image.
/// The at the specified position.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal ref TPixel GetPixelReference(int x, int y)
- {
- return ref this.PixelBuffer[x, y];
- }
+ internal ref TPixel GetPixelReference(int x, int y) => ref this.PixelBuffer[x, y];
///
/// Copies the pixels to a of the same size.
@@ -249,10 +245,7 @@ namespace SixLabors.ImageSharp
}
///
- public override string ToString()
- {
- return $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
- }
+ public override string ToString() => $"ImageFrame<{typeof(TPixel).Name}>: {this.Width}x{this.Height}";
///
/// Returns a copy of the image frame in the given pixel format.
@@ -309,15 +302,9 @@ namespace SixLabors.ImageSharp
/// Clones the current instance.
///
/// The
- internal ImageFrame Clone()
- {
- return new ImageFrame(this.configuration, this);
- }
+ internal ImageFrame Clone() => new ImageFrame(this.configuration, this);
///
- void IDisposable.Dispose()
- {
- this.Dispose();
- }
+ void IDisposable.Dispose() => this.Dispose();
}
}
\ 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 05791c92e5..2863fbf8f9 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 47a2fb775f..4b819e2013 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 40880bd085..7e74157e70 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/MetaData/Profiles/Exif/ExifReader.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
index 72db6305dd..549cb3fe09 100644
--- a/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifReader.cs
@@ -88,19 +88,19 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
}
uint ifdOffset = this.ReadUInt32();
- this.AddValues(values, (int)ifdOffset);
+ this.AddValues(values, ifdOffset);
uint thumbnailOffset = this.ReadUInt32();
- this.GetThumbnail((int)thumbnailOffset);
+ this.GetThumbnail(thumbnailOffset);
if (this.exifOffset != 0)
{
- this.AddValues(values, (int)this.exifOffset);
+ this.AddValues(values, this.exifOffset);
}
if (this.gpsOffset != 0)
{
- this.AddValues(values, (int)this.gpsOffset);
+ this.AddValues(values, this.gpsOffset);
}
return values;
@@ -153,9 +153,14 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
///
/// The values.
/// The index.
- private void AddValues(List values, int index)
+ private void AddValues(List values, uint index)
{
- this.position = index;
+ if (index > (uint)this.exifData.Length)
+ {
+ return;
+ }
+
+ this.position = (int)index;
int count = this.ReadUInt16();
for (int i = 0; i < count; i++)
@@ -431,7 +436,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return null;
}
- private void GetThumbnail(int offset)
+ private void GetThumbnail(uint offset)
{
var values = new List();
this.AddValues(values, offset);
@@ -515,10 +520,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Exif
return new Rational(numerator, denominator, false);
}
- private sbyte ConvertToSignedByte(ReadOnlySpan buffer)
- {
- return unchecked((sbyte)buffer[0]);
- }
+ private sbyte ConvertToSignedByte(ReadOnlySpan buffer) => unchecked((sbyte)buffer[0]);
private int ConvertToInt32(ReadOnlySpan buffer) // SignedLong in Exif Specification
{
diff --git a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
index 5d7d729b2c..79b9132192 100644
--- a/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
+++ b/src/ImageSharp/MetaData/Profiles/ICC/DataWriter/IccDataWriter.Matrix.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.MetaData.Profiles.Icc
/// The matrix to write
/// True if the values are encoded as Single; false if encoded as Fix16
/// The number of bytes written
- public int WriteMatrix(DenseMatrix value, bool isSingle)
+ public int WriteMatrix(in DenseMatrix value, bool isSingle)
{
int count = 0;
for (int y = 0; y < value.Rows; y++)
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
index 9b1e29db81..19d1c5dad1 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
@@ -1,4242 +1,3926 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-//
-using System;
-using System.Numerics;
-using System.Buffers;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
-{
- ///
- /// Collection of Porter Duff alpha blending functions applying different composition models.
- ///
- ///
- /// These functions are designed to be a general solution for all color cases,
- /// that is, they take in account the alpha value of both the backdrop
- /// and source, and there's no need to alpha-premultiply neither the backdrop
- /// nor the source.
- /// Note there are faster functions for when the backdrop color is known
- /// to be opaque
- ///
- internal static class DefaultPixelBlenders
- where TPixel : struct, IPixel
- {
-
- internal class NormalSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static NormalSrc Instance { get; } = new NormalSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.NormalSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.NormalSrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class MultiplySrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static MultiplySrc Instance { get; } = new MultiplySrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.MultiplySrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.MultiplySrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class AddSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static AddSrc Instance { get; } = new AddSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.AddSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.AddSrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class SubtractSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static SubtractSrc Instance { get; } = new SubtractSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.SubtractSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.SubtractSrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class ScreenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static ScreenSrc Instance { get; } = new ScreenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.ScreenSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.ScreenSrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class DarkenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static DarkenSrc Instance { get; } = new DarkenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.DarkenSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner buffer = memoryManager.Allocate(destination.Length * 3))
- {
- Span destinationSpan = buffer.Slice(0, destination.Length);
- Span backgroundSpan = buffer.Slice(destination.Length, destination.Length);
- Span sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
-
- PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length);
- PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length);
-
- for (int i = 0; i < destination.Length; i++)
- {
- destinationSpan[i] = PorterDuffFunctions.DarkenSrc(backgroundSpan[i], sourceSpan[i], amount[i]);
- }
-
- PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
- }
- }
- }
-
- internal class LightenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static LightenSrc Instance { get; } = new LightenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return PorterDuffFunctions.LightenSrc(background, source, amount);
- }
-
- ///
- public override void Blend(MemoryAllocator memoryManager, Span destination, Span background, Span source, Span amount)
- {
- Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
- Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
- Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
-
- using (IMemoryOwner