diff --git a/.gitattributes b/.gitattributes
index b9a9ddd4c3..195506770b 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -68,6 +68,7 @@
*.gif binary
*.jpg binary
*.png binary
+*.tga binary
*.ttf binary
*.snk binary
# diff as plain text
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 1dc081782a..01c1f10397 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -24,7 +24,7 @@
-
+
diff --git a/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs b/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs
new file mode 100644
index 0000000000..c32d0a46e7
--- /dev/null
+++ b/src/ImageSharp.Drawing/Common/Extensions/GraphicsOptionsExtensions.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Numerics;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Extensions methods fpor the class.
+ ///
+ internal static class GraphicsOptionsExtensions
+ {
+ ///
+ /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
+ ///
+ /// The graphics options.
+ /// The source color.
+ /// true if the color can be considered opaque
+ ///
+ /// Blending and composition is an expensive operation, in some cases, like
+ /// filling with a solid color, the blending can be avoided by a plain color replacement.
+ /// This method can be useful for such processors to select the fast path.
+ ///
+ public static bool IsOpaqueColorWithoutBlending(this GraphicsOptions options, Color color)
+ {
+ if (options.ColorBlendingMode != PixelColorBlendingMode.Normal)
+ {
+ return false;
+ }
+
+ if (options.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver
+ && options.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
+ {
+ return false;
+ }
+
+ const float Opaque = 1F;
+
+ if (options.BlendPercentage != Opaque)
+ {
+ return false;
+ }
+
+ if (((Vector4)color).W != Opaque)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs b/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
index f4a6458206..c008f4419e 100644
--- a/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
+++ b/src/ImageSharp.Drawing/Primitives/ShapeRegion.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Primitives
using (IMemoryOwner tempBuffer = configuration.MemoryAllocator.Allocate(buffer.Length))
{
- Span innerBuffer = tempBuffer.GetSpan();
+ Span innerBuffer = tempBuffer.Memory.Span;
int count = this.Shape.FindIntersections(start, end, innerBuffer);
for (int i = 0; i < count; i++)
@@ -61,4 +61,4 @@ namespace SixLabors.ImageSharp.Primitives
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
index 7e75d7effc..a9df07ced3 100644
--- a/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
+++ b/src/ImageSharp.Drawing/Processing/BrushApplicator.cs
@@ -1,68 +1,85 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
-
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Processing
{
///
- /// primitive that converts a point in to a color for discovering the fill color based on an implementation
+ /// A primitive that converts a point into a color for discovering the fill color based on an implementation.
///
/// The pixel format.
- ///
- public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush
+ ///
+ public abstract class BrushApplicator : IDisposable
where TPixel : struct, IPixel
{
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
/// The target.
- /// The options.
- internal BrushApplicator(ImageFrame target, GraphicsOptions options)
+ internal BrushApplicator(Configuration configuration, GraphicsOptions options, ImageFrame target)
{
+ this.Configuration = configuration;
this.Target = target;
this.Options = options;
this.Blender = PixelOperations.Instance.GetPixelBlender(options);
}
///
- /// Gets the blender
+ /// Gets the configuration instance to use when performing operations.
+ ///
+ protected Configuration Configuration { get; }
+
+ ///
+ /// Gets the pixel blender.
///
internal PixelBlender Blender { get; }
///
- /// Gets the destination
+ /// Gets the target image.
///
protected ImageFrame Target { get; }
///
- /// Gets the blend percentage
+ /// Gets thegraphics options
///
protected GraphicsOptions Options { get; }
///
- /// Gets the color for a single pixel.
+ /// Gets the overlay pixel at the specified position.
///
- /// The x coordinate.
- /// The y coordinate.
- /// The a that should be applied to the pixel.
+ /// The x-coordinate.
+ /// The y-coordinate.
+ /// The at the specified position.
internal abstract TPixel this[int x, int y] { get; }
///
- public abstract void Dispose();
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// Whether to dispose managed and unmanaged objects.
+ protected virtual void Dispose(bool disposing)
+ {
+ }
///
/// Applies the opacity weighting for each pixel in a scanline to the target based on the pattern contained in the brush.
///
- /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.
- /// The x position in the target pixel space that the start of the scanline data corresponds to.
- /// The y position in the target pixel space that whole scanline corresponds to.
+ /// A collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target.
+ /// The x-position in the target pixel space that the start of the scanline data corresponds to.
+ /// The y-position in the target pixel space that whole scanline corresponds to.
/// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs.
internal virtual void Apply(Span scanline, int x, int y)
{
@@ -71,8 +88,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
{
- Span amountSpan = amountBuffer.GetSpan();
- Span overlaySpan = overlay.GetSpan();
+ Span amountSpan = amountBuffer.Memory.Span;
+ Span overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@@ -89,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing
}
Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
- this.Blender.Blend(this.Target.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
+ this.Blender.Blend(this.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan);
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
index 91da332a16..fbab3605d2 100644
--- a/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -47,12 +47,14 @@ namespace SixLabors.ImageSharp.Processing
///
public override BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options) =>
+ RectangleF region) =>
new RadialGradientBrushApplicator(
- source,
+ configuration,
options,
+ source,
this.center,
this.referenceAxisEnd,
this.axisRatio,
@@ -86,24 +88,26 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
- /// The target image
- /// The options
- /// Center of the ellipse
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
+ /// The target image.
+ /// Center of the ellipse.
/// Point on one angular points of the ellipse.
///
/// Ratio of the axis length's. Used to determine the length of the second axis,
/// the first is defined by and .
- /// Definition of colors
+ /// Definition of colors.
/// Defines how the gradient colors are repeated.
public RadialGradientBrushApplicator(
- ImageFrame target,
+ Configuration configuration,
GraphicsOptions options,
+ ImageFrame target,
PointF center,
PointF referenceAxisEnd,
float axisRatio,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
- : base(target, options, colorStops, repetitionMode)
+ : base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
@@ -122,11 +126,6 @@ namespace SixLabors.ImageSharp.Processing
this.cosRotation = (float)Math.Cos(this.rotation);
}
- ///
- public override void Dispose()
- {
- }
-
///
protected override float PositionOnGradient(float xt, float yt)
{
@@ -139,16 +138,13 @@ namespace SixLabors.ImageSharp.Processing
float xSquared = x * x;
float ySquared = y * y;
- var inBoundaryChecker = (xSquared / this.referenceRadiusSquared)
- + (ySquared / this.secondRadiusSquared);
-
- return inBoundaryChecker;
+ return (xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared);
}
private float AngleBetween(PointF junction, PointF a, PointF b)
{
- var vA = a - junction;
- var vB = b - junction;
+ PointF vA = a - junction;
+ PointF vB = b - junction;
return MathF.Atan2(vB.Y, vB.X) - MathF.Atan2(vA.Y, vA.X);
}
@@ -156,6 +152,7 @@ namespace SixLabors.ImageSharp.Processing
PointF p1,
PointF p2)
{
+ // TODO: Can we not just use Vector2 distance here?
float dX = p1.X - p2.X;
float dXsquared = dX * dX;
@@ -165,4 +162,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs
index 981cf1bef4..6c79984378 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawImageExtensions.cs
@@ -22,14 +22,17 @@ namespace SixLabors.ImageSharp.Processing
public static IImageProcessingContext DrawImage(
this IImageProcessingContext source,
Image image,
- float opacity) =>
- source.ApplyProcessor(
+ float opacity)
+ {
+ var options = new GraphicsOptions();
+ return source.ApplyProcessor(
new DrawImageProcessor(
- image,
- Point.Empty,
- GraphicsOptions.Default.ColorBlendingMode,
- GraphicsOptions.Default.AlphaCompositionMode,
- opacity));
+ image,
+ Point.Empty,
+ options.ColorBlendingMode,
+ options.AlphaCompositionMode,
+ opacity));
+ }
///
/// Draws the given image together with the current one by blending their pixels.
@@ -49,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
image,
Point.Empty,
colorBlending,
- GraphicsOptions.Default.AlphaCompositionMode,
+ new GraphicsOptions().AlphaCompositionMode,
opacity));
///
@@ -100,14 +103,17 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Image image,
Point location,
- float opacity) =>
- source.ApplyProcessor(
+ float opacity)
+ {
+ var options = new GraphicsOptions();
+ return source.ApplyProcessor(
new DrawImageProcessor(
- image,
- location,
- GraphicsOptions.Default.ColorBlendingMode,
- GraphicsOptions.Default.AlphaCompositionMode,
- opacity));
+ image,
+ location,
+ options.ColorBlendingMode,
+ options.AlphaCompositionMode,
+ opacity));
+ }
///
/// Draws the given image together with the current one by blending their pixels.
@@ -129,7 +135,7 @@ namespace SixLabors.ImageSharp.Processing
image,
location,
colorBlending,
- GraphicsOptions.Default.AlphaCompositionMode,
+ new GraphicsOptions().AlphaCompositionMode,
opacity));
///
@@ -172,4 +178,4 @@ namespace SixLabors.ImageSharp.Processing
options.AlphaCompositionMode,
options.BlendPercentage));
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs
index a68b69a444..90b8c68ac2 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs
@@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing
/// The .
public static IImageProcessingContext
Draw(this IImageProcessingContext source, IPen pen, IPathCollection paths) =>
- source.Draw(GraphicsOptions.Default, pen, paths);
+ source.Draw(new GraphicsOptions(), pen, paths);
///
/// Draws the outline of the polygon with the provided brush at the provided thickness.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs
index dfe30f6a3c..822375ca97 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPathExtensions.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The path.
/// The .
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, IPath path) =>
- source.Draw(GraphicsOptions.Default, pen, path);
+ source.Draw(new GraphicsOptions(), pen, path);
///
/// Draws the outline of the polygon with the provided brush at the provided thickness.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs
index 86d8e9e2e2..d51e586452 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawPolygonExtensions.cs
@@ -86,7 +86,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IPen pen,
params PointF[] points) =>
- source.Draw(GraphicsOptions.Default, pen, new Polygon(new LinearLineSegment(points)));
+ source.Draw(new GraphicsOptions(), pen, new Polygon(new LinearLineSegment(points)));
///
/// Draws the provided Points as a closed Linear Polygon with the provided Pen.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs
index da78ab2ecc..b3b5dd76a5 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawRectangleExtensions.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The shape.
/// The .
public static IImageProcessingContext Draw(this IImageProcessingContext source, IPen pen, RectangleF shape) =>
- source.Draw(GraphicsOptions.Default, pen, shape);
+ source.Draw(new GraphicsOptions(), pen, shape);
///
/// Draws the outline of the rectangle with the provided brush at the provided thickness.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs
index 05cd3a1ae6..82dbb8d97e 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/DrawTextExtensions.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
Color color,
PointF location) =>
- source.DrawText(TextGraphicsOptions.Default, text, font, color, location);
+ source.DrawText(new TextGraphicsOptions(), text, font, color, location);
///
/// Draws the text onto the the image filled via the brush.
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
IBrush brush,
PointF location) =>
- source.DrawText(TextGraphicsOptions.Default, text, font, brush, location);
+ source.DrawText(new TextGraphicsOptions(), text, font, brush, location);
///
/// Draws the text onto the the image filled via the brush.
@@ -109,7 +109,7 @@ namespace SixLabors.ImageSharp.Processing
Font font,
IPen pen,
PointF location) =>
- source.DrawText(TextGraphicsOptions.Default, text, font, pen, location);
+ source.DrawText(new TextGraphicsOptions(), text, font, pen, location);
///
/// Draws the text onto the the image outlined via the pen.
@@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Processing
IBrush brush,
IPen pen,
PointF location) =>
- source.DrawText(TextGraphicsOptions.Default, text, font, brush, pen, location);
+ source.DrawText(new TextGraphicsOptions(), text, font, brush, pen, location);
///
/// Draws the text using the default resolution of 72dpi onto the the image filled via the brush then outlined via the pen.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs
index 5de9c6d4ed..030fe6ff1f 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathBuilderExtensions.cs
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IBrush brush,
Action path) =>
- source.Fill(GraphicsOptions.Default, brush, path);
+ source.Fill(new GraphicsOptions(), brush, path);
///
/// Flood fills the image in the shape of the provided polygon with the specified brush.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs
index 776e1f7e4e..5d8aaf3071 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
IBrush brush,
IPathCollection paths) =>
- source.Fill(GraphicsOptions.Default, brush, paths);
+ source.Fill(new GraphicsOptions(), brush, paths);
///
/// Flood fills the image in the shape of the provided polygon with the specified brush.
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs
index 718016a9e6..4d262aa5fb 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/FillPathExtensions.cs
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
/// The path.
/// The .
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, IPath path) =>
- source.Fill(GraphicsOptions.Default, brush, new ShapeRegion(path));
+ source.Fill(new GraphicsOptions(), brush, new ShapeRegion(path));
///
/// Flood fills the image in the shape of the provided polygon with the specified brush..
diff --git a/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs b/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs
index 294e575140..fbb6dbda56 100644
--- a/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs
+++ b/src/ImageSharp.Drawing/Processing/Extensions/FillRegionExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Processing
/// The details how to fill the region of interest.
/// The .
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush) =>
- source.Fill(GraphicsOptions.Default, brush);
+ source.Fill(new GraphicsOptions(), brush);
///
/// Flood fills the image with the specified color.
@@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Processing
/// The region.
/// The .
public static IImageProcessingContext Fill(this IImageProcessingContext source, IBrush brush, Region region) =>
- source.Fill(GraphicsOptions.Default, brush, region);
+ source.Fill(new GraphicsOptions(), brush, region);
///
/// Flood fills the image with in the region with the specified color.
@@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
IBrush brush,
Region region) =>
- source.ApplyProcessor(new FillRegionProcessor(brush, region, options));
+ source.ApplyProcessor(new FillRegionProcessor(options, brush, region));
///
/// Flood fills the image with the specified brush.
@@ -90,6 +90,6 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
GraphicsOptions options,
IBrush brush) =>
- source.ApplyProcessor(new FillProcessor(brush, options));
+ source.ApplyProcessor(new FillProcessor(options, brush));
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/GradientBrush.cs b/src/ImageSharp.Drawing/Processing/GradientBrush.cs
index 9826748c46..3be56c0424 100644
--- a/src/ImageSharp.Drawing/Processing/GradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/GradientBrush.cs
@@ -1,11 +1,9 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
-
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
@@ -38,9 +36,10 @@ namespace SixLabors.ImageSharp.Processing
///
public abstract BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel;
///
@@ -58,27 +57,24 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
- /// The target.
- /// The options.
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
+ /// The target image.
/// An array of color stops sorted by their position.
/// Defines if and how the gradient should be repeated.
protected GradientBrushApplicator(
- ImageFrame target,
+ Configuration configuration,
GraphicsOptions options,
+ ImageFrame target,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
- : base(target, options)
+ : base(configuration, options, target)
{
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
this.repetitionMode = repetitionMode;
}
- ///
- /// Base implementation of the indexer for gradients
- /// (follows the facade pattern, using abstract methods)
- ///
- /// X coordinate of the Pixel.
- /// Y coordinate of the Pixel.
+ ///
internal override TPixel this[int x, int y]
{
get
@@ -92,10 +88,10 @@ namespace SixLabors.ImageSharp.Processing
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
break;
case GradientRepetitionMode.Repeat:
- positionOnCompleteGradient = positionOnCompleteGradient % 1;
+ positionOnCompleteGradient %= 1;
break;
case GradientRepetitionMode.Reflect:
- positionOnCompleteGradient = positionOnCompleteGradient % 2;
+ positionOnCompleteGradient %= 2;
if (positionOnCompleteGradient > 1)
{
positionOnCompleteGradient = 2 - positionOnCompleteGradient;
@@ -121,19 +117,8 @@ namespace SixLabors.ImageSharp.Processing
}
else
{
- var fromAsVector = from.Color.ToVector4();
- var toAsVector = to.Color.ToVector4();
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
-
- // TODO: this should be changeble for different gradienting functions
- Vector4 result = PorterDuffFunctions.NormalSrcOver(
- fromAsVector,
- toAsVector,
- onLocalGradient);
-
- TPixel resultColor = default;
- resultColor.FromVector4(result);
- return resultColor;
+ return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel();
}
}
}
@@ -142,8 +127,8 @@ namespace SixLabors.ImageSharp.Processing
/// calculates the position on the gradient for a given point.
/// This method is abstract as it's content depends on the shape of the gradient.
///
- /// The x coordinate of the point
- /// The y coordinate of the point
+ /// The x-coordinate of the point.
+ /// The y-coordinate of the point.
///
/// The position the given point has on the gradient.
/// The position is not bound to the [0..1] interval.
@@ -176,4 +161,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/IBrush.cs b/src/ImageSharp.Drawing/Processing/IBrush.cs
index 0cd2e20fda..f4c7ef7cbb 100644
--- a/src/ImageSharp.Drawing/Processing/IBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/IBrush.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
@@ -19,20 +19,22 @@ namespace SixLabors.ImageSharp.Processing
/// Creates the applicator for this brush.
///
/// The pixel type.
+ /// The configuration instance to use when performing operations.
+ /// The graphic options.
/// The source image.
/// The region the brush will be applied to.
- /// The graphic options
///
- /// The brush applicator for this brush
+ /// The for this brush.
///
///
/// The when being applied to things like shapes would usually be the
- /// bounding box of the shape not necessarily the bounds of the whole image
+ /// bounding box of the shape not necessarily the bounds of the whole image.
///
BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/ImageBrush.cs b/src/ImageSharp.Drawing/Processing/ImageBrush.cs
index 8485ddfd09..e38614070f 100644
--- a/src/ImageSharp.Drawing/Processing/ImageBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/ImageBrush.cs
@@ -1,11 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
@@ -32,19 +31,20 @@ namespace SixLabors.ImageSharp.Processing
///
public BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel
{
if (this.image is Image specificImage)
{
- return new ImageBrushApplicator(source, specificImage, region, options, false);
+ return new ImageBrushApplicator(configuration, options, source, specificImage, region, false);
}
specificImage = this.image.CloneAs();
- return new ImageBrushApplicator(source, specificImage, region, options, true);
+ return new ImageBrushApplicator(configuration, options, source, specificImage, region, true);
}
///
@@ -79,21 +79,25 @@ namespace SixLabors.ImageSharp.Processing
///
private readonly int offsetX;
+ private bool isDisposed;
+
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
/// The target image.
/// The image.
/// The region.
- /// The options
/// Whether to dispose the image on disposal of the applicator.
public ImageBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame target,
Image image,
RectangleF region,
- GraphicsOptions options,
bool shouldDisposeImage)
- : base(target, options)
+ : base(configuration, options, target)
{
this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
@@ -104,14 +108,7 @@ namespace SixLabors.ImageSharp.Processing
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);
}
- ///
- /// Gets the color for a single pixel.
- ///
- /// The x.
- /// The y.
- ///
- /// The color
- ///
+ ///
internal override TPixel this[int x, int y]
{
get
@@ -123,14 +120,21 @@ namespace SixLabors.ImageSharp.Processing
}
///
- public override void Dispose()
+ protected override void Dispose(bool disposing)
{
- if (this.shouldDisposeImage)
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing && this.shouldDisposeImage)
{
this.sourceImage?.Dispose();
- this.sourceImage = null;
- this.sourceFrame = null;
}
+
+ this.sourceImage = null;
+ this.sourceFrame = null;
+ this.isDisposed = true;
}
///
@@ -140,8 +144,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner amountBuffer = this.Target.MemoryAllocator.Allocate(scanline.Length))
using (IMemoryOwner overlay = this.Target.MemoryAllocator.Allocate(scanline.Length))
{
- Span amountSpan = amountBuffer.GetSpan();
- Span overlaySpan = overlay.GetSpan();
+ Span amountSpan = amountBuffer.Memory.Span;
+ Span overlaySpan = overlay.Memory.Span;
int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
@@ -152,13 +156,12 @@ namespace SixLabors.ImageSharp.Processing
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int sourceX = (i + offsetX) % this.xLength;
- TPixel pixel = sourceRow[sourceX];
- overlaySpan[i] = pixel;
+ overlaySpan[i] = sourceRow[sourceX];
}
Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
- this.sourceFrame.Configuration,
+ this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@@ -167,4 +170,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
index bb99eeb26a..044bee72c5 100644
--- a/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/LinearGradientBrush.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -39,16 +39,18 @@ namespace SixLabors.ImageSharp.Processing
///
public override BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options) =>
+ RectangleF region) =>
new LinearGradientBrushApplicator(
+ configuration,
+ options,
source,
this.p1,
this.p2,
this.ColorStops,
- this.RepetitionMode,
- options);
+ this.RepetitionMode);
///
/// The linear gradient brush applicator.
@@ -93,20 +95,22 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
- /// The source
- /// start point of the gradient
- /// end point of the gradient
- /// tuple list of colors and their respective position between 0 and 1 on the line
- /// defines how the gradient colors are repeated.
- /// the graphics options
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
+ /// The source image.
+ /// The start point of the gradient.
+ /// The end point of the gradient.
+ /// A tuple list of colors and their respective position between 0 and 1 on the line.
+ /// Defines how the gradient colors are repeated.
public LinearGradientBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
PointF start,
PointF end,
ColorStop[] colorStops,
- GradientRepetitionMode repetitionMode,
- GraphicsOptions options)
- : base(source, options, colorStops, repetitionMode)
+ GradientRepetitionMode repetitionMode)
+ : base(configuration, options, source, colorStops, repetitionMode)
{
this.start = start;
this.end = end;
@@ -148,14 +152,9 @@ namespace SixLabors.ImageSharp.Processing
float distance = MathF.Sqrt(MathF.Pow(x4 - this.start.X, 2) + MathF.Pow(y4 - this.start.Y, 2));
// get and return ratio
- float ratio = distance / this.length;
- return ratio;
+ return distance / this.length;
}
}
-
- public override void Dispose()
- {
- }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
index 7315dc5a3e..9e354120e6 100644
--- a/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/PathGradientBrush.cs
@@ -83,12 +83,13 @@ namespace SixLabors.ImageSharp.Processing
///
public BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel
{
- return new PathGradientBrushApplicator(source, this.edges, this.centerColor, options);
+ return new PathGradientBrushApplicator(configuration, options, source, this.edges, this.centerColor);
}
private static Color CalculateCenterColor(Color[] colors)
@@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Processing
"One or more color is needed to construct a path gradient brush.");
}
- return new Color(colors.Select(c => c.ToVector4()).Aggregate((p1, p2) => p1 + p2) / colors.Length);
+ return new Color(colors.Select(c => (Vector4)c).Aggregate((p1, p2) => p1 + p2) / colors.Length);
}
private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length();
@@ -141,10 +142,10 @@ namespace SixLabors.ImageSharp.Processing
Vector2[] points = path.LineSegments.SelectMany(s => s.Flatten()).Select(p => (Vector2)p).ToArray();
this.Start = points.First();
- this.StartColor = startColor.ToVector4();
+ this.StartColor = (Vector4)startColor;
this.End = points.Last();
- this.EndColor = endColor.ToVector4();
+ this.EndColor = (Vector4)endColor;
this.length = DistanceBetween(this.End, this.Start);
this.buffer = new PointF[this.path.MaxIntersections];
@@ -199,23 +200,25 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
/// The source image.
/// Edges of the polygon.
/// Color at the center of the gradient area to which the other colors converge.
- /// The options.
public PathGradientBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
IList edges,
- Color centerColor,
- GraphicsOptions options)
- : base(source, options)
+ Color centerColor)
+ : base(configuration, options, source)
{
this.edges = edges;
PointF[] points = edges.Select(s => s.Start).ToArray();
this.center = points.Aggregate((p1, p2) => p1 + p2) / edges.Count;
- this.centerColor = centerColor.ToVector4();
+ this.centerColor = (Vector4)centerColor;
this.maxDistance = points.Select(p => (Vector2)(p - this.center)).Select(d => d.Length()).Max();
}
@@ -232,7 +235,7 @@ namespace SixLabors.ImageSharp.Processing
return new Color(this.centerColor).ToPixel();
}
- Vector2 direction = Vector2.Normalize(point - this.center);
+ var direction = Vector2.Normalize(point - this.center);
PointF end = point + (PointF)(direction * this.maxDistance);
@@ -250,7 +253,7 @@ namespace SixLabors.ImageSharp.Processing
float length = DistanceBetween(intersection, this.center);
float ratio = length > 0 ? DistanceBetween(intersection, point) / length : 0;
- Vector4 color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
+ var color = Vector4.Lerp(edgeColor, this.centerColor, ratio);
return new Color(color).ToPixel();
}
@@ -277,11 +280,6 @@ namespace SixLabors.ImageSharp.Processing
return closest;
}
-
- ///
- public override void Dispose()
- {
- }
}
}
}
diff --git a/src/ImageSharp.Drawing/Processing/PatternBrush.cs b/src/ImageSharp.Drawing/Processing/PatternBrush.cs
index 1999af8a39..726df5a797 100644
--- a/src/ImageSharp.Drawing/Processing/PatternBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/PatternBrush.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -61,8 +61,8 @@ namespace SixLabors.ImageSharp.Processing
/// The pattern.
internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix pattern)
{
- var foreColorVector = foreColor.ToVector4();
- var backColorVector = backColor.ToVector4();
+ var foreColorVector = (Vector4)foreColor;
+ var backColorVector = (Vector4)backColor;
this.pattern = new DenseMatrix(pattern.Columns, pattern.Rows);
this.patternVector = new DenseMatrix(pattern.Columns, pattern.Rows);
for (int i = 0; i < pattern.Data.Length; i++)
@@ -92,14 +92,16 @@ namespace SixLabors.ImageSharp.Processing
///
public BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel =>
new PatternBrushApplicator(
+ configuration,
+ options,
source,
- this.pattern.ToPixelMatrix(source.Configuration),
- options);
+ this.pattern.ToPixelMatrix(configuration));
///
/// The pattern brush applicator.
@@ -115,41 +117,33 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
/// The source image.
/// The pattern.
- /// The options
- public PatternBrushApplicator(ImageFrame source, in DenseMatrix pattern, GraphicsOptions options)
- : base(source, options)
+ public PatternBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
+ ImageFrame source,
+ in DenseMatrix pattern)
+ : base(configuration, options, source)
{
this.pattern = pattern;
}
- ///
- /// Gets the color for a single pixel.
- /// #
- /// The x.
- /// The y.
- ///
- /// The Color.
- ///
+ ///
internal override TPixel this[int x, int y]
{
get
{
- x = x % this.pattern.Columns;
- y = y % this.pattern.Rows;
+ x %= this.pattern.Columns;
+ y %= this.pattern.Rows;
// 2d array index at row/column
return this.pattern[y, x];
}
}
- ///
- public override void Dispose()
- {
- // noop
- }
-
///
internal override void Apply(Span scanline, int x, int y)
{
@@ -159,12 +153,12 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
{
- Span amountSpan = amountBuffer.GetSpan();
- Span overlaySpan = overlay.GetSpan();
+ Span amountSpan = amountBuffer.Memory.Span;
+ Span overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
- amountSpan[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1);
+ amountSpan[i] = NumberUtils.ClampFloat(scanline[i] * this.Options.BlendPercentage, 0, 1F);
int patternX = (x + i) % this.pattern.Columns;
overlaySpan[i] = this.pattern[patternY, patternX];
@@ -172,7 +166,7 @@ namespace SixLabors.ImageSharp.Processing
Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
- this.Target.Configuration,
+ this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@@ -181,4 +175,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
index 1d3cf35576..3963f99a5c 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor.cs
@@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
///
/// Initializes a new instance of the class.
///
- /// The brush to use for filling.
/// The defining how to blend the brush pixels over the image pixels.
- public FillProcessor(IBrush brush, GraphicsOptions options)
+ /// The brush to use for filling.
+ public FillProcessor(GraphicsOptions options, IBrush brush)
{
this.Brush = brush;
this.Options = options;
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
index 4e052818da..fc94826187 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillProcessor{TPixel}.cs
@@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX;
- Rectangle workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
+ var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY);
IBrush brush = this.definition.Brush;
GraphicsOptions options = this.definition.Options;
@@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
ParallelExecutionSettings parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration)
.MultiplyMinimumPixelsPerTask(4);
- var colorPixel = solidBrush.Color.ToPixel();
+ TPixel colorPixel = solidBrush.Color.ToPixel();
ParallelHelper.IterateRows(
workingRect,
@@ -84,11 +84,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
using (IMemoryOwner amount = source.MemoryAllocator.Allocate(width))
using (BrushApplicator applicator = brush.CreateApplicator(
+ configuration,
+ options,
source,
- sourceRectangle,
- options))
+ sourceRectangle))
{
- amount.GetSpan().Fill(1f);
+ amount.Memory.Span.Fill(1f);
ParallelHelper.IterateRows(
workingRect,
@@ -100,7 +101,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int offsetY = y - startY;
int offsetX = minX - startX;
- applicator.Apply(amount.GetSpan(), offsetX, offsetY);
+ applicator.Apply(amount.Memory.Span, offsetX, offsetY);
}
});
}
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
index 2318f3168b..7d51be1c51 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor.cs
@@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
///
/// Initializes a new instance of the class.
///
+ /// The graphics options.
/// The details how to fill the region of interest.
/// The region of interest to be filled.
- /// The configuration options.
- public FillRegionProcessor(IBrush brush, Region region, GraphicsOptions options)
+ public FillRegionProcessor(GraphicsOptions options, IBrush brush, Region region)
{
this.Region = region;
this.Brush = brush;
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
index 45d5015ae0..4744a4e920 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Drawing/FillRegionProcessor{TPixel}.cs
@@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
}
}
- using (BrushApplicator applicator = brush.CreateApplicator(source, rect, options))
+ using (BrushApplicator applicator = brush.CreateApplicator(configuration, options, source, rect))
{
int scanlineWidth = maxX - minX;
using (IMemoryOwner bBuffer = source.MemoryAllocator.Allocate(maxIntersections))
@@ -81,8 +81,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
- Span buffer = bBuffer.GetSpan();
- Span scanline = bScanline.GetSpan();
+ Span buffer = bBuffer.Memory.Span;
+ Span scanline = bScanline.Memory.Span;
bool isSolidBrushWithoutBlending = this.IsSolidBrushWithoutBlending(out SolidBrush solidBrush);
TPixel solidBrushColor = isSolidBrushWithoutBlending ? solidBrush.Color.ToPixel() : default;
diff --git a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
index ea042635dd..64d32efb80 100644
--- a/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
+++ b/src/ImageSharp.Drawing/Processing/Processors/Text/DrawTextProcessor{TPixel}.cs
@@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
VerticalAlignment = this.Options.VerticalAlignment
};
- this.textRenderer = new CachingGlyphRenderer(this.Source.GetMemoryAllocator(), this.Text.Length, this.Pen, this.Brush != null);
+ this.textRenderer = new CachingGlyphRenderer(this.Configuration.MemoryAllocator, this.Text.Length, this.Pen, this.Brush != null);
this.textRenderer.Options = (GraphicsOptions)this.Options;
var renderer = new TextRenderer(this.textRenderer);
renderer.RenderText(this.Text, style);
@@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
if (operations?.Count > 0)
{
- using (BrushApplicator app = brush.CreateApplicator(source, this.SourceRectangle, this.textRenderer.Options))
+ using (BrushApplicator app = brush.CreateApplicator(this.Configuration, this.textRenderer.Options, source, this.SourceRectangle))
{
foreach (DrawingOperation operation in operations)
{
@@ -326,6 +326,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
float subpixelFraction = 1f / subpixelCount;
float subpixelFractionPoint = subpixelFraction / subpixelCount;
+ Span intersectionSpan = rowIntersectionBuffer.Memory.Span;
+ Span buffer = bufferBacking.Memory.Span;
for (int y = 0; y <= size.Height; y++)
{
@@ -337,8 +339,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Text
{
var start = new PointF(path.Bounds.Left - 1, subPixel);
var end = new PointF(path.Bounds.Right + 1, subPixel);
- Span intersectionSpan = rowIntersectionBuffer.GetSpan();
- Span buffer = bufferBacking.GetSpan();
int pointsFound = path.FindIntersections(start, end, intersectionSpan);
if (pointsFound == 0)
diff --git a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
index f4d2dd81f4..2b1b6913f8 100644
--- a/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/RadialGradientBrush.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -9,7 +9,7 @@ using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
///
- /// A Circular Gradient Brush, defined by center point and radius.
+ /// A radial gradient brush, defined by center point and radius.
///
public sealed class RadialGradientBrush : GradientBrush
{
@@ -35,12 +35,14 @@ namespace SixLabors.ImageSharp.Processing
///
public override BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options) =>
+ RectangleF region) =>
new RadialGradientBrushApplicator(
- source,
+ configuration,
options,
+ source,
this.center,
this.radius,
this.ColorStops,
@@ -57,30 +59,27 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
- /// The target image
- /// The options.
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
+ /// The target image.
/// Center point of the gradient.
/// Radius of the gradient.
/// Definition of colors.
/// How the colors are repeated beyond the first gradient.
public RadialGradientBrushApplicator(
- ImageFrame target,
+ Configuration configuration,
GraphicsOptions options,
+ ImageFrame target,
PointF center,
float radius,
ColorStop[] colorStops,
GradientRepetitionMode repetitionMode)
- : base(target, options, colorStops, repetitionMode)
+ : base(configuration, options, target, colorStops, repetitionMode)
{
this.center = center;
this.radius = radius;
}
- ///
- public override void Dispose()
- {
- }
-
///
/// As this is a circular gradient, the position on the gradient is based on
/// the distance of the point to the center.
@@ -90,6 +89,7 @@ namespace SixLabors.ImageSharp.Processing
/// the position on the color gradient.
protected override float PositionOnGradient(float x, float y)
{
+ // TODO: Can this not use Vector2 distance?
float distance = MathF.Sqrt(MathF.Pow(this.center.X - x, 2) + MathF.Pow(this.center.Y - y, 2));
return distance / this.radius;
}
@@ -101,4 +101,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
index fca95be327..e0e43cf780 100644
--- a/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/RecolorBrush.cs
@@ -1,11 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -38,9 +37,6 @@ namespace SixLabors.ImageSharp.Processing
///
/// Gets the source color.
///
- ///
- /// The color of the source.
- ///
public Color SourceColor { get; }
///
@@ -50,17 +46,19 @@ namespace SixLabors.ImageSharp.Processing
///
public BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
ImageFrame source,
- RectangleF region,
- GraphicsOptions options)
+ RectangleF region)
where TPixel : struct, IPixel
{
return new RecolorBrushApplicator(
+ configuration,
+ options,
source,
this.SourceColor.ToPixel(),
this.TargetColor.ToPixel(),
- this.Threshold,
- options);
+ this.Threshold);
}
///
@@ -74,11 +72,6 @@ namespace SixLabors.ImageSharp.Processing
///
private readonly Vector4 sourceColor;
- ///
- /// The target color.
- ///
- private readonly Vector4 targetColor;
-
///
/// The threshold.
///
@@ -89,16 +82,22 @@ namespace SixLabors.ImageSharp.Processing
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The options
/// The source image.
/// Color of the source.
/// Color of the target.
/// The threshold .
- /// The options
- public RecolorBrushApplicator(ImageFrame source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options)
- : base(source, options)
+ public RecolorBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
+ ImageFrame source,
+ TPixel sourceColor,
+ TPixel targetColor,
+ float threshold)
+ : base(configuration, options, source)
{
this.sourceColor = sourceColor.ToVector4();
- this.targetColor = targetColor.ToVector4();
this.targetColorPixel = targetColor;
// Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
@@ -109,14 +108,7 @@ namespace SixLabors.ImageSharp.Processing
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
}
- ///
- /// Gets the color for a single pixel.
- ///
- /// The x.
- /// The y.
- ///
- /// The color
- ///
+ ///
internal override TPixel this[int x, int y]
{
get
@@ -138,11 +130,6 @@ namespace SixLabors.ImageSharp.Processing
}
}
- ///
- public override void Dispose()
- {
- }
-
///
internal override void Apply(Span scanline, int x, int y)
{
@@ -151,8 +138,8 @@ namespace SixLabors.ImageSharp.Processing
using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length))
{
- Span amountSpan = amountBuffer.GetSpan();
- Span overlaySpan = overlay.GetSpan();
+ Span amountSpan = amountBuffer.Memory.Span;
+ Span overlaySpan = overlay.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@@ -167,7 +154,7 @@ namespace SixLabors.ImageSharp.Processing
Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
- this.Target.Configuration,
+ this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
@@ -176,4 +163,4 @@ namespace SixLabors.ImageSharp.Processing
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/SolidBrush.cs b/src/ImageSharp.Drawing/Processing/SolidBrush.cs
index c62566f6b7..c297ede211 100644
--- a/src/ImageSharp.Drawing/Processing/SolidBrush.cs
+++ b/src/ImageSharp.Drawing/Processing/SolidBrush.cs
@@ -1,11 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.Advanced;
-using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;
@@ -17,33 +16,29 @@ namespace SixLabors.ImageSharp.Processing
///
public class SolidBrush : IBrush
{
- ///
- /// The color to paint.
- ///
- private readonly Color color;
-
///
/// Initializes a new instance of the class.
///
/// The color.
public SolidBrush(Color color)
{
- this.color = color;
+ this.Color = color;
}
///
/// Gets the color.
///
- ///
- /// The color.
- ///
- public Color Color => this.color;
+ public Color Color { get; }
///
- public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options)
+ public BrushApplicator CreateApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
+ ImageFrame source,
+ RectangleF region)
where TPixel : struct, IPixel
{
- return new SolidBrushApplicator(source, this.color.ToPixel(), options);
+ return new SolidBrushApplicator(configuration, options, source, this.Color.ToPixel());
}
///
@@ -52,38 +47,49 @@ namespace SixLabors.ImageSharp.Processing
private class SolidBrushApplicator : BrushApplicator
where TPixel : struct, IPixel
{
+ private bool isDisposed;
+
///
/// Initializes a new instance of the class.
///
+ /// The configuration instance to use when performing operations.
+ /// The graphics options.
/// The source image.
/// The color.
- /// The options
- public SolidBrushApplicator(ImageFrame source, TPixel color, GraphicsOptions options)
- : base(source, options)
+ public SolidBrushApplicator(
+ Configuration configuration,
+ GraphicsOptions options,
+ ImageFrame source,
+ TPixel color)
+ : base(configuration, options, source)
{
this.Colors = source.MemoryAllocator.Allocate(source.Width);
- this.Colors.GetSpan().Fill(color);
+ this.Colors.Memory.Span.Fill(color);
}
///
/// Gets the colors.
///
- protected IMemoryOwner Colors { get; }
+ protected IMemoryOwner Colors { get; private set; }
- ///
- /// Gets the color for a single pixel.
- ///
- /// The x.
- /// The y.
- ///
- /// The color
- ///
- internal override TPixel this[int x, int y] => this.Colors.GetSpan()[x];
+ ///
+ internal override TPixel this[int x, int y] => this.Colors.Memory.Span[x];
///
- public override void Dispose()
+ protected override void Dispose(bool disposing)
{
- this.Colors.Dispose();
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ this.Colors.Dispose();
+ }
+
+ this.Colors = null;
+ this.isDisposed = true;
}
///
@@ -102,17 +108,17 @@ namespace SixLabors.ImageSharp.Processing
}
MemoryAllocator memoryAllocator = this.Target.MemoryAllocator;
- Configuration configuration = this.Target.Configuration;
+ Configuration configuration = this.Configuration;
if (this.Options.BlendPercentage == 1f)
{
- this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.GetSpan(), scanline);
+ this.Blender.Blend(configuration, destinationRow, destinationRow, this.Colors.Memory.Span, scanline);
}
else
{
using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length))
{
- Span amountSpan = amountBuffer.GetSpan();
+ Span amountSpan = amountBuffer.Memory.Span;
for (int i = 0; i < scanline.Length; i++)
{
@@ -123,11 +129,11 @@ namespace SixLabors.ImageSharp.Processing
configuration,
destinationRow,
destinationRow,
- this.Colors.GetSpan(),
+ this.Colors.Memory.Span,
amountSpan);
}
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
index 6c140be72e..63730d1bf7 100644
--- a/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
+++ b/src/ImageSharp.Drawing/Processing/TextGraphicsOptions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Fonts;
@@ -9,120 +9,169 @@ namespace SixLabors.ImageSharp.Processing
///
/// Options for influencing the drawing functions.
///
- public struct TextGraphicsOptions
+ public class TextGraphicsOptions : IDeepCloneable
{
- private const int DefaultTextDpi = 72;
+ private int antialiasSubpixelDepth = 16;
+ private float blendPercentage = 1F;
+ private float tabWidth = 4F;
+ private float dpiX = 72F;
+ private float dpiY = 72F;
///
- /// Represents the default .
+ /// Initializes a new instance of the class.
///
- public static readonly TextGraphicsOptions Default = new TextGraphicsOptions(true);
-
- private float? blendPercentage;
-
- private int? antialiasSubpixelDepth;
-
- private bool? antialias;
-
- private bool? applyKerning;
-
- private float? tabWidth;
-
- private float? dpiX;
-
- private float? dpiY;
-
- private HorizontalAlignment? horizontalAlignment;
-
- private VerticalAlignment? verticalAlignment;
+ public TextGraphicsOptions()
+ {
+ }
- ///
- /// Initializes a new instance of the struct.
- ///
- /// If set to true [enable antialiasing].
- public TextGraphicsOptions(bool enableAntialiasing)
+ private TextGraphicsOptions(TextGraphicsOptions source)
{
- this.applyKerning = true;
- this.tabWidth = 4;
- this.WrapTextWidth = 0;
- this.horizontalAlignment = HorizontalAlignment.Left;
- this.verticalAlignment = VerticalAlignment.Top;
-
- this.antialiasSubpixelDepth = 16;
- this.ColorBlendingMode = PixelColorBlendingMode.Normal;
- this.AlphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
- this.blendPercentage = 1;
- this.antialias = enableAntialiasing;
- this.dpiX = DefaultTextDpi;
- this.dpiY = DefaultTextDpi;
+ this.AlphaCompositionMode = source.AlphaCompositionMode;
+ this.Antialias = source.Antialias;
+ this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
+ this.ApplyKerning = source.ApplyKerning;
+ this.BlendPercentage = source.BlendPercentage;
+ this.ColorBlendingMode = source.ColorBlendingMode;
+ this.DpiX = source.DpiX;
+ this.DpiY = source.DpiY;
+ this.HorizontalAlignment = source.HorizontalAlignment;
+ this.TabWidth = source.TabWidth;
+ this.WrapTextWidth = source.WrapTextWidth;
+ this.VerticalAlignment = source.VerticalAlignment;
}
///
/// Gets or sets a value indicating whether antialiasing should be applied.
+ /// Defaults to true.
///
- public bool Antialias { get => this.antialias ?? true; set => this.antialias = value; }
+ public bool Antialias { get; set; } = true;
///
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
///
- public int AntialiasSubpixelDepth { get => this.antialiasSubpixelDepth ?? 16; set => this.antialiasSubpixelDepth = value; }
+ public int AntialiasSubpixelDepth
+ {
+ get
+ {
+ return this.antialiasSubpixelDepth;
+ }
+
+ set
+ {
+ Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
+ this.antialiasSubpixelDepth = value;
+ }
+ }
///
- /// Gets or sets a value indicating the blending percentage to apply to the drawing operation
+ /// Gets or sets a value indicating the blending percentage to apply to the drawing operation.
///
- public float BlendPercentage { get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; }
+ public float BlendPercentage
+ {
+ get
+ {
+ return this.blendPercentage;
+ }
- // In the future we could expose a PixelBlender directly on here
- // or some forms of PixelBlender factory for each pixel type. Will need
- // some API thought post V1.
+ set
+ {
+ Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
+ this.blendPercentage = value;
+ }
+ }
///
- /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
+ /// Gets or sets a value indicating the color blending percentage to apply to the drawing operation.
+ /// Defaults to .
///
- public PixelColorBlendingMode ColorBlendingMode { get; set; }
+ public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
///
/// Gets or sets a value indicating the color blending percentage to apply to the drawing operation
+ /// Defaults to .
///
- public PixelAlphaCompositionMode AlphaCompositionMode { get; set; }
+ public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
///
/// Gets or sets a value indicating whether the text should be drawing with kerning enabled.
+ /// Defaults to true;
///
- public bool ApplyKerning { get => this.applyKerning ?? true; set => this.applyKerning = value; }
+ public bool ApplyKerning { get; set; } = true;
///
/// Gets or sets a value indicating the number of space widths a tab should lock to.
+ /// Defaults to 4.
///
- public float TabWidth { get => this.tabWidth ?? 4; set => this.tabWidth = value; }
+ public float TabWidth
+ {
+ get
+ {
+ return this.tabWidth;
+ }
+
+ set
+ {
+ Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.TabWidth));
+ this.tabWidth = value;
+ }
+ }
///
- /// Gets or sets a value indicating if greater than zero determine the width at which text should wrap.
+ /// Gets or sets a value, if greater than 0, indicating the width at which text should wrap.
+ /// Defaults to 0.
///
public float WrapTextWidth { get; set; }
///
- /// Gets or sets a value indicating the DPI to render text along the X axis.
+ /// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the X axis.
+ /// Defaults to 72.
///
- public float DpiX { get => this.dpiX ?? DefaultTextDpi; set => this.dpiX = value; }
+ public float DpiX
+ {
+ get
+ {
+ return this.dpiX;
+ }
+
+ set
+ {
+ Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiX));
+ this.dpiX = value;
+ }
+ }
///
- /// Gets or sets a value indicating the DPI to render text along the Y axis.
+ /// Gets or sets a value indicating the DPI (Dots Per Inch) to render text along the Y axis.
+ /// Defaults to 72.
///
- public float DpiY { get => this.dpiY ?? DefaultTextDpi; set => this.dpiY = value; }
+ public float DpiY
+ {
+ get
+ {
+ return this.dpiY;
+ }
+
+ set
+ {
+ Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.DpiY));
+ this.dpiY = value;
+ }
+ }
///
/// Gets or sets a value indicating how to align the text relative to the rendering space.
/// If is greater than zero it will align relative to the space
/// defined by the location and width, if equals zero, and thus
/// wrapping disabled, then the alignment is relative to the drawing location.
+ /// Defaults to .
///
- public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; }
+ public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Left;
///
/// Gets or sets a value indicating how to align the text relative to the rendering space.
+ /// Defaults to .
///
- public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; }
+ public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
///
/// Performs an implicit conversion from to .
@@ -133,8 +182,9 @@ namespace SixLabors.ImageSharp.Processing
///
public static implicit operator TextGraphicsOptions(GraphicsOptions options)
{
- return new TextGraphicsOptions(options.Antialias)
+ return new TextGraphicsOptions()
{
+ Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
blendPercentage = options.BlendPercentage,
ColorBlendingMode = options.ColorBlendingMode,
@@ -151,13 +201,17 @@ namespace SixLabors.ImageSharp.Processing
///
public static explicit operator GraphicsOptions(TextGraphicsOptions options)
{
- return new GraphicsOptions(options.Antialias)
+ return new GraphicsOptions()
{
+ Antialias = options.Antialias,
AntialiasSubpixelDepth = options.AntialiasSubpixelDepth,
ColorBlendingMode = options.ColorBlendingMode,
AlphaCompositionMode = options.AlphaCompositionMode,
BlendPercentage = options.BlendPercentage
};
}
+
+ ///
+ public TextGraphicsOptions DeepClone() => new TextGraphicsOptions(this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp.Drawing/Utils/NumberUtils.cs b/src/ImageSharp.Drawing/Utils/NumberUtils.cs
new file mode 100644
index 0000000000..d034c5d7ed
--- /dev/null
+++ b/src/ImageSharp.Drawing/Utils/NumberUtils.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Utility methods for numeric primitives.
+ ///
+ internal static class NumberUtils
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static float ClampFloat(float value, float min, float max)
+ {
+ if (value >= max)
+ {
+ return max;
+ }
+
+ if (value <= min)
+ {
+ return min;
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/ImageSharp.Drawing/Utils/QuickSort.cs b/src/ImageSharp.Drawing/Utils/QuickSort.cs
index ca1da5505a..14e3146a0b 100644
--- a/src/ImageSharp.Drawing/Utils/QuickSort.cs
+++ b/src/ImageSharp.Drawing/Utils/QuickSort.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
diff --git a/src/ImageSharp/Color/Color.cs b/src/ImageSharp/Color/Color.cs
index 76f3995171..5fad7a8e39 100644
--- a/src/ImageSharp/Color/Color.cs
+++ b/src/ImageSharp/Color/Color.cs
@@ -133,8 +133,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex();
///
- /// Converts the color instance to an
- /// implementation defined by .
+ /// Converts the color instance to a specified type.
///
/// The pixel type to convert to.
/// The pixel value.
@@ -147,6 +146,24 @@ namespace SixLabors.ImageSharp
return pixel;
}
+ ///
+ /// Bulk converts a span of to a span of a specified type.
+ ///
+ /// The pixel type to convert to.
+ /// The configuration.
+ /// The source color span.
+ /// The destination pixel span.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static void ToPixel(
+ Configuration configuration,
+ ReadOnlySpan source,
+ Span destination)
+ where TPixel : struct, IPixel
+ {
+ ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source);
+ PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination);
+ }
+
///
[MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other)
@@ -166,19 +183,5 @@ namespace SixLabors.ImageSharp
{
return this.data.PackedValue.GetHashCode();
}
-
- ///
- /// Bulk convert a span of to a span of a specified pixel type.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- internal static void ToPixel(
- Configuration configuration,
- ReadOnlySpan source,
- Span destination)
- where TPixel : struct, IPixel
- {
- ReadOnlySpan rgba64Span = MemoryMarshal.Cast(source);
- PixelOperations.Instance.FromRgba64(configuration, rgba64Span, destination);
- }
}
}
diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs
index 7460c9cac1..c51a54a40b 100644
--- a/src/ImageSharp/Common/Helpers/ImageMaths.cs
+++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs
@@ -1,7 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats;
@@ -14,6 +15,20 @@ namespace SixLabors.ImageSharp
///
internal static class ImageMaths
{
+ ///
+ /// Vector for converting pixel to gray value as specified by ITU-R Recommendation BT.709.
+ ///
+ private static readonly Vector4 Bt709 = new Vector4(.2126f, .7152f, .0722f, 0.0f);
+
+ ///
+ /// Convert a pixel value to grayscale using ITU-R Recommendation BT.709.
+ ///
+ /// The vector to get the luminance from.
+ /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int GetBT709Luminance(ref Vector4 vector, int luminanceLevels)
+ => (int)MathF.Round(Vector4.Dot(vector, Bt709) * (luminanceLevels - 1));
+
///
/// Gets the luminance from the rgb components using the formula as specified by ITU-R Recommendation BT.709.
///
diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs
index ae20490c77..4dba7a7e8e 100644
--- a/src/ImageSharp/Configuration.cs
+++ b/src/ImageSharp/Configuration.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Processing;
using SixLabors.Memory;
@@ -150,6 +151,7 @@ namespace SixLabors.ImageSharp
///
///
/// .
+ /// .
///
/// The default configuration of .
internal static Configuration CreateDefaultInstance()
@@ -158,7 +160,8 @@ namespace SixLabors.ImageSharp
new PngConfigurationModule(),
new JpegConfigurationModule(),
new GifConfigurationModule(),
- new BmpConfigurationModule());
+ new BmpConfigurationModule(),
+ new TgaConfigurationModule());
}
}
}
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index b7733e0269..03e082cce0 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -387,9 +387,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (rowHasUndefinedPixels)
{
// Slow path with undefined pixels.
+ int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
- int idx = (y * width * 3) + (x * 3);
+ int idx = rowStartIdx + (x * 3);
if (undefinedPixels[x, y])
{
switch (this.options.RleSkippedPixelHandling)
@@ -418,9 +419,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else
{
// Fast path without any undefined pixels.
+ int rowStartIdx = y * width * 3;
for (int x = 0; x < width; x++)
{
- int idx = (y * width * 3) + (x * 3);
+ int idx = rowStartIdx + (x * 3);
color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
pixelRow[x] = color;
}
diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs
index c75f9465af..1fee4a8371 100644
--- a/src/ImageSharp/Formats/Png/PngChunk.cs
+++ b/src/ImageSharp/Formats/Png/PngChunk.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.Memory;
@@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png
///
internal readonly struct PngChunk
{
- public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0)
+ public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
{
this.Length = length;
this.Type = type;
this.Data = data;
- this.Crc = crc;
}
///
@@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png
///
public IManagedByteBuffer Data { get; }
- ///
- /// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
- /// including the chunk type code and chunk data fields, but not including the length field.
- /// The CRC is always present, even for chunks containing no data
- ///
- public uint Crc { get; }
-
///
/// Gets a value indicating whether the given chunk is critical to decoding
///
public bool IsCritical =>
this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette ||
- this.Type == PngChunkType.Data ||
- this.Type == PngChunkType.End;
+ this.Type == PngChunkType.Data;
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 037f648f0a..b24a5eabda 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (image is null)
{
- throw new ImageFormatException("PNG Image does not contain a data chunk");
+ PngThrowHelper.ThrowNoData();
}
return image;
@@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.Width == 0 && this.header.Height == 0)
{
- throw new ImageFormatException("PNG Image does not contain a header chunk");
+ PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
@@ -407,7 +407,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha:
return this.header.BitDepth * 4;
default:
- throw new NotSupportedException("Unsupported PNG color type");
+ PngThrowHelper.ThrowNotSupportedColor();
+ return -1;
}
}
@@ -528,7 +529,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
- throw new ImageFormatException("Unknown filter type.");
+ PngThrowHelper.ThrowUnknownFilter();
+ break;
}
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
@@ -601,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
default:
- throw new ImageFormatException("Unknown filter type.");
+ PngThrowHelper.ThrowUnknownFilter();
+ break;
}
Span rowSpan = image.GetPixelRowSpan(this.currentRow);
@@ -1119,13 +1122,9 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk = new PngChunk(
length: length,
type: type,
- data: this.ReadChunkData(length),
- crc: this.ReadChunkCrc());
+ data: this.ReadChunkData(length));
- if (chunk.IsCritical)
- {
- this.ValidateChunk(chunk);
- }
+ this.ValidateChunk(chunk);
return true;
}
@@ -1136,6 +1135,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// The .
private void ValidateChunk(in PngChunk chunk)
{
+ if (!chunk.IsCritical)
+ {
+ return;
+ }
+
Span chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
@@ -1144,31 +1148,34 @@ namespace SixLabors.ImageSharp.Formats.Png
this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan());
- if (this.crc.Value != chunk.Crc)
+ uint crc = this.ReadChunkCrc();
+ if (this.crc.Value != crc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
-
- throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+ PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
}
}
///
/// Reads the cycle redundancy chunk from the data.
///
- ///
- /// Thrown if the input stream is not valid or corrupt.
- ///
+ [MethodImpl(InliningOptions.ShortMethod)]
private uint ReadChunkCrc()
{
- return this.currentStream.Read(this.buffer, 0, 4) == 4
- ? BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
- : throw new ImageFormatException("Image stream is not valid!");
+ uint crc = 0;
+ if (this.currentStream.Read(this.buffer, 0, 4) == 4)
+ {
+ crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
+ }
+
+ return crc;
}
///
/// Skips the chunk data and the cycle redundancy chunk read from the data.
///
/// The image format chunk.
+ [MethodImpl(InliningOptions.ShortMethod)]
private void SkipChunkDataAndCrc(in PngChunk chunk)
{
this.currentStream.Skip(chunk.Length);
@@ -1179,6 +1186,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Reads the chunk data from the stream.
///
/// The length of the chunk data to read.
+ [MethodImpl(InliningOptions.ShortMethod)]
private IManagedByteBuffer ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
@@ -1195,11 +1203,20 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Thrown if the input stream is not valid.
///
+ [MethodImpl(InliningOptions.ShortMethod)]
private PngChunkType ReadChunkType()
{
- return this.currentStream.Read(this.buffer, 0, 4) == 4
- ? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer)
- : throw new ImageFormatException("Invalid PNG data.");
+ if (this.currentStream.Read(this.buffer, 0, 4) == 4)
+ {
+ return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
+ }
+ else
+ {
+ PngThrowHelper.ThrowInvalidChunkType();
+
+ // The IDE cannot detect the throw here.
+ return default;
+ }
}
///
@@ -1208,6 +1225,7 @@ namespace SixLabors.ImageSharp.Formats.Png
///
/// Whether the length was read.
///
+ [MethodImpl(InliningOptions.ShortMethod)]
private bool TryReadChunkLength(out int result)
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 09575bb288..19c6af27f7 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -699,7 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
using (var memoryStream = new MemoryStream())
{
- using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
+ using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
deflateStream.Write(textBytes);
}
@@ -790,7 +790,7 @@ namespace SixLabors.ImageSharp.Formats.Png
using (var memoryStream = new MemoryStream())
{
- using (var deflateStream = new ZlibDeflateStream(memoryStream, this.options.CompressionLevel))
+ using (var deflateStream = new ZlibDeflateStream(this.memoryAllocator, memoryStream, this.options.CompressionLevel))
{
if (this.options.InterlaceMethod == PngInterlaceMode.Adam7)
{
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
new file mode 100644
index 0000000000..00b40c50b4
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Png
+{
+ ///
+ /// Cold path optimizations for throwing png format based exceptions.
+ ///
+ internal static class PngThrowHelper
+ {
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
index a06983b9ed..c4dc82a4dc 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/Adler32.cs
@@ -1,8 +1,9 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@@ -112,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update(ReadOnlySpan data)
{
- // (By Per Bothner)
+ ref byte dataRef = ref MemoryMarshal.GetReference(data);
uint s1 = this.checksum & 0xFFFF;
uint s2 = this.checksum >> 16;
@@ -133,8 +134,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
count -= n;
while (--n >= 0)
{
- s1 = s1 + (uint)(data[offset++] & 0xff);
- s2 = s2 + s1;
+ s1 += Unsafe.Add(ref dataRef, offset++);
+ s2 += s1;
}
s1 %= Base;
@@ -144,4 +145,4 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.checksum = (s2 << 16) | s1;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs
index d1588c384f..77355e908c 100644
--- a/src/ImageSharp/Formats/Png/Zlib/Crc32.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/Crc32.cs
@@ -1,8 +1,9 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@@ -141,9 +142,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.crc ^= CrcSeed;
+ ref uint crcTableRef = ref MemoryMarshal.GetReference(CrcTable.AsSpan());
for (int i = 0; i < data.Length; i++)
{
- this.crc = CrcTable[(this.crc ^ data[i]) & 0xFF] ^ (this.crc >> 8);
+ this.crc = Unsafe.Add(ref crcTableRef, (int)((this.crc ^ data[i]) & 0xFF)) ^ (this.crc >> 8);
}
this.crc ^= CrcSeed;
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs
new file mode 100644
index 0000000000..5f62b13c7f
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ internal static class DeflateThrowHelper
+ {
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNull(string name) => throw new ArgumentNullException(name);
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated.");
+
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input.");
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/Deflater.cs b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
new file mode 100644
index 0000000000..6c4ea44d1d
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/Deflater.cs
@@ -0,0 +1,295 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// This class compresses input with the deflate algorithm described in RFC 1951.
+ /// It has several compression levels and three different strategies described below.
+ ///
+ internal sealed class Deflater : IDisposable
+ {
+ ///
+ /// The best and slowest compression level. This tries to find very
+ /// long and distant string repetitions.
+ ///
+ public const int BestCompression = 9;
+
+ ///
+ /// The worst but fastest compression level.
+ ///
+ public const int BestSpeed = 1;
+
+ ///
+ /// The default compression level.
+ ///
+ public const int DefaultCompression = -1;
+
+ ///
+ /// This level won't compress at all but output uncompressed blocks.
+ ///
+ public const int NoCompression = 0;
+
+ ///
+ /// The compression method. This is the only method supported so far.
+ /// There is no need to use this constant at all.
+ ///
+ public const int Deflated = 8;
+
+ ///
+ /// Compression level.
+ ///
+ private int level;
+
+ ///
+ /// The current state.
+ ///
+ private int state;
+
+ private DeflaterEngine engine;
+ private bool isDisposed;
+
+ private const int IsFlushing = 0x04;
+ private const int IsFinishing = 0x08;
+ private const int BusyState = 0x10;
+ private const int FlushingState = 0x14;
+ private const int FinishingState = 0x1c;
+ private const int FinishedState = 0x1e;
+ private const int ClosedState = 0x7f;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ /// The compression level, a value between NoCompression and BestCompression.
+ ///
+ /// if level is out of range.
+ public Deflater(MemoryAllocator memoryAllocator, int level)
+ {
+ if (level == DefaultCompression)
+ {
+ level = 6;
+ }
+ else if (level < NoCompression || level > BestCompression)
+ {
+ throw new ArgumentOutOfRangeException(nameof(level));
+ }
+
+ // TODO: Possibly provide DeflateStrategy as an option.
+ this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default);
+
+ this.SetLevel(level);
+ this.Reset();
+ }
+
+ ///
+ /// Compression Level as an enum for safer use
+ ///
+ public enum CompressionLevel
+ {
+ ///
+ /// The best and slowest compression level. This tries to find very
+ /// long and distant string repetitions.
+ ///
+ BestCompression = Deflater.BestCompression,
+
+ ///
+ /// The worst but fastest compression level.
+ ///
+ BestSpeed = Deflater.BestSpeed,
+
+ ///
+ /// The default compression level.
+ ///
+ DefaultCompression = Deflater.DefaultCompression,
+
+ ///
+ /// This level won't compress at all but output uncompressed blocks.
+ ///
+ NoCompression = Deflater.NoCompression,
+
+ ///
+ /// The compression method. This is the only method supported so far.
+ /// There is no need to use this constant at all.
+ ///
+ Deflated = Deflater.Deflated
+ }
+
+ ///
+ /// Gets a value indicating whetherthe stream was finished and no more output bytes
+ /// are available.
+ ///
+ public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed;
+
+ ///
+ /// Gets a value indicating whether the input buffer is empty.
+ /// You should then call setInput().
+ /// NOTE: This method can also return true when the stream
+ /// was finished.
+ ///
+ public bool IsNeedingInput => this.engine.NeedsInput();
+
+ ///
+ /// Resets the deflater. The deflater acts afterwards as if it was
+ /// just created with the same compression level and strategy as it
+ /// had before.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset()
+ {
+ this.state = BusyState;
+ this.engine.Pending.Reset();
+ this.engine.Reset();
+ }
+
+ ///
+ /// Flushes the current input block. Further calls to Deflate() will
+ /// produce enough output to inflate everything in the current input
+ /// block. It is used by DeflaterOutputStream to implement Flush().
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Flush() => this.state |= IsFlushing;
+
+ ///
+ /// Finishes the deflater with the current input block. It is an error
+ /// to give more input after this method was called. This method must
+ /// be called to force all bytes to be flushed.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Finish() => this.state |= IsFlushing | IsFinishing;
+
+ ///
+ /// Sets the data which should be compressed next. This should be
+ /// only called when needsInput indicates that more input is needed.
+ /// The given byte array should not be changed, before needsInput() returns
+ /// true again.
+ ///
+ /// The buffer containing the input data.
+ /// The start of the data.
+ /// The number of data bytes of input.
+ ///
+ /// if the buffer was finished or if previous input is still pending.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void SetInput(byte[] input, int offset, int count)
+ {
+ if ((this.state & IsFinishing) != 0)
+ {
+ DeflateThrowHelper.ThrowAlreadyFinished();
+ }
+
+ this.engine.SetInput(input, offset, count);
+ }
+
+ ///
+ /// Sets the compression level. There is no guarantee of the exact
+ /// position of the change, but if you call this when needsInput is
+ /// true the change of compression level will occur somewhere near
+ /// before the end of the so far given input.
+ ///
+ ///
+ /// the new compression level.
+ ///
+ public void SetLevel(int level)
+ {
+ if (level == DefaultCompression)
+ {
+ level = 6;
+ }
+ else if (level < NoCompression || level > BestCompression)
+ {
+ throw new ArgumentOutOfRangeException(nameof(level));
+ }
+
+ if (this.level != level)
+ {
+ this.level = level;
+ this.engine.SetLevel(level);
+ }
+ }
+
+ ///
+ /// Deflates the current input block to the given array.
+ ///
+ /// Buffer to store the compressed data.
+ /// Offset into the output array.
+ /// The maximum number of bytes that may be stored.
+ ///
+ /// The number of compressed bytes added to the output, or 0 if either
+ /// or returns true or length is zero.
+ ///
+ public int Deflate(byte[] output, int offset, int length)
+ {
+ int origLength = length;
+
+ if (this.state == ClosedState)
+ {
+ DeflateThrowHelper.ThrowAlreadyClosed();
+ }
+
+ while (true)
+ {
+ int count = this.engine.Pending.Flush(output, offset, length);
+ offset += count;
+ length -= count;
+
+ if (length == 0 || this.state == FinishedState)
+ {
+ break;
+ }
+
+ if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0))
+ {
+ switch (this.state)
+ {
+ case BusyState:
+ // We need more input now
+ return origLength - length;
+
+ case FlushingState:
+ if (this.level != NoCompression)
+ {
+ // We have to supply some lookahead. 8 bit lookahead
+ // is needed by the zlib inflater, and we must fill
+ // the next byte, so that all bits are flushed.
+ int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7);
+ while (neededbits > 0)
+ {
+ // Write a static tree block consisting solely of an EOF:
+ this.engine.Pending.WriteBits(2, 10);
+ neededbits -= 10;
+ }
+ }
+
+ this.state = BusyState;
+ break;
+
+ case FinishingState:
+ this.engine.Pending.AlignToByte();
+ this.state = FinishedState;
+ break;
+ }
+ }
+ }
+
+ return origLength - length;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.engine.Dispose();
+ this.engine = null;
+ this.isDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
new file mode 100644
index 0000000000..67e8c6900b
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs
@@ -0,0 +1,151 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+//
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// This class contains constants used for deflation.
+ ///
+ public static class DeflaterConstants
+ {
+ ///
+ /// Set to true to enable debugging
+ ///
+ public const bool DEBUGGING = false;
+
+ ///
+ /// Written to Zip file to identify a stored block
+ ///
+ public const int STORED_BLOCK = 0;
+
+ ///
+ /// Identifies static tree in Zip file
+ ///
+ public const int STATIC_TREES = 1;
+
+ ///
+ /// Identifies dynamic tree in Zip file
+ ///
+ public const int DYN_TREES = 2;
+
+ ///
+ /// Header flag indicating a preset dictionary for deflation
+ ///
+ public const int PRESET_DICT = 0x20;
+
+ ///
+ /// Sets internal buffer sizes for Huffman encoding
+ ///
+ public const int DEFAULT_MEM_LEVEL = 8;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_MATCH = 258;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MIN_MATCH = 3;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_WBITS = 15;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int WSIZE = 1 << MAX_WBITS;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int WMASK = WSIZE - 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_SIZE = 1 << HASH_BITS;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_MASK = HASH_SIZE - 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5);
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_STORED = 0;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_FAST = 1;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public const int DEFLATE_SLOW = 2;
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] GOOD_LENGTH = { 0, 4, 4, 4, 4, 8, 8, 8, 32, 32 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] MAX_LAZY = { 0, 4, 5, 6, 4, 16, 16, 32, 128, 258 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] NICE_LENGTH = { 0, 8, 16, 32, 16, 32, 128, 128, 258, 258 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] MAX_CHAIN = { 0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096 };
+
+ ///
+ /// Internal compression engine constant
+ ///
+ public static int[] COMPR_FUNC = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 2 };
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
new file mode 100644
index 0000000000..0163eec0b7
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs
@@ -0,0 +1,859 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// Strategies for deflater
+ ///
+ public enum DeflateStrategy
+ {
+ ///
+ /// The default strategy
+ ///
+ Default = 0,
+
+ ///
+ /// This strategy will only allow longer string repetitions. It is
+ /// useful for random data with a small character set.
+ ///
+ Filtered = 1,
+
+ ///
+ /// This strategy will not look for string repetitions at all. It
+ /// only encodes with Huffman trees (which means, that more common
+ /// characters get a smaller encoding.
+ ///
+ HuffmanOnly = 2
+ }
+
+ // DEFLATE ALGORITHM:
+ //
+ // The uncompressed stream is inserted into the window array. When
+ // the window array is full the first half is thrown away and the
+ // second half is copied to the beginning.
+ //
+ // The head array is a hash table. Three characters build a hash value
+ // and they the value points to the corresponding index in window of
+ // the last string with this hash. The prev array implements a
+ // linked list of matches with the same hash: prev[index & WMASK] points
+ // to the previous index with the same hash.
+ //
+
+ ///
+ /// Low level compression engine for deflate algorithm which uses a 32K sliding window
+ /// with secondary compression from Huffman/Shannon-Fano codes.
+ ///
+ internal sealed unsafe class DeflaterEngine : IDisposable
+ {
+ private const int TooFar = 4096;
+
+ // Hash index of string to be inserted
+ private int insertHashIndex;
+
+ private int matchStart;
+
+ // Length of best match
+ private int matchLen;
+
+ // Set if previous match exists
+ private bool prevAvailable;
+
+ private int blockStart;
+
+ ///
+ /// Points to the current character in the window.
+ ///
+ private int strstart;
+
+ ///
+ /// lookahead is the number of characters starting at strstart in
+ /// window that are valid.
+ /// So window[strstart] until window[strstart+lookahead-1] are valid
+ /// characters.
+ ///
+ private int lookahead;
+
+ ///
+ /// The current compression function.
+ ///
+ private int compressionFunction;
+
+ ///
+ /// The input data for compression.
+ ///
+ private byte[] inputBuf;
+
+ ///
+ /// The offset into inputBuf, where input data starts.
+ ///
+ private int inputOff;
+
+ ///
+ /// The end offset of the input data.
+ ///
+ private int inputEnd;
+
+ private readonly DeflateStrategy strategy;
+ private DeflaterHuffman huffman;
+ private bool isDisposed;
+
+ ///
+ /// Hashtable, hashing three characters to an index for window, so
+ /// that window[index]..window[index+2] have this hash code.
+ /// Note that the array should really be unsigned short, so you need
+ /// to and the values with 0xFFFF.
+ ///
+ private IMemoryOwner headMemoryOwner;
+ private MemoryHandle headMemoryHandle;
+ private readonly Memory head;
+ private readonly short* pinnedHeadPointer;
+
+ ///
+ /// prev[index & WMASK] points to the previous index that has the
+ /// same hash code as the string starting at index. This way
+ /// entries with the same hash code are in a linked list.
+ /// Note that the array should really be unsigned short, so you need
+ /// to and the values with 0xFFFF.
+ ///
+ private IMemoryOwner prevMemoryOwner;
+ private MemoryHandle prevMemoryHandle;
+ private readonly Memory prev;
+ private readonly short* pinnedPrevPointer;
+
+ ///
+ /// This array contains the part of the uncompressed stream that
+ /// is of relevance. The current character is indexed by strstart.
+ ///
+ private IManagedByteBuffer windowMemoryOwner;
+ private MemoryHandle windowMemoryHandle;
+ private readonly byte[] window;
+ private readonly byte* pinnedWindowPointer;
+
+ private int maxChain;
+ private int maxLazy;
+ private int niceLength;
+ private int goodLength;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ /// The deflate strategy to use.
+ public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy)
+ {
+ this.huffman = new DeflaterHuffman(memoryAllocator);
+ this.Pending = this.huffman.Pending;
+ this.strategy = strategy;
+
+ // Create pinned pointers to the various buffers to allow indexing
+ // without bounds checks.
+ this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE);
+ this.window = this.windowMemoryOwner.Array;
+ this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin();
+ this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer;
+
+ this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE);
+ this.head = this.headMemoryOwner.Memory;
+ this.headMemoryHandle = this.headMemoryOwner.Memory.Pin();
+ this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer;
+
+ this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE);
+ this.prev = this.prevMemoryOwner.Memory;
+ this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin();
+ this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer;
+
+ // We start at index 1, to avoid an implementation deficiency, that
+ // we cannot build a repeat pattern at index 0.
+ this.blockStart = this.strstart = 1;
+ }
+
+ ///
+ /// Gets the pending buffer to use.
+ ///
+ public DeflaterPendingBuffer Pending { get; }
+
+ ///
+ /// Deflate drives actual compression of data
+ ///
+ /// True to flush input buffers
+ /// Finish deflation with the current input.
+ /// Returns true if progress has been made.
+ public bool Deflate(bool flush, bool finish)
+ {
+ bool progress = false;
+ do
+ {
+ this.FillWindow();
+ bool canFlush = flush && (this.inputOff == this.inputEnd);
+
+ switch (this.compressionFunction)
+ {
+ case DeflaterConstants.DEFLATE_STORED:
+ progress = this.DeflateStored(canFlush, finish);
+ break;
+
+ case DeflaterConstants.DEFLATE_FAST:
+ progress = this.DeflateFast(canFlush, finish);
+ break;
+
+ case DeflaterConstants.DEFLATE_SLOW:
+ progress = this.DeflateSlow(canFlush, finish);
+ break;
+
+ default:
+ DeflateThrowHelper.ThrowUnknownCompression();
+ break;
+ }
+ }
+ while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made
+ return progress;
+ }
+
+ ///
+ /// Sets input data to be deflated. Should only be called when
+ /// returns true
+ ///
+ /// The buffer containing input data.
+ /// The offset of the first byte of data.
+ /// The number of bytes of data to use as input.
+ public void SetInput(byte[] buffer, int offset, int count)
+ {
+ if (buffer is null)
+ {
+ DeflateThrowHelper.ThrowNull(nameof(buffer));
+ }
+
+ if (offset < 0)
+ {
+ DeflateThrowHelper.ThrowOutOfRange(nameof(offset));
+ }
+
+ if (count < 0)
+ {
+ DeflateThrowHelper.ThrowOutOfRange(nameof(count));
+ }
+
+ if (this.inputOff < this.inputEnd)
+ {
+ DeflateThrowHelper.ThrowNotProcessed();
+ }
+
+ int end = offset + count;
+
+ // We want to throw an ArgumentOutOfRangeException early.
+ // The check is very tricky: it also handles integer wrap around.
+ if ((offset > end) || (end > buffer.Length))
+ {
+ DeflateThrowHelper.ThrowOutOfRange(nameof(count));
+ }
+
+ this.inputBuf = buffer;
+ this.inputOff = offset;
+ this.inputEnd = end;
+ }
+
+ ///
+ /// Determines if more input is needed.
+ ///
+ /// Return true if input is needed via SetInput
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool NeedsInput() => this.inputEnd == this.inputOff;
+
+ ///
+ /// Reset internal state
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset()
+ {
+ this.huffman.Reset();
+ this.blockStart = this.strstart = 1;
+ this.lookahead = 0;
+ this.prevAvailable = false;
+ this.matchLen = DeflaterConstants.MIN_MATCH - 1;
+ this.head.Span.Slice(0, DeflaterConstants.HASH_SIZE).Clear();
+ this.prev.Span.Slice(0, DeflaterConstants.WSIZE).Clear();
+ }
+
+ ///
+ /// Set the deflate level (0-9)
+ ///
+ /// The value to set the level to.
+ public void SetLevel(int level)
+ {
+ if ((level < 0) || (level > 9))
+ {
+ DeflateThrowHelper.ThrowOutOfRange(nameof(level));
+ }
+
+ this.goodLength = DeflaterConstants.GOOD_LENGTH[level];
+ this.maxLazy = DeflaterConstants.MAX_LAZY[level];
+ this.niceLength = DeflaterConstants.NICE_LENGTH[level];
+ this.maxChain = DeflaterConstants.MAX_CHAIN[level];
+
+ if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction)
+ {
+ switch (this.compressionFunction)
+ {
+ case DeflaterConstants.DEFLATE_STORED:
+ if (this.strstart > this.blockStart)
+ {
+ this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
+ this.blockStart = this.strstart;
+ }
+
+ this.UpdateHash();
+ break;
+
+ case DeflaterConstants.DEFLATE_FAST:
+ if (this.strstart > this.blockStart)
+ {
+ this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
+ this.blockStart = this.strstart;
+ }
+
+ break;
+
+ case DeflaterConstants.DEFLATE_SLOW:
+ if (this.prevAvailable)
+ {
+ this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF);
+ }
+
+ if (this.strstart > this.blockStart)
+ {
+ this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
+ this.blockStart = this.strstart;
+ }
+
+ this.prevAvailable = false;
+ this.matchLen = DeflaterConstants.MIN_MATCH - 1;
+ break;
+ }
+
+ this.compressionFunction = DeflaterConstants.COMPR_FUNC[level];
+ }
+ }
+
+ ///
+ /// Fill the window
+ ///
+ public void FillWindow()
+ {
+ // If the window is almost full and there is insufficient lookahead,
+ // move the upper half to the lower one to make room in the upper half.
+ if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)
+ {
+ this.SlideWindow();
+ }
+
+ // If there is not enough lookahead, but still some input left, read in the input.
+ if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd)
+ {
+ int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart;
+
+ if (more > this.inputEnd - this.inputOff)
+ {
+ more = this.inputEnd - this.inputOff;
+ }
+
+ Array.Copy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
+
+ this.inputOff += more;
+ this.lookahead += more;
+ }
+
+ if (this.lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ this.UpdateHash();
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.huffman.Dispose();
+
+ this.windowMemoryHandle.Dispose();
+ this.windowMemoryOwner.Dispose();
+
+ this.headMemoryHandle.Dispose();
+ this.headMemoryOwner.Dispose();
+
+ this.prevMemoryHandle.Dispose();
+ this.prevMemoryOwner.Dispose();
+
+ this.windowMemoryOwner = null;
+ this.headMemoryOwner = null;
+ this.prevMemoryOwner = null;
+ this.huffman = null;
+
+ this.isDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private void UpdateHash()
+ {
+ byte* pinned = this.pinnedWindowPointer;
+ this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1];
+ }
+
+ ///
+ /// Inserts the current string in the head hash and returns the previous
+ /// value for this hash.
+ ///
+ /// The previous hash value
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private int InsertString()
+ {
+ short match;
+ int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK;
+
+ short* pinnedHead = this.pinnedHeadPointer;
+ this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash];
+ pinnedHead[hash] = unchecked((short)this.strstart);
+ this.insertHashIndex = hash;
+ return match & 0xFFFF;
+ }
+
+ private void SlideWindow()
+ {
+ Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE);
+ this.matchStart -= DeflaterConstants.WSIZE;
+ this.strstart -= DeflaterConstants.WSIZE;
+ this.blockStart -= DeflaterConstants.WSIZE;
+
+ // Slide the hash table (could be avoided with 32 bit values
+ // at the expense of memory usage).
+ short* pinnedHead = this.pinnedHeadPointer;
+ for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i)
+ {
+ int m = pinnedHead[i] & 0xFFFF;
+ pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
+ }
+
+ // Slide the prev table.
+ short* pinnedPrev = this.pinnedPrevPointer;
+ for (int i = 0; i < DeflaterConstants.WSIZE; i++)
+ {
+ int m = pinnedPrev[i] & 0xFFFF;
+ pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
+ }
+ }
+
+ ///
+ ///
+ /// Find the best (longest) string in the window matching the
+ /// string starting at strstart.
+ ///
+ ///
+ /// Preconditions:
+ ///
+ /// strstart + DeflaterConstants.MAX_MATCH <= window.length.
+ ///
+ ///
+ /// The current match.
+ /// True if a match greater than the minimum length is found
+ private bool FindLongestMatch(int curMatch)
+ {
+ int match;
+ int scan = this.strstart;
+
+ // scanMax is the highest position that we can look at
+ int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1;
+ int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0);
+
+ int chainLength = this.maxChain;
+ int niceLength = Math.Min(this.niceLength, this.lookahead);
+
+ int matchStrt = this.matchStart;
+ this.matchLen = Math.Max(this.matchLen, DeflaterConstants.MIN_MATCH - 1);
+ int matchLength = this.matchLen;
+
+ if (scan + matchLength > scanMax)
+ {
+ return false;
+ }
+
+ byte* pinnedWindow = this.pinnedWindowPointer;
+ int scanStart = this.strstart;
+ byte scanEnd1 = pinnedWindow[scan + matchLength - 1];
+ byte scanEnd = pinnedWindow[scan + matchLength];
+
+ // Do not waste too much time if we already have a good match:
+ if (matchLength >= this.goodLength)
+ {
+ chainLength >>= 2;
+ }
+
+ short* pinnedPrev = this.pinnedPrevPointer;
+ do
+ {
+ match = curMatch;
+ scan = scanStart;
+
+ if (pinnedWindow[match + matchLength] != scanEnd
+ || pinnedWindow[match + matchLength - 1] != scanEnd1
+ || pinnedWindow[match] != pinnedWindow[scan]
+ || pinnedWindow[++match] != pinnedWindow[++scan])
+ {
+ continue;
+ }
+
+ // scan is set to strstart+1 and the comparison passed, so
+ // scanMax - scan is the maximum number of bytes we can compare.
+ // below we compare 8 bytes at a time, so first we compare
+ // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8
+ // n & (8 - 1) == n % 8.
+ switch ((scanMax - scan) & 7)
+ {
+ case 1:
+ if (pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 2:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 3:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 4:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 5:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 6:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+
+ case 7:
+ if (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match])
+ {
+ break;
+ }
+
+ break;
+ }
+
+ if (pinnedWindow[scan] == pinnedWindow[match])
+ {
+ // We check for insufficient lookahead only every 8th comparison;
+ // the 256th check will be made at strstart + 258 unless lookahead is
+ // exhausted first.
+ do
+ {
+ if (scan == scanMax)
+ {
+ ++scan; // advance to first position not matched
+ ++match;
+
+ break;
+ }
+ }
+ while (pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]
+ && pinnedWindow[++scan] == pinnedWindow[++match]);
+ }
+
+ if (scan - scanStart > matchLength)
+ {
+ matchStrt = curMatch;
+ matchLength = scan - scanStart;
+
+ if (matchLength >= niceLength)
+ {
+ break;
+ }
+
+ scanEnd1 = pinnedWindow[scan - 1];
+ scanEnd = pinnedWindow[scan];
+ }
+ }
+ while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0);
+
+ this.matchStart = matchStrt;
+ this.matchLen = matchLength;
+ return matchLength >= DeflaterConstants.MIN_MATCH;
+ }
+
+ private bool DeflateStored(bool flush, bool finish)
+ {
+ if (!flush && (this.lookahead == 0))
+ {
+ return false;
+ }
+
+ this.strstart += this.lookahead;
+ this.lookahead = 0;
+
+ int storedLength = this.strstart - this.blockStart;
+
+ if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full
+ (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window
+ flush)
+ {
+ bool lastBlock = finish;
+ if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE)
+ {
+ storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
+ lastBlock = false;
+ }
+
+ this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock);
+ this.blockStart += storedLength;
+ return !(lastBlock || storedLength == 0);
+ }
+
+ return true;
+ }
+
+ private bool DeflateFast(bool flush, bool finish)
+ {
+ if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
+ {
+ return false;
+ }
+
+ while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
+ {
+ if (this.lookahead == 0)
+ {
+ // We are flushing everything
+ this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
+ this.blockStart = this.strstart;
+ return false;
+ }
+
+ if (this.strstart > (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
+ {
+ // slide window, as FindLongestMatch needs this.
+ // This should only happen when flushing and the window
+ // is almost full.
+ this.SlideWindow();
+ }
+
+ int hashHead;
+ if (this.lookahead >= DeflaterConstants.MIN_MATCH &&
+ (hashHead = this.InsertString()) != 0 &&
+ this.strategy != DeflateStrategy.HuffmanOnly &&
+ this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
+ this.FindLongestMatch(hashHead))
+ {
+ // longestMatch sets matchStart and matchLen
+ bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen);
+
+ this.lookahead -= this.matchLen;
+ if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ while (--this.matchLen > 0)
+ {
+ ++this.strstart;
+ this.InsertString();
+ }
+
+ ++this.strstart;
+ }
+ else
+ {
+ this.strstart += this.matchLen;
+ if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1)
+ {
+ this.UpdateHash();
+ }
+ }
+
+ this.matchLen = DeflaterConstants.MIN_MATCH - 1;
+ if (!full)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // No match found
+ this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff);
+ ++this.strstart;
+ --this.lookahead;
+ }
+
+ if (this.huffman.IsFull())
+ {
+ bool lastBlock = finish && (this.lookahead == 0);
+ this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock);
+ this.blockStart = this.strstart;
+ return !lastBlock;
+ }
+ }
+
+ return true;
+ }
+
+ private bool DeflateSlow(bool flush, bool finish)
+ {
+ if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
+ {
+ return false;
+ }
+
+ while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
+ {
+ if (this.lookahead == 0)
+ {
+ if (this.prevAvailable)
+ {
+ this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
+ }
+
+ this.prevAvailable = false;
+
+ // We are flushing everything
+ this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
+ this.blockStart = this.strstart;
+ return false;
+ }
+
+ if (this.strstart >= (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)
+ {
+ // slide window, as FindLongestMatch needs this.
+ // This should only happen when flushing and the window
+ // is almost full.
+ this.SlideWindow();
+ }
+
+ int prevMatch = this.matchStart;
+ int prevLen = this.matchLen;
+ if (this.lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ int hashHead = this.InsertString();
+
+ if (this.strategy != DeflateStrategy.HuffmanOnly &&
+ hashHead != 0 &&
+ this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
+ this.FindLongestMatch(hashHead))
+ {
+ // longestMatch sets matchStart and matchLen
+ // Discard match if too small and too far away
+ if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar)))
+ {
+ this.matchLen = DeflaterConstants.MIN_MATCH - 1;
+ }
+ }
+ }
+
+ // previous match was better
+ if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen))
+ {
+ this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen);
+ prevLen -= 2;
+ do
+ {
+ this.strstart++;
+ this.lookahead--;
+ if (this.lookahead >= DeflaterConstants.MIN_MATCH)
+ {
+ this.InsertString();
+ }
+ }
+ while (--prevLen > 0);
+
+ this.strstart++;
+ this.lookahead--;
+ this.prevAvailable = false;
+ this.matchLen = DeflaterConstants.MIN_MATCH - 1;
+ }
+ else
+ {
+ if (this.prevAvailable)
+ {
+ this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
+ }
+
+ this.prevAvailable = true;
+ this.strstart++;
+ this.lookahead--;
+ }
+
+ if (this.huffman.IsFull())
+ {
+ int len = this.strstart - this.blockStart;
+ if (this.prevAvailable)
+ {
+ len--;
+ }
+
+ bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable;
+ this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock);
+ this.blockStart += len;
+ return !lastBlock;
+ }
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
new file mode 100644
index 0000000000..96ff6b6576
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs
@@ -0,0 +1,949 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// Performs Deflate Huffman encoding.
+ ///
+ internal sealed unsafe class DeflaterHuffman : IDisposable
+ {
+ private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
+
+ // The number of literal codes.
+ private const int LiteralNumber = 286;
+
+ // Number of distance codes
+ private const int DistanceNumber = 30;
+
+ // Number of codes used to transfer bit lengths
+ private const int BitLengthNumber = 19;
+
+ // Repeat previous bit length 3-6 times (2 bits of repeat count)
+ private const int Repeat3To6 = 16;
+
+ // Repeat a zero length 3-10 times (3 bits of repeat count)
+ private const int Repeat3To10 = 17;
+
+ // Repeat a zero length 11-138 times (7 bits of repeat count)
+ private const int Repeat11To138 = 18;
+
+ private const int EofSymbol = 256;
+
+ // The lengths of the bit length codes are sent in order of decreasing
+ // probability, to avoid transmitting the lengths for unused bit length codes.
+ private static readonly int[] BitLengthOrder = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ private static readonly byte[] Bit4Reverse = { 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 };
+
+ private static readonly short[] StaticLCodes;
+ private static readonly byte[] StaticLLength;
+ private static readonly short[] StaticDCodes;
+ private static readonly byte[] StaticDLength;
+
+ private Tree literalTree;
+ private Tree distTree;
+ private Tree blTree;
+
+ // Buffer for distances
+ private readonly IMemoryOwner distanceManagedBuffer;
+ private readonly short* pinnedDistanceBuffer;
+ private MemoryHandle distanceBufferHandle;
+
+ private readonly IMemoryOwner literalManagedBuffer;
+ private readonly short* pinnedLiteralBuffer;
+ private MemoryHandle literalBufferHandle;
+
+ private int lastLiteral;
+ private int extraBits;
+ private bool isDisposed;
+
+ // TODO: These should be pre-generated array/readonlyspans.
+ static DeflaterHuffman()
+ {
+ // See RFC 1951 3.2.6
+ // Literal codes
+ StaticLCodes = new short[LiteralNumber];
+ StaticLLength = new byte[LiteralNumber];
+
+ int i = 0;
+ while (i < 144)
+ {
+ StaticLCodes[i] = BitReverse((0x030 + i) << 8);
+ StaticLLength[i++] = 8;
+ }
+
+ while (i < 256)
+ {
+ StaticLCodes[i] = BitReverse((0x190 - 144 + i) << 7);
+ StaticLLength[i++] = 9;
+ }
+
+ while (i < 280)
+ {
+ StaticLCodes[i] = BitReverse((0x000 - 256 + i) << 9);
+ StaticLLength[i++] = 7;
+ }
+
+ while (i < LiteralNumber)
+ {
+ StaticLCodes[i] = BitReverse((0x0c0 - 280 + i) << 8);
+ StaticLLength[i++] = 8;
+ }
+
+ // Distance codes
+ StaticDCodes = new short[DistanceNumber];
+ StaticDLength = new byte[DistanceNumber];
+ for (i = 0; i < DistanceNumber; i++)
+ {
+ StaticDCodes[i] = BitReverse(i << 11);
+ StaticDLength[i] = 5;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ public DeflaterHuffman(MemoryAllocator memoryAllocator)
+ {
+ this.Pending = new DeflaterPendingBuffer(memoryAllocator);
+
+ this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15);
+ this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
+ this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
+
+ this.distanceManagedBuffer = memoryAllocator.Allocate(BufferSize);
+ this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin();
+ this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer;
+
+ this.literalManagedBuffer = memoryAllocator.Allocate(BufferSize);
+ this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin();
+ this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
+ }
+
+ ///
+ /// Gets the pending buffer to use.
+ ///
+ public DeflaterPendingBuffer Pending { get; private set; }
+
+ ///
+ /// Reset internal state
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset()
+ {
+ this.lastLiteral = 0;
+ this.extraBits = 0;
+ this.literalTree.Reset();
+ this.distTree.Reset();
+ this.blTree.Reset();
+ }
+
+ ///
+ /// Write all trees to pending buffer
+ ///
+ /// The number/rank of treecodes to send.
+ public void SendAllTrees(int blTreeCodes)
+ {
+ this.blTree.BuildCodes();
+ this.literalTree.BuildCodes();
+ this.distTree.BuildCodes();
+ this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5);
+ this.Pending.WriteBits(this.distTree.NumCodes - 1, 5);
+ this.Pending.WriteBits(blTreeCodes - 4, 4);
+ for (int rank = 0; rank < blTreeCodes; rank++)
+ {
+ this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3);
+ }
+
+ this.literalTree.WriteTree(this.Pending, this.blTree);
+ this.distTree.WriteTree(this.Pending, this.blTree);
+ }
+
+ ///
+ /// Compress current buffer writing data to pending buffer
+ ///
+ public void CompressBlock()
+ {
+ DeflaterPendingBuffer pendingBuffer = this.Pending;
+ short* pinnedDistance = this.pinnedDistanceBuffer;
+ short* pinnedLiteral = this.pinnedLiteralBuffer;
+
+ for (int i = 0; i < this.lastLiteral; i++)
+ {
+ int litlen = pinnedLiteral[i] & 0xFF;
+ int dist = pinnedDistance[i];
+ if (dist-- != 0)
+ {
+ int lc = Lcode(litlen);
+ this.literalTree.WriteSymbol(pendingBuffer, lc);
+
+ int bits = (lc - 261) / 4;
+ if (bits > 0 && bits <= 5)
+ {
+ this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits);
+ }
+
+ int dc = Dcode(dist);
+ this.distTree.WriteSymbol(pendingBuffer, dc);
+
+ bits = (dc >> 1) - 1;
+ if (bits > 0)
+ {
+ this.Pending.WriteBits(dist & ((1 << bits) - 1), bits);
+ }
+ }
+ else
+ {
+ this.literalTree.WriteSymbol(pendingBuffer, litlen);
+ }
+ }
+
+ this.literalTree.WriteSymbol(pendingBuffer, EofSymbol);
+ }
+
+ ///
+ /// Flush block to output with no compression
+ ///
+ /// Data to write
+ /// Index of first byte to write
+ /// Count of bytes to write
+ /// True if this is the last block
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
+ {
+ this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
+ this.Pending.AlignToByte();
+ this.Pending.WriteShort(storedLength);
+ this.Pending.WriteShort(~storedLength);
+ this.Pending.WriteBlock(stored, storedOffset, storedLength);
+ this.Reset();
+ }
+
+ ///
+ /// Flush block to output with compression
+ ///
+ /// Data to flush
+ /// Index of first byte to flush
+ /// Count of bytes to flush
+ /// True if this is the last block
+ public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
+ {
+ this.literalTree.Frequencies[EofSymbol]++;
+
+ // Build trees
+ this.literalTree.BuildTree();
+ this.distTree.BuildTree();
+
+ // Calculate bitlen frequency
+ this.literalTree.CalcBLFreq(this.blTree);
+ this.distTree.CalcBLFreq(this.blTree);
+
+ // Build bitlen tree
+ this.blTree.BuildTree();
+
+ int blTreeCodes = 4;
+ for (int i = 18; i > blTreeCodes; i--)
+ {
+ if (this.blTree.Length[BitLengthOrder[i]] > 0)
+ {
+ blTreeCodes = i + 1;
+ }
+ }
+
+ int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength()
+ + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength()
+ + this.extraBits;
+
+ int static_len = this.extraBits;
+ ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength);
+ for (int i = 0; i < LiteralNumber; i++)
+ {
+ static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
+ }
+
+ ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength);
+ for (int i = 0; i < DistanceNumber; i++)
+ {
+ static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
+ }
+
+ if (opt_len >= static_len)
+ {
+ // Force static trees
+ opt_len = static_len;
+ }
+
+ if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3)
+ {
+ // Store Block
+ this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock);
+ }
+ else if (opt_len == static_len)
+ {
+ // Encode with static tree
+ this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3);
+ this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength);
+ this.distTree.SetStaticCodes(StaticDCodes, StaticDLength);
+ this.CompressBlock();
+ this.Reset();
+ }
+ else
+ {
+ // Encode with dynamic tree
+ this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3);
+ this.SendAllTrees(blTreeCodes);
+ this.CompressBlock();
+ this.Reset();
+ }
+ }
+
+ ///
+ /// Get value indicating if internal buffer is full
+ ///
+ /// true if buffer is full
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool IsFull() => this.lastLiteral >= BufferSize;
+
+ ///
+ /// Add literal to buffer
+ ///
+ /// Literal value to add to buffer.
+ /// Value indicating internal buffer is full
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool TallyLit(int literal)
+ {
+ this.pinnedDistanceBuffer[this.lastLiteral] = 0;
+ this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal;
+ this.literalTree.Frequencies[literal]++;
+ return this.IsFull();
+ }
+
+ ///
+ /// Add distance code and length to literal and distance trees
+ ///
+ /// Distance code
+ /// Length
+ /// Value indicating if internal buffer is full
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public bool TallyDist(int distance, int length)
+ {
+ this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance;
+ this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3);
+
+ int lc = Lcode(length - 3);
+ this.literalTree.Frequencies[lc]++;
+ if (lc >= 265 && lc < 285)
+ {
+ this.extraBits += (lc - 261) / 4;
+ }
+
+ int dc = Dcode(distance - 1);
+ this.distTree.Frequencies[dc]++;
+ if (dc >= 4)
+ {
+ this.extraBits += (dc >> 1) - 1;
+ }
+
+ return this.IsFull();
+ }
+
+ ///
+ /// Reverse the bits of a 16 bit value.
+ ///
+ /// Value to reverse bits
+ /// Value with bits reversed
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static short BitReverse(int toReverse)
+ {
+ return (short)(Bit4Reverse[toReverse & 0xF] << 12
+ | Bit4Reverse[(toReverse >> 4) & 0xF] << 8
+ | Bit4Reverse[(toReverse >> 8) & 0xF] << 4
+ | Bit4Reverse[toReverse >> 12]);
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.Pending.Dispose();
+ this.distanceBufferHandle.Dispose();
+ this.distanceManagedBuffer.Dispose();
+ this.literalBufferHandle.Dispose();
+ this.literalManagedBuffer.Dispose();
+
+ this.literalTree.Dispose();
+ this.blTree.Dispose();
+ this.distTree.Dispose();
+
+ this.Pending = null;
+ this.literalTree = null;
+ this.blTree = null;
+ this.distTree = null;
+ this.isDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int Lcode(int length)
+ {
+ if (length == 255)
+ {
+ return 285;
+ }
+
+ int code = 257;
+ while (length >= 8)
+ {
+ code += 4;
+ length >>= 1;
+ }
+
+ return code + length;
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ private static int Dcode(int distance)
+ {
+ int code = 0;
+ while (distance >= 4)
+ {
+ code += 2;
+ distance >>= 1;
+ }
+
+ return code + distance;
+ }
+
+ private sealed class Tree : IDisposable
+ {
+ private readonly int minNumCodes;
+ private readonly int[] bitLengthCounts;
+ private readonly int maxLength;
+ private bool isDisposed;
+
+ private readonly int elementCount;
+
+ private readonly MemoryAllocator memoryAllocator;
+
+ private IMemoryOwner codesMemoryOwner;
+ private MemoryHandle codesMemoryHandle;
+ private readonly short* codes;
+
+ private IMemoryOwner frequenciesMemoryOwner;
+ private MemoryHandle frequenciesMemoryHandle;
+
+ private IManagedByteBuffer lengthsMemoryOwner;
+ private MemoryHandle lengthsMemoryHandle;
+
+ public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
+ {
+ this.memoryAllocator = memoryAllocator;
+ this.elementCount = elements;
+ this.minNumCodes = minCodes;
+ this.maxLength = maxLength;
+
+ this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements);
+ this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
+ this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
+
+ this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements);
+ this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
+ this.Length = (byte*)this.lengthsMemoryHandle.Pointer;
+
+ this.codesMemoryOwner = memoryAllocator.Allocate(elements);
+ this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin();
+ this.codes = (short*)this.codesMemoryHandle.Pointer;
+
+ // Maxes out at 15.
+ this.bitLengthCounts = new int[maxLength];
+ }
+
+ public int NumCodes { get; private set; }
+
+ public short* Frequencies { get; }
+
+ public byte* Length { get; }
+
+ ///
+ /// Resets the internal state of the tree
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset()
+ {
+ this.frequenciesMemoryOwner.Memory.Span.Clear();
+ this.lengthsMemoryOwner.Memory.Span.Clear();
+ this.codesMemoryOwner.Memory.Span.Clear();
+ }
+
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code)
+ => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]);
+
+ ///
+ /// Set static codes and length
+ ///
+ /// new codes
+ /// length for new codes
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths)
+ {
+ staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span);
+ staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span);
+ }
+
+ ///
+ /// Build dynamic codes and lengths
+ ///
+ public void BuildCodes()
+ {
+ // Maxes out at 15 * 4
+ Span nextCode = stackalloc int[this.maxLength];
+ ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode);
+ ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts);
+
+ int code = 0;
+ for (int bits = 0; bits < this.maxLength; bits++)
+ {
+ Unsafe.Add(ref nextCodeRef, bits) = code;
+ code += Unsafe.Add(ref bitLengthCountsRef, bits) << (15 - bits);
+ }
+
+ for (int i = 0; i < this.NumCodes; i++)
+ {
+ int bits = this.Length[i];
+ if (bits > 0)
+ {
+ this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, bits - 1));
+ Unsafe.Add(ref nextCodeRef, bits - 1) += 1 << (16 - bits);
+ }
+ }
+ }
+
+ public void BuildTree()
+ {
+ int numSymbols = this.elementCount;
+
+ // heap is a priority queue, sorted by frequency, least frequent
+ // nodes first. The heap is a binary tree, with the property, that
+ // the parent node is smaller than both child nodes. This assures
+ // that the smallest node is the first parent.
+ //
+ // The binary tree is encoded in an array: 0 is root node and
+ // the nodes 2*n+1, 2*n+2 are the child nodes of node n.
+ // Maxes out at 286 * 4 so too large for the stack.
+ using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols))
+ {
+ ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span);
+
+ int heapLen = 0;
+ int maxCode = 0;
+ for (int n = 0; n < numSymbols; n++)
+ {
+ int freq = this.Frequencies[n];
+ if (freq != 0)
+ {
+ // Insert n into heap
+ int pos = heapLen++;
+ int ppos;
+ while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, ppos = (pos - 1) >> 1)] > freq)
+ {
+ Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, ppos);
+ pos = ppos;
+ }
+
+ Unsafe.Add(ref heapRef, pos) = n;
+
+ maxCode = n;
+ }
+ }
+
+ // We could encode a single literal with 0 bits but then we
+ // don't see the literals. Therefore we force at least two
+ // literals to avoid this case. We don't care about order in
+ // this case, both literals get a 1 bit code.
+ while (heapLen < 2)
+ {
+ Unsafe.Add(ref heapRef, heapLen++) = maxCode < 2 ? ++maxCode : 0;
+ }
+
+ this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes);
+
+ int numLeafs = heapLen;
+ int childrenLength = (4 * heapLen) - 2;
+ using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength))
+ using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1))
+ {
+ ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span);
+ ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span);
+ int numNodes = numLeafs;
+
+ for (int i = 0; i < heapLen; i++)
+ {
+ int node = Unsafe.Add(ref heapRef, i);
+ int i2 = 2 * i;
+ Unsafe.Add(ref childrenRef, i2) = node;
+ Unsafe.Add(ref childrenRef, i2 + 1) = -1;
+ Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8;
+ Unsafe.Add(ref heapRef, i) = i;
+ }
+
+ // Construct the Huffman tree by repeatedly combining the least two
+ // frequent nodes.
+ do
+ {
+ int first = Unsafe.Add(ref heapRef, 0);
+ int last = Unsafe.Add(ref heapRef, --heapLen);
+
+ // Propagate the hole to the leafs of the heap
+ int ppos = 0;
+ int path = 1;
+
+ while (path < heapLen)
+ {
+ if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1)))
+ {
+ path++;
+ }
+
+ Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
+ ppos = path;
+ path = (path * 2) + 1;
+ }
+
+ // Now propagate the last element down along path. Normally
+ // it shouldn't go too deep.
+ int lastVal = Unsafe.Add(ref valuesRef, last);
+ while ((path = ppos) > 0
+ && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal)
+ {
+ Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
+ }
+
+ Unsafe.Add(ref heapRef, path) = last;
+
+ int second = Unsafe.Add(ref heapRef, 0);
+
+ // Create a new node father of first and second
+ last = numNodes++;
+ Unsafe.Add(ref childrenRef, 2 * last) = first;
+ Unsafe.Add(ref childrenRef, (2 * last) + 1) = second;
+ int mindepth = Math.Min(Unsafe.Add(ref valuesRef, first) & 0xFF, Unsafe.Add(ref valuesRef, second) & 0xFF);
+ Unsafe.Add(ref valuesRef, last) = lastVal = Unsafe.Add(ref valuesRef, first) + Unsafe.Add(ref valuesRef, second) - mindepth + 1;
+
+ // Again, propagate the hole to the leafs
+ ppos = 0;
+ path = 1;
+
+ while (path < heapLen)
+ {
+ if (path + 1 < heapLen
+ && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path)) > Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, path + 1)))
+ {
+ path++;
+ }
+
+ Unsafe.Add(ref heapRef, ppos) = Unsafe.Add(ref heapRef, path);
+ ppos = path;
+ path = (ppos * 2) + 1;
+ }
+
+ // Now propagate the new element down along path
+ while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, Unsafe.Add(ref heapRef, ppos = (path - 1) >> 1)) > lastVal)
+ {
+ Unsafe.Add(ref heapRef, path) = Unsafe.Add(ref heapRef, ppos);
+ }
+
+ Unsafe.Add(ref heapRef, path) = last;
+ }
+ while (heapLen > 1);
+
+ if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1)
+ {
+ DeflateThrowHelper.ThrowHeapViolated();
+ }
+
+ this.BuildLength(childrenMemoryOwner.Memory.Span);
+ }
+ }
+ }
+
+ ///
+ /// Get encoded length
+ ///
+ /// Encoded length, the sum of frequencies * lengths
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public int GetEncodedLength()
+ {
+ int len = 0;
+ for (int i = 0; i < this.elementCount; i++)
+ {
+ len += this.Frequencies[i] * this.Length[i];
+ }
+
+ return len;
+ }
+
+ ///
+ /// Scan a literal or distance tree to determine the frequencies of the codes
+ /// in the bit length tree.
+ ///
+ public void CalcBLFreq(Tree blTree)
+ {
+ int maxCount; // max repeat count
+ int minCount; // min repeat count
+ int count; // repeat count of the current code
+ int curLen = -1; // length of current code
+
+ int i = 0;
+ while (i < this.NumCodes)
+ {
+ count = 1;
+ int nextlen = this.Length[i];
+ if (nextlen == 0)
+ {
+ maxCount = 138;
+ minCount = 3;
+ }
+ else
+ {
+ maxCount = 6;
+ minCount = 3;
+ if (curLen != nextlen)
+ {
+ blTree.Frequencies[nextlen]++;
+ count = 0;
+ }
+ }
+
+ curLen = nextlen;
+ i++;
+
+ while (i < this.NumCodes && curLen == this.Length[i])
+ {
+ i++;
+ if (++count >= maxCount)
+ {
+ break;
+ }
+ }
+
+ if (count < minCount)
+ {
+ blTree.Frequencies[curLen] += (short)count;
+ }
+ else if (curLen != 0)
+ {
+ blTree.Frequencies[Repeat3To6]++;
+ }
+ else if (count <= 10)
+ {
+ blTree.Frequencies[Repeat3To10]++;
+ }
+ else
+ {
+ blTree.Frequencies[Repeat11To138]++;
+ }
+ }
+ }
+
+ ///
+ /// Write the tree values.
+ ///
+ /// The pending buffer.
+ /// The tree to write.
+ public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree)
+ {
+ int maxCount; // max repeat count
+ int minCount; // min repeat count
+ int count; // repeat count of the current code
+ int curLen = -1; // length of current code
+
+ int i = 0;
+ while (i < this.NumCodes)
+ {
+ count = 1;
+ int nextlen = this.Length[i];
+ if (nextlen == 0)
+ {
+ maxCount = 138;
+ minCount = 3;
+ }
+ else
+ {
+ maxCount = 6;
+ minCount = 3;
+ if (curLen != nextlen)
+ {
+ bitLengthTree.WriteSymbol(pendingBuffer, nextlen);
+ count = 0;
+ }
+ }
+
+ curLen = nextlen;
+ i++;
+
+ while (i < this.NumCodes && curLen == this.Length[i])
+ {
+ i++;
+ if (++count >= maxCount)
+ {
+ break;
+ }
+ }
+
+ if (count < minCount)
+ {
+ while (count-- > 0)
+ {
+ bitLengthTree.WriteSymbol(pendingBuffer, curLen);
+ }
+ }
+ else if (curLen != 0)
+ {
+ bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6);
+ pendingBuffer.WriteBits(count - 3, 2);
+ }
+ else if (count <= 10)
+ {
+ bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10);
+ pendingBuffer.WriteBits(count - 3, 3);
+ }
+ else
+ {
+ bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138);
+ pendingBuffer.WriteBits(count - 11, 7);
+ }
+ }
+ }
+
+ private void BuildLength(ReadOnlySpan children)
+ {
+ byte* lengthPtr = this.Length;
+ ref int childrenRef = ref MemoryMarshal.GetReference(children);
+ ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts);
+
+ int maxLen = this.maxLength;
+ int numNodes = children.Length >> 1;
+ int numLeafs = (numNodes + 1) >> 1;
+ int overflow = 0;
+
+ Array.Clear(this.bitLengthCounts, 0, maxLen);
+
+ // First calculate optimal bit lengths
+ using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean))
+ {
+ ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span);
+
+ for (int i = numNodes - 1; i >= 0; i--)
+ {
+ if (children[(2 * i) + 1] != -1)
+ {
+ int bitLength = Unsafe.Add(ref lengthsRef, i) + 1;
+ if (bitLength > maxLen)
+ {
+ bitLength = maxLen;
+ overflow++;
+ }
+
+ Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, 2 * i)) = Unsafe.Add(ref lengthsRef, Unsafe.Add(ref childrenRef, (2 * i) + 1)) = bitLength;
+ }
+ else
+ {
+ // A leaf node
+ int bitLength = Unsafe.Add(ref lengthsRef, i);
+ Unsafe.Add(ref bitLengthCountsRef, bitLength - 1)++;
+ lengthPtr[Unsafe.Add(ref childrenRef, 2 * i)] = (byte)Unsafe.Add(ref lengthsRef, i);
+ }
+ }
+ }
+
+ if (overflow == 0)
+ {
+ return;
+ }
+
+ int incrBitLen = maxLen - 1;
+ do
+ {
+ // Find the first bit length which could increase:
+ while (Unsafe.Add(ref bitLengthCountsRef, --incrBitLen) == 0)
+ {
+ }
+
+ // Move this node one down and remove a corresponding
+ // number of overflow nodes.
+ do
+ {
+ Unsafe.Add(ref bitLengthCountsRef, incrBitLen)--;
+ Unsafe.Add(ref bitLengthCountsRef, ++incrBitLen)++;
+ overflow -= 1 << (maxLen - 1 - incrBitLen);
+ }
+ while (overflow > 0 && incrBitLen < maxLen - 1);
+ }
+ while (overflow > 0);
+
+ // We may have overshot above. Move some nodes from maxLength to
+ // maxLength-1 in that case.
+ Unsafe.Add(ref bitLengthCountsRef, maxLen - 1) += overflow;
+ Unsafe.Add(ref bitLengthCountsRef, maxLen - 2) -= overflow;
+
+ // Now recompute all bit lengths, scanning in increasing
+ // frequency. It is simpler to reconstruct all lengths instead of
+ // fixing only the wrong ones. This idea is taken from 'ar'
+ // written by Haruhiko Okumura.
+ //
+ // The nodes were inserted with decreasing frequency into the childs
+ // array.
+ int nodeIndex = 2 * numLeafs;
+ for (int bits = maxLen; bits != 0; bits--)
+ {
+ int n = Unsafe.Add(ref bitLengthCountsRef, bits - 1);
+ while (n > 0)
+ {
+ int childIndex = 2 * Unsafe.Add(ref childrenRef, nodeIndex++);
+ if (Unsafe.Add(ref childrenRef, childIndex + 1) == -1)
+ {
+ // We found another leaf
+ lengthPtr[Unsafe.Add(ref childrenRef, childIndex)] = (byte)bits;
+ n--;
+ }
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.frequenciesMemoryHandle.Dispose();
+ this.frequenciesMemoryOwner.Dispose();
+
+ this.lengthsMemoryHandle.Dispose();
+ this.lengthsMemoryOwner.Dispose();
+
+ this.codesMemoryHandle.Dispose();
+ this.codesMemoryOwner.Dispose();
+
+ this.frequenciesMemoryOwner = null;
+ this.lengthsMemoryOwner = null;
+ this.codesMemoryOwner = null;
+
+ this.isDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
new file mode 100644
index 0000000000..9eeb12cb08
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// A special stream deflating or compressing the bytes that are
+ /// written to it. It uses a Deflater to perform actual deflating.
+ ///
+ internal sealed class DeflaterOutputStream : Stream
+ {
+ private const int BufferLength = 512;
+ private IManagedByteBuffer memoryOwner;
+ private readonly byte[] buffer;
+ private Deflater deflater;
+ private readonly Stream rawStream;
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ /// The output stream where deflated output is written.
+ /// The compression level.
+ public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel)
+ {
+ this.rawStream = rawStream;
+ this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength);
+ this.buffer = this.memoryOwner.Array;
+ this.deflater = new Deflater(memoryAllocator, compressionLevel);
+ }
+
+ ///
+ public override bool CanRead => false;
+
+ ///
+ public override bool CanSeek => false;
+
+ ///
+ public override bool CanWrite => this.rawStream.CanWrite;
+
+ ///
+ public override long Length => this.rawStream.Length;
+
+ ///
+ public override long Position
+ {
+ get
+ {
+ return this.rawStream.Position;
+ }
+
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+
+ ///
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ ///
+ public override int ReadByte() => throw new NotSupportedException();
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+ ///
+ public override void Flush()
+ {
+ this.deflater.Flush();
+ this.Deflate(true);
+ this.rawStream.Flush();
+ }
+
+ ///
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ this.deflater.SetInput(buffer, offset, count);
+ this.Deflate();
+ }
+
+ private void Deflate() => this.Deflate(false);
+
+ private void Deflate(bool flushing)
+ {
+ while (flushing || !this.deflater.IsNeedingInput)
+ {
+ int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength);
+
+ if (deflateCount <= 0)
+ {
+ break;
+ }
+
+ this.rawStream.Write(this.buffer, 0, deflateCount);
+ }
+
+ if (!this.deflater.IsNeedingInput)
+ {
+ DeflateThrowHelper.ThrowNoDeflate();
+ }
+ }
+
+ private void Finish()
+ {
+ this.deflater.Finish();
+ while (!this.deflater.IsFinished)
+ {
+ int len = this.deflater.Deflate(this.buffer, 0, BufferLength);
+ if (len <= 0)
+ {
+ break;
+ }
+
+ this.rawStream.Write(this.buffer, 0, len);
+ }
+
+ if (!this.deflater.IsFinished)
+ {
+ DeflateThrowHelper.ThrowNoDeflate();
+ }
+
+ this.rawStream.Flush();
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (!this.isDisposed)
+ {
+ if (disposing)
+ {
+ this.Finish();
+ this.deflater.Dispose();
+ this.memoryOwner.Dispose();
+ }
+
+ this.deflater = null;
+ this.memoryOwner = null;
+ this.isDisposed = true;
+ base.Dispose(disposing);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
new file mode 100644
index 0000000000..a5f00f03ca
--- /dev/null
+++ b/src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs
@@ -0,0 +1,179 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Png.Zlib
+{
+ ///
+ /// Stores pending data for writing data to the Deflater.
+ ///
+ internal sealed unsafe class DeflaterPendingBuffer : IDisposable
+ {
+ private readonly byte[] buffer;
+ private readonly byte* pinnedBuffer;
+ private IManagedByteBuffer bufferMemoryOwner;
+ private MemoryHandle bufferMemoryHandle;
+
+ private int start;
+ private int end;
+ private uint bits;
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator to use for buffer allocations.
+ public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
+ {
+ this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
+ this.buffer = this.bufferMemoryOwner.Array;
+ this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin();
+ this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
+ }
+
+ ///
+ /// Gets the number of bits written to the buffer.
+ ///
+ public int BitCount { get; private set; }
+
+ ///
+ /// Gets a value indicating whether indicates the buffer has been flushed.
+ ///
+ public bool IsFlushed => this.end == 0;
+
+ ///
+ /// Clear internal state/buffers.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void Reset() => this.start = this.end = this.BitCount = 0;
+
+ ///
+ /// Write a short value to buffer LSB first.
+ ///
+ /// The value to write.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void WriteShort(int value)
+ {
+ byte* pinned = this.pinnedBuffer;
+ pinned[this.end++] = unchecked((byte)value);
+ pinned[this.end++] = unchecked((byte)(value >> 8));
+ }
+
+ ///
+ /// Write a block of data to the internal buffer.
+ ///
+ /// The data to write.
+ /// The offset of first byte to write.
+ /// The number of bytes to write.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void WriteBlock(byte[] block, int offset, int length)
+ {
+ Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length));
+ this.end += length;
+ }
+
+ ///
+ /// Aligns internal buffer on a byte boundary.
+ ///
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void AlignToByte()
+ {
+ if (this.BitCount > 0)
+ {
+ byte* pinned = this.pinnedBuffer;
+ pinned[this.end++] = unchecked((byte)this.bits);
+ if (this.BitCount > 8)
+ {
+ pinned[this.end++] = unchecked((byte)(this.bits >> 8));
+ }
+ }
+
+ this.bits = 0;
+ this.BitCount = 0;
+ }
+
+ ///
+ /// Write bits to internal buffer
+ ///
+ /// source of bits
+ /// number of bits to write
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void WriteBits(int b, int count)
+ {
+ this.bits |= (uint)(b << this.BitCount);
+ this.BitCount += count;
+ if (this.BitCount >= 16)
+ {
+ byte* pinned = this.pinnedBuffer;
+ pinned[this.end++] = unchecked((byte)this.bits);
+ pinned[this.end++] = unchecked((byte)(this.bits >> 8));
+ this.bits >>= 16;
+ this.BitCount -= 16;
+ }
+ }
+
+ ///
+ /// Write a short value to internal buffer most significant byte first
+ ///
+ /// The value to write
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public void WriteShortMSB(int value)
+ {
+ byte* pinned = this.pinnedBuffer;
+ pinned[this.end++] = unchecked((byte)(value >> 8));
+ pinned[this.end++] = unchecked((byte)value);
+ }
+
+ ///
+ /// Flushes the pending buffer into the given output array.
+ /// If the output array is to small, only a partial flush is done.
+ ///
+ /// The output array.
+ /// The offset into output array.
+ /// The maximum number of bytes to store.
+ /// The number of bytes flushed.
+ public int Flush(byte[] output, int offset, int length)
+ {
+ if (this.BitCount >= 8)
+ {
+ this.pinnedBuffer[this.end++] = unchecked((byte)this.bits);
+ this.bits >>= 8;
+ this.BitCount -= 8;
+ }
+
+ if (length > this.end - this.start)
+ {
+ length = this.end - this.start;
+
+ Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
+ this.start = 0;
+ this.end = 0;
+ }
+ else
+ {
+ Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
+ this.start += length;
+ }
+
+ return length;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this.isDisposed)
+ {
+ this.bufferMemoryHandle.Dispose();
+ this.bufferMemoryOwner.Dispose();
+ this.bufferMemoryOwner = null;
+ this.isDisposed = true;
+ }
+
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Png/Zlib/README.md b/src/ImageSharp/Formats/Png/Zlib/README.md
index c297a91d5e..59f75d05f6 100644
--- a/src/ImageSharp/Formats/Png/Zlib/README.md
+++ b/src/ImageSharp/Formats/Png/Zlib/README.md
@@ -1,2 +1,5 @@
-Adler32.cs and Crc32.cs have been copied from
-https://github.com/ygrenier/SharpZipLib.Portable
+Deflatestream implementation adapted from
+
+https://github.com/icsharpcode/SharpZipLib
+
+LIcensed under MIT
diff --git a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
index 8e0bac938f..3c52d306f9 100644
--- a/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
+++ b/src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs
@@ -1,9 +1,9 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
-using System.IO.Compression;
+using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
@@ -38,14 +38,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
///
/// The stream responsible for compressing the input stream.
///
- private System.IO.Compression.DeflateStream deflateStream;
+ // private DeflateStream deflateStream;
+ private DeflaterOutputStream deflateStream;
///
/// Initializes a new instance of the class.
///
+ /// The memory allocator to use for buffer allocations.
/// The stream to compress.
/// The compression level.
- public ZlibDeflateStream(Stream stream, int compressionLevel)
+ public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, int compressionLevel)
{
this.rawStream = stream;
@@ -60,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// +---+---+
// |CMF|FLG|
// +---+---+
- int cmf = 0x78;
+ const int Cmf = 0x78;
int flg = 218;
// http://stackoverflow.com/a/2331025/277304
@@ -78,29 +80,17 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
// Just in case
- flg -= ((cmf * 256) + flg) % 31;
+ flg -= ((Cmf * 256) + flg) % 31;
if (flg < 0)
{
flg += 31;
}
- this.rawStream.WriteByte((byte)cmf);
+ this.rawStream.WriteByte(Cmf);
this.rawStream.WriteByte((byte)flg);
- // Initialize the deflate Stream.
- CompressionLevel level = CompressionLevel.Optimal;
-
- if (compressionLevel >= 1 && compressionLevel <= 5)
- {
- level = CompressionLevel.Fastest;
- }
- else if (compressionLevel == 0)
- {
- level = CompressionLevel.NoCompression;
- }
-
- this.deflateStream = new System.IO.Compression.DeflateStream(this.rawStream, level, true);
+ this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel);
}
///
@@ -110,41 +100,36 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public override bool CanSeek => false;
///
- public override bool CanWrite => true;
+ public override bool CanWrite => this.rawStream.CanWrite;
///
- public override long Length => throw new NotSupportedException();
+ public override long Length => this.rawStream.Length;
///
public override long Position
{
- get => throw new NotSupportedException();
- set => throw new NotSupportedException();
+ get
+ {
+ return this.rawStream.Position;
+ }
+
+ set
+ {
+ throw new NotSupportedException();
+ }
}
///
- public override void Flush()
- {
- this.deflateStream?.Flush();
- }
+ public override void Flush() => this.deflateStream.Flush();
///
- public override int Read(byte[] buffer, int offset, int count)
- {
- throw new NotSupportedException();
- }
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
///
- public override long Seek(long offset, SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
///
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
+ public override void SetLength(long value) => throw new NotSupportedException();
///
public override void Write(byte[] buffer, int offset, int count)
@@ -164,17 +149,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (disposing)
{
// dispose managed resources
- if (this.deflateStream != null)
- {
- this.deflateStream.Dispose();
- this.deflateStream = null;
- }
- else
- {
- // Hack: empty input?
- this.rawStream.WriteByte(3);
- this.rawStream.WriteByte(0);
- }
+ this.deflateStream.Dispose();
// Add the crc
uint crc = (uint)this.adler32.Value;
@@ -184,11 +159,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.rawStream.WriteByte((byte)(crc & 0xFF));
}
- base.Dispose(disposing);
+ this.deflateStream = null;
- // Call the appropriate methods to clean up
- // unmanaged resources here.
- // Note disposing is done.
+ base.Dispose(disposing);
this.isDisposed = true;
}
}
diff --git a/src/ImageSharp/Formats/README.md b/src/ImageSharp/Formats/README.md
new file mode 100644
index 0000000000..4a2b401b1d
--- /dev/null
+++ b/src/ImageSharp/Formats/README.md
@@ -0,0 +1,6 @@
+# Encoder/Decoder for true vision targa files
+
+Useful links for reference:
+
+- [FileFront](https://www.fileformat.info/format/tga/egff.htm)
+- [Tga Specification](http://www.dca.fee.unicamp.br/~martino/disciplinas/ea978/tgaffs.pdf)
diff --git a/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
new file mode 100644
index 0000000000..e99e8b0c8d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/ITgaDecoderOptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// The options for decoding tga images. Currently empty, but this may change in the future.
+ ///
+ internal interface ITgaDecoderOptions
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
new file mode 100644
index 0000000000..49983d2369
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/ITgaEncoderOptions.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Configuration options for use during tga encoding.
+ ///
+ internal interface ITgaEncoderOptions
+ {
+ ///
+ /// Gets the number of bits per pixel.
+ ///
+ TgaBitsPerPixel? BitsPerPixel { get; }
+
+ ///
+ /// Gets a value indicating whether run length compression should be used.
+ ///
+ TgaCompression Compression { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TGA_Specification.pdf b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf
new file mode 100644
index 0000000000..09c9a4ddda
Binary files /dev/null and b/src/ImageSharp/Formats/Tga/TGA_Specification.pdf differ
diff --git a/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
new file mode 100644
index 0000000000..a0666fa84d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaBitsPerPixel.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Enumerates the available bits per pixel the tga encoder supports.
+ ///
+ public enum TgaBitsPerPixel : byte
+ {
+ ///
+ /// 8 bits per pixel. Each pixel consists of 1 byte.
+ ///
+ Pixel8 = 8,
+
+ ///
+ /// 16 bits per pixel. Each pixel consists of 2 bytes.
+ ///
+ Pixel16 = 16,
+
+ ///
+ /// 24 bits per pixel. Each pixel consists of 3 bytes.
+ ///
+ Pixel24 = 24,
+
+ ///
+ /// 32 bits per pixel. Each pixel consists of 4 bytes.
+ ///
+ Pixel32 = 32
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaCompression.cs b/src/ImageSharp/Formats/Tga/TgaCompression.cs
new file mode 100644
index 0000000000..cc6e005eda
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaCompression.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Indicates if compression is used.
+ ///
+ public enum TgaCompression
+ {
+ ///
+ /// No compression is used.
+ ///
+ None,
+
+ ///
+ /// Run length encoding is used.
+ ///
+ RunLength,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
new file mode 100644
index 0000000000..18fbf4acd0
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaConfigurationModule.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the tga format.
+ ///
+ public sealed class TgaConfigurationModule : IConfigurationModule
+ {
+ ///
+ public void Configure(Configuration configuration)
+ {
+ configuration.ImageFormatsManager.SetEncoder(TgaFormat.Instance, new TgaEncoder());
+ configuration.ImageFormatsManager.SetDecoder(TgaFormat.Instance, new TgaDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new TgaImageFormatDetector());
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaConstants.cs b/src/ImageSharp/Formats/Tga/TgaConstants.cs
new file mode 100644
index 0000000000..5aabe92a1d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaConstants.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal static class TgaConstants
+ {
+ ///
+ /// The list of mimetypes that equate to a targa file.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/x-tga", "image/x-targa" };
+
+ ///
+ /// The list of file extensions that equate to a targa file.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "tga", "vda", "icb", "vst" };
+
+ ///
+ /// The file header length of a tga image in bytes.
+ ///
+ public const int FileHeaderLength = 18;
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoder.cs b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
new file mode 100644
index 0000000000..b97388773a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaDecoder.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Image decoder for Truevision TGA images.
+ ///
+ public sealed class TgaDecoder : IImageDecoder, ITgaDecoderOptions, IImageInfoDetector
+ {
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new TgaDecoderCore(configuration, this).Decode(stream);
+ }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream) => this.Decode(configuration, stream);
+
+ ///
+ public IImageInfo Identify(Configuration configuration, Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ return new TgaDecoderCore(configuration, this).Identify(stream);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
new file mode 100644
index 0000000000..d861450e04
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
@@ -0,0 +1,588 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal sealed class TgaDecoderCore
+ {
+ ///
+ /// The metadata.
+ ///
+ private ImageMetadata metadata;
+
+ ///
+ /// The tga specific metadata.
+ ///
+ private TgaMetadata tgaMetadata;
+
+ ///
+ /// The file header containing general information about the image.
+ ///
+ private TgaFileHeader fileHeader;
+
+ ///
+ /// The global configuration.
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The stream to decode from.
+ ///
+ private Stream currentStream;
+
+ ///
+ /// The bitmap decoder options.
+ ///
+ private readonly ITgaDecoderOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The options.
+ public TgaDecoderCore(Configuration configuration, ITgaDecoderOptions options)
+ {
+ this.configuration = configuration;
+ this.memoryAllocator = configuration.MemoryAllocator;
+ this.options = options;
+ }
+
+ ///
+ /// Decodes the image from the specified stream.
+ ///
+ /// The pixel format.
+ /// The stream, where the image should be decoded from. Cannot be null.
+ ///
+ /// is null.
+ ///
+ /// The decoded image.
+ public Image Decode(Stream stream)
+ where TPixel : struct, IPixel
+ {
+ try
+ {
+ bool inverted = this.ReadFileHeader(stream);
+ this.currentStream.Skip(this.fileHeader.IdLength);
+
+ // Parse the color map, if present.
+ if (this.fileHeader.ColorMapType != 0 && this.fileHeader.ColorMapType != 1)
+ {
+ TgaThrowHelper.ThrowNotSupportedException($"Unknown tga colormap type {this.fileHeader.ColorMapType} found");
+ }
+
+ if (this.fileHeader.Width == 0 || this.fileHeader.Height == 0)
+ {
+ throw new UnknownImageFormatException("Width or height cannot be 0");
+ }
+
+ var image = new Image(this.configuration, this.fileHeader.Width, this.fileHeader.Height, this.metadata);
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ if (this.fileHeader.ColorMapType is 1)
+ {
+ if (this.fileHeader.CMapLength <= 0)
+ {
+ TgaThrowHelper.ThrowImageFormatException("Missing tga color map length");
+ }
+
+ if (this.fileHeader.CMapDepth <= 0)
+ {
+ TgaThrowHelper.ThrowImageFormatException("Missing tga color map depth");
+ }
+
+ int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
+ int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes;
+ using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean))
+ {
+ this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes);
+
+ if (this.fileHeader.ImageType is TgaImageType.RleColorMapped)
+ {
+ this.ReadPalettedRle(
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ pixels,
+ palette.Array,
+ colorMapPixelSizeInBytes,
+ inverted);
+ }
+ else
+ {
+ this.ReadPaletted(
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ pixels,
+ palette.Array,
+ colorMapPixelSizeInBytes,
+ inverted);
+ }
+ }
+
+ return image;
+ }
+
+ // Even if the image type indicates it is not a paletted image, it can still contain a palette. Skip those bytes.
+ if (this.fileHeader.CMapLength > 0)
+ {
+ int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
+ this.currentStream.Skip(this.fileHeader.CMapLength * colorMapPixelSizeInBytes);
+ }
+
+ switch (this.fileHeader.PixelDepth)
+ {
+ case 8:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 1, inverted);
+ }
+ else
+ {
+ this.ReadMonoChrome(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 15:
+ case 16:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 2, inverted);
+ }
+ else
+ {
+ this.ReadBgra16(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 24:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 3, inverted);
+ }
+ else
+ {
+ this.ReadBgr24(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ case 32:
+ if (this.fileHeader.ImageType.IsRunLengthEncoded())
+ {
+ this.ReadRle(this.fileHeader.Width, this.fileHeader.Height, pixels, 4, inverted);
+ }
+ else
+ {
+ this.ReadBgra32(this.fileHeader.Width, this.fileHeader.Height, pixels, inverted);
+ }
+
+ break;
+
+ default:
+ TgaThrowHelper.ThrowNotSupportedException("Does not support this kind of tga files.");
+ break;
+ }
+
+ return image;
+ }
+ catch (IndexOutOfRangeException e)
+ {
+ throw new ImageFormatException("TGA image does not have a valid format.", e);
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image with a palette.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The color palette.
+ /// Color map size of one entry in bytes.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadPaletted(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(width, AllocationOptions.Clean))
+ {
+ TPixel color = default;
+ Span rowSpan = row.GetSpan();
+
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ switch (colorMapPixelSizeInBytes)
+ {
+ case 2:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ Bgra5551 bgra = Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]);
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ color.FromBgra5551(bgra);
+ pixelRow[x] = color;
+ }
+
+ break;
+
+ case 3:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+ color.FromBgr24(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
+ break;
+
+ case 4:
+ for (int x = 0; x < width; x++)
+ {
+ int colorIndex = rowSpan[x];
+ color.FromBgra32(Unsafe.As(ref palette[colorIndex * colorMapPixelSizeInBytes]));
+ pixelRow[x] = color;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads a run length encoded TGA image with a palette.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The color palette.
+ /// Color map size of one entry in bytes.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadPalettedRle(int width, int height, Buffer2D pixels, byte[] palette, int colorMapPixelSizeInBytes, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ int bytesPerPixel = 1;
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
+ {
+ TPixel color = default;
+ Span bufferSpan = buffer.GetSpan();
+ this.UncompressRle(width, height, bufferSpan, bytesPerPixel: 1);
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ int rowStartIdx = y * width * bytesPerPixel;
+ for (int x = 0; x < width; x++)
+ {
+ int idx = rowStartIdx + x;
+ switch (colorMapPixelSizeInBytes)
+ {
+ case 1:
+ color.FromGray8(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ case 2:
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ Bgra5551 bgra = Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]);
+ bgra.PackedValue = (ushort)(bgra.PackedValue | 0x8000);
+ color.FromBgra5551(bgra);
+ break;
+ case 3:
+ color.FromBgr24(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ case 4:
+ color.FromBgra32(Unsafe.As(ref palette[bufferSpan[idx] * colorMapPixelSizeInBytes]));
+ break;
+ }
+
+ pixelRow[x] = color;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed monochrome TGA image.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadMonoChrome(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromGray8Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 16 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgra16(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ Span rowSpan = row.GetSpan();
+
+ // We need to set each alpha component value to fully opaque.
+ for (int x = 1; x < rowSpan.Length; x += 2)
+ {
+ rowSpan[x] = (byte)(rowSpan[x] | (1 << 7));
+ }
+
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgra5551Bytes(
+ this.configuration,
+ rowSpan,
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 24 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgr24(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgr24Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a uncompressed TGA image where each pixels has 32 bit.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadBgra32(int width, int height, Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
+ {
+ for (int y = 0; y < height; y++)
+ {
+ this.currentStream.Read(row);
+ int newY = Invert(y, height, inverted);
+ Span pixelSpan = pixels.GetRowSpan(newY);
+ PixelOperations.Instance.FromBgra32Bytes(
+ this.configuration,
+ row.GetSpan(),
+ pixelSpan,
+ width);
+ }
+ }
+ }
+
+ ///
+ /// Reads a run length encoded TGA image.
+ ///
+ /// The pixel type.
+ /// The width of the image.
+ /// The height of the image.
+ /// The to assign the palette to.
+ /// The bytes per pixel.
+ /// Indicates, if the origin of the image is top left rather the bottom left (the default).
+ private void ReadRle(int width, int height, Buffer2D pixels, int bytesPerPixel, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default;
+ using (IMemoryOwner buffer = this.memoryAllocator.Allocate(width * height * bytesPerPixel, AllocationOptions.Clean))
+ {
+ Span bufferSpan = buffer.GetSpan();
+ this.UncompressRle(width, height, bufferSpan, bytesPerPixel);
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ int rowStartIdx = y * width * bytesPerPixel;
+ for (int x = 0; x < width; x++)
+ {
+ int idx = rowStartIdx + (x * bytesPerPixel);
+ switch (bytesPerPixel)
+ {
+ case 1:
+ color.FromGray8(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 2:
+ // Set alpha value to 1, to treat it as opaque for Bgra5551.
+ bufferSpan[idx + 1] = (byte)(bufferSpan[idx + 1] | 128);
+ color.FromBgra5551(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 3:
+ color.FromBgr24(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ case 4:
+ color.FromBgra32(Unsafe.As(ref bufferSpan[idx]));
+ break;
+ }
+
+ pixelRow[x] = color;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Reads the raw image information from the specified stream.
+ ///
+ /// The containing image data.
+ public IImageInfo Identify(Stream stream)
+ {
+ this.ReadFileHeader(stream);
+ return new ImageInfo(
+ new PixelTypeInfo(this.fileHeader.PixelDepth),
+ this.fileHeader.Width,
+ this.fileHeader.Height,
+ this.metadata);
+ }
+
+ ///
+ /// Produce uncompressed tga data from a run length encoded stream.
+ ///
+ /// The width of the image.
+ /// The height of the image.
+ /// Buffer for uncompressed data.
+ /// The bytes used per pixel.
+ private void UncompressRle(int width, int height, Span buffer, int bytesPerPixel)
+ {
+ int uncompressedPixels = 0;
+ var pixel = new byte[bytesPerPixel];
+ int totalPixels = width * height;
+ while (uncompressedPixels < totalPixels)
+ {
+ byte runLengthByte = (byte)this.currentStream.ReadByte();
+
+ // The high bit of a run length packet is set to 1.
+ int highBit = runLengthByte >> 7;
+ if (highBit == 1)
+ {
+ int runLength = runLengthByte & 127;
+ this.currentStream.Read(pixel, 0, bytesPerPixel);
+ int bufferIdx = uncompressedPixels * bytesPerPixel;
+ for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
+ {
+ pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
+ bufferIdx += bytesPerPixel;
+ }
+ }
+ else
+ {
+ // Non-run-length encoded packet.
+ int runLength = runLengthByte;
+ int bufferIdx = uncompressedPixels * bytesPerPixel;
+ for (int i = 0; i < runLength + 1; i++, uncompressedPixels++)
+ {
+ this.currentStream.Read(pixel, 0, bytesPerPixel);
+ pixel.AsSpan().CopyTo(buffer.Slice(bufferIdx));
+ bufferIdx += bytesPerPixel;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Returns the y- value based on the given height.
+ ///
+ /// The y- value representing the current row.
+ /// The height of the bitmap.
+ /// Whether the bitmap is inverted.
+ /// The representing the inverted value.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static int Invert(int y, int height, bool inverted) => (!inverted) ? height - y - 1 : y;
+
+ ///
+ /// Reads the tga file header from the stream.
+ ///
+ /// The containing image data.
+ /// true, if the image origin is top left.
+ private bool ReadFileHeader(Stream stream)
+ {
+ this.currentStream = stream;
+
+#if NETCOREAPP2_1
+ Span buffer = stackalloc byte[TgaFileHeader.Size];
+#else
+ var buffer = new byte[TgaFileHeader.Size];
+#endif
+ this.currentStream.Read(buffer, 0, TgaFileHeader.Size);
+ this.fileHeader = TgaFileHeader.Parse(buffer);
+ this.metadata = new ImageMetadata();
+ this.tgaMetadata = this.metadata.GetFormatMetadata(TgaFormat.Instance);
+ this.tgaMetadata.BitsPerPixel = (TgaBitsPerPixel)this.fileHeader.PixelDepth;
+
+ // Bit at position 5 of the descriptor indicates, that the origin is top left instead of bottom right.
+ if ((this.fileHeader.ImageDescriptor & (1 << 5)) != 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoder.cs b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
new file mode 100644
index 0000000000..2fcbb822f5
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoder.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Image encoder for writing an image to a stream as a targa truevision image.
+ ///
+ public sealed class TgaEncoder : IImageEncoder, ITgaEncoderOptions
+ {
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public TgaBitsPerPixel? BitsPerPixel { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether no compression or run length compression should be used.
+ ///
+ public TgaCompression Compression { get; set; } = TgaCompression.RunLength;
+
+ ///
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ var encoder = new TgaEncoderCore(this, image.GetMemoryAllocator());
+ encoder.Encode(image, stream);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
new file mode 100644
index 0000000000..28b87e9857
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
@@ -0,0 +1,348 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers.Binary;
+using System.IO;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Image encoder for writing an image to a stream as a truevision targa image.
+ ///
+ internal sealed class TgaEncoderCore
+ {
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
+ private readonly MemoryAllocator memoryAllocator;
+
+ ///
+ /// The global configuration.
+ ///
+ private Configuration configuration;
+
+ ///
+ /// Reusable buffer for writing data.
+ ///
+ private readonly byte[] buffer = new byte[2];
+
+ ///
+ /// The color depth, in number of bits per pixel.
+ ///
+ private TgaBitsPerPixel? bitsPerPixel;
+
+ ///
+ /// Indicates if run length compression should be used.
+ ///
+ private readonly TgaCompression compression;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The encoder options.
+ /// The memory manager.
+ public TgaEncoderCore(ITgaEncoderOptions options, MemoryAllocator memoryAllocator)
+ {
+ this.memoryAllocator = memoryAllocator;
+ this.bitsPerPixel = options.BitsPerPixel;
+ this.compression = options.Compression;
+ }
+
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(image, nameof(image));
+ Guard.NotNull(stream, nameof(stream));
+
+ this.configuration = image.GetConfiguration();
+ ImageMetadata metadata = image.Metadata;
+ TgaMetadata tgaMetadata = metadata.GetFormatMetadata(TgaFormat.Instance);
+ this.bitsPerPixel = this.bitsPerPixel ?? tgaMetadata.BitsPerPixel;
+
+ TgaImageType imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleTrueColor : TgaImageType.TrueColor;
+ if (this.bitsPerPixel == TgaBitsPerPixel.Pixel8)
+ {
+ imageType = this.compression is TgaCompression.RunLength ? TgaImageType.RleBlackAndWhite : TgaImageType.BlackAndWhite;
+ }
+
+ // If compression is used, set bit 5 of the image descriptor to indicate an left top origin.
+ byte imageDescriptor = (byte)(this.compression is TgaCompression.RunLength ? 32 : 0);
+
+ var fileHeader = new TgaFileHeader(
+ idLength: 0,
+ colorMapType: 0,
+ imageType: imageType,
+ cMapStart: 0,
+ cMapLength: 0,
+ cMapDepth: 0,
+ xOffset: 0,
+ yOffset: this.compression is TgaCompression.RunLength ? (short)image.Height : (short)0, // When run length encoding is used, the origin should be top left instead of the default bottom left.
+ width: (short)image.Width,
+ height: (short)image.Height,
+ pixelDepth: (byte)this.bitsPerPixel.Value,
+ imageDescriptor: imageDescriptor);
+
+#if NETCOREAPP2_1
+ Span buffer = stackalloc byte[TgaFileHeader.Size];
+#else
+ byte[] buffer = new byte[TgaFileHeader.Size];
+#endif
+ fileHeader.WriteTo(buffer);
+
+ stream.Write(buffer, 0, TgaFileHeader.Size);
+
+ if (this.compression is TgaCompression.RunLength)
+ {
+ this.WriteRunLengthEndcodedImage(stream, image.Frames.RootFrame);
+ }
+ else
+ {
+ this.WriteImage(stream, image.Frames.RootFrame);
+ }
+
+ stream.Flush();
+ }
+
+ ///
+ /// Writes the pixel data to the binary stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ ///
+ /// The containing pixel data.
+ ///
+ private void WriteImage(Stream stream, ImageFrame image)
+ where TPixel : struct, IPixel
+ {
+ Buffer2D pixels = image.PixelBuffer;
+ switch (this.bitsPerPixel)
+ {
+ case TgaBitsPerPixel.Pixel8:
+ this.Write8Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel16:
+ this.Write16Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel24:
+ this.Write24Bit(stream, pixels);
+ break;
+
+ case TgaBitsPerPixel.Pixel32:
+ this.Write32Bit(stream, pixels);
+ break;
+ }
+ }
+
+ ///
+ /// Writes a run length encoded tga image to the stream.
+ ///
+ /// The pixel type.
+ /// The stream to write the image to.
+ /// The image to encode.
+ private void WriteRunLengthEndcodedImage(Stream stream, ImageFrame image)
+ where TPixel : struct, IPixel
+ {
+ Rgba32 color = default;
+ Buffer2D pixels = image.PixelBuffer;
+ Span pixelSpan = pixels.GetSpan();
+ int totalPixels = image.Width * image.Height;
+ int encodedPixels = 0;
+ while (encodedPixels < totalPixels)
+ {
+ TPixel currentPixel = pixelSpan[encodedPixels];
+ currentPixel.ToRgba32(ref color);
+ byte equalPixelCount = this.FindEqualPixels(pixelSpan.Slice(encodedPixels));
+
+ // Write the number of equal pixels, with the high bit set, indicating ist a compressed pixel run.
+ stream.WriteByte((byte)(equalPixelCount | 128));
+ switch (this.bitsPerPixel)
+ {
+ case TgaBitsPerPixel.Pixel8:
+ int luminance = GetLuminance(currentPixel);
+ stream.WriteByte((byte)luminance);
+ break;
+
+ case TgaBitsPerPixel.Pixel16:
+ var bgra5551 = new Bgra5551(color.ToVector4());
+ BinaryPrimitives.TryWriteInt16LittleEndian(this.buffer, (short)bgra5551.PackedValue);
+ stream.WriteByte(this.buffer[0]);
+ stream.WriteByte(this.buffer[1]);
+
+ break;
+
+ case TgaBitsPerPixel.Pixel24:
+ stream.WriteByte(color.B);
+ stream.WriteByte(color.G);
+ stream.WriteByte(color.R);
+ break;
+
+ case TgaBitsPerPixel.Pixel32:
+ stream.WriteByte(color.B);
+ stream.WriteByte(color.G);
+ stream.WriteByte(color.R);
+ stream.WriteByte(color.A);
+ break;
+ }
+
+ encodedPixels += equalPixelCount + 1;
+ }
+ }
+
+ ///
+ /// Finds consecutive pixels, which have the same value starting from the pixel span offset 0.
+ ///
+ /// The pixel type.
+ /// The pixel span to search in.
+ /// The number of equal pixels.
+ private byte FindEqualPixels(Span pixelSpan)
+ where TPixel : struct, IPixel
+ {
+ int idx = 0;
+ byte equalPixelCount = 0;
+ while (equalPixelCount < 127 && idx < pixelSpan.Length - 1)
+ {
+ TPixel currentPixel = pixelSpan[idx];
+ TPixel nextPixel = pixelSpan[idx + 1];
+ if (currentPixel.Equals(nextPixel))
+ {
+ equalPixelCount++;
+ }
+ else
+ {
+ return equalPixelCount;
+ }
+
+ idx++;
+ }
+
+ return equalPixelCount;
+ }
+
+ private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
+
+ ///
+ /// Writes the 8bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write8Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToGray8Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Writes the 16bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write16Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgra5551Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Writes the 24bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write24Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgr24Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Writes the 32bit pixels uncompressed to the stream.
+ ///
+ /// The pixel format.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write32Bit(Stream stream, Buffer2D pixels)
+ where TPixel : struct, IPixel
+ {
+ using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
+ {
+ for (int y = pixels.Height - 1; y >= 0; y--)
+ {
+ Span pixelSpan = pixels.GetRowSpan(y);
+ PixelOperations.Instance.ToBgra32Bytes(
+ this.configuration,
+ pixelSpan,
+ row.GetSpan(),
+ pixelSpan.Length);
+ stream.Write(row.Array, 0, row.Length());
+ }
+ }
+ }
+
+ ///
+ /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
+ ///
+ /// The pixel to get the luminance from.
+ [MethodImpl(InliningOptions.ShortMethod)]
+ public static int GetLuminance(TPixel sourcePixel)
+ where TPixel : struct, IPixel
+ {
+ var vector = sourcePixel.ToVector4();
+ return ImageMaths.GetBT709Luminance(ref vector, 256);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaFileHeader.cs b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
new file mode 100644
index 0000000000..e2bbb6fbd2
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaFileHeader.cs
@@ -0,0 +1,147 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// This block of bytes tells the application detailed information about the targa image.
+ ///
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ internal readonly struct TgaFileHeader
+ {
+ ///
+ /// Defines the size of the data structure in the targa file.
+ ///
+ public const int Size = TgaConstants.FileHeaderLength;
+
+ public TgaFileHeader(
+ byte idLength,
+ byte colorMapType,
+ TgaImageType imageType,
+ short cMapStart,
+ short cMapLength,
+ byte cMapDepth,
+ short xOffset,
+ short yOffset,
+ short width,
+ short height,
+ byte pixelDepth,
+ byte imageDescriptor)
+ {
+ this.IdLength = idLength;
+ this.ColorMapType = colorMapType;
+ this.ImageType = imageType;
+ this.CMapStart = cMapStart;
+ this.CMapLength = cMapLength;
+ this.CMapDepth = cMapDepth;
+ this.XOffset = xOffset;
+ this.YOffset = yOffset;
+ this.Width = width;
+ this.Height = height;
+ this.PixelDepth = pixelDepth;
+ this.ImageDescriptor = imageDescriptor;
+ }
+
+ ///
+ /// Gets the id length.
+ /// This field identifies the number of bytes contained in Field 6, the Image ID Field. The maximum number
+ /// of characters is 255. A value of zero indicates that no Image ID field is included with the image.
+ ///
+ public byte IdLength { get; }
+
+ ///
+ /// Gets the color map type.
+ /// This field indicates the type of color map (if any) included with the image. There are currently 2 defined
+ /// values for this field:
+ /// 0 - indicates that no color-map data is included with this image.
+ /// 1 - indicates that a color-map is included with this image.
+ ///
+ public byte ColorMapType { get; }
+
+ ///
+ /// Gets the image type.
+ /// The TGA File Format can be used to store Pseudo-Color, True-Color and Direct-Color images of various
+ /// pixel depths.
+ ///
+ public TgaImageType ImageType { get; }
+
+ ///
+ /// Gets the start of the color map.
+ /// This field and its sub-fields describe the color map (if any) used for the image. If the Color Map Type field
+ /// is set to zero, indicating that no color map exists, then these 5 bytes should be set to zero.
+ ///
+ public short CMapStart { get; }
+
+ ///
+ /// Gets the total number of color map entries included.
+ ///
+ public short CMapLength { get; }
+
+ ///
+ /// Gets the number of bits per entry. Typically 15, 16, 24 or 32-bit values are used.
+ ///
+ public byte CMapDepth { get; }
+
+ ///
+ /// Gets the XOffset.
+ /// These bytes specify the absolute horizontal coordinate for the lower left
+ /// corner of the image as it is positioned on a display device having an
+ /// origin at the lower left of the screen.
+ ///
+ public short XOffset { get; }
+
+ ///
+ /// Gets the YOffset.
+ /// These bytes specify the absolute vertical coordinate for the lower left
+ /// corner of the image as it is positioned on a display device having an
+ /// origin at the lower left of the screen.
+ ///
+ public short YOffset { get; }
+
+ ///
+ /// Gets the width of the image in pixels.
+ ///
+ public short Width { get; }
+
+ ///
+ /// Gets the height of the image in pixels.
+ ///
+ public short Height { get; }
+
+ ///
+ /// Gets the number of bits per pixel. This number includes
+ /// the Attribute or Alpha channel bits. Common values are 8, 16, 24 and
+ /// 32 but other pixel depths could be used.
+ ///
+ public byte PixelDepth { get; }
+
+ ///
+ /// Gets the ImageDescriptor.
+ /// ImageDescriptor contains two pieces of information.
+ /// Bits 0 through 3 contain the number of attribute bits per pixel.
+ /// Attribute bits are found only in pixels for the 16- and 32-bit flavors of the TGA format and are called alpha channel,
+ /// overlay, or interrupt bits. Bits 4 and 5 contain the image origin location (coordinate 0,0) of the image.
+ /// This position may be any of the four corners of the display screen.
+ /// When both of these bits are set to zero, the image origin is the lower-left corner of the screen.
+ /// Bits 6 and 7 of the ImageDescriptor field are unused and should be set to 0.
+ ///
+ public byte ImageDescriptor { get; }
+
+ public static TgaFileHeader Parse(Span data)
+ {
+ return MemoryMarshal.Cast(data)[0];
+ }
+
+ public void WriteTo(Span buffer)
+ {
+ ref TgaFileHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer));
+
+ dest = this;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaFormat.cs b/src/ImageSharp/Formats/Tga/TgaFormat.cs
new file mode 100644
index 0000000000..badb1d77a4
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaFormat.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the tga format.
+ ///
+ public sealed class TgaFormat : IImageFormat
+ {
+ ///
+ /// Gets the current instance.
+ ///
+ public static TgaFormat Instance { get; } = new TgaFormat();
+
+ ///
+ public string Name => "TGA";
+
+ ///
+ public string DefaultMimeType => "image/tga";
+
+ ///
+ public IEnumerable MimeTypes => TgaConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => TgaConstants.FileExtensions;
+
+ ///
+ public TgaMetadata CreateDefaultFormatMetadata() => new TgaMetadata();
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
new file mode 100644
index 0000000000..bd9cfa900c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs
@@ -0,0 +1,40 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Detects tga file headers.
+ ///
+ public sealed class TgaImageFormatDetector : IImageFormatDetector
+ {
+ ///
+ public int HeaderSize => TgaConstants.FileHeaderLength;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header)
+ {
+ return this.IsSupportedFileFormat(header) ? TgaFormat.Instance : null;
+ }
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ {
+ if (header.Length >= this.HeaderSize)
+ {
+ // There is no magick bytes in a tga file, so at least the image type
+ // and the colormap type in the header will be checked for a valid value.
+ if (header[1] != 0 && header[1] != 1)
+ {
+ return false;
+ }
+
+ var imageType = (TgaImageType)header[2];
+ return imageType.IsValid();
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageType.cs b/src/ImageSharp/Formats/Tga/TgaImageType.cs
new file mode 100644
index 0000000000..491fd3ea77
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageType.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.
+ ImageSharp.Formats.Tga
+{
+ ///
+ /// Defines the tga image type. The TGA File Format can be used to store Pseudo-Color,
+ /// True-Color and Direct-Color images of various pixel depths.
+ ///
+ public enum TgaImageType : byte
+ {
+ ///
+ /// No image data included.
+ ///
+ NoImageData = 0,
+
+ ///
+ /// Uncompressed, color mapped image.
+ ///
+ ColorMapped = 1,
+
+ ///
+ /// Uncompressed true color image.
+ ///
+ TrueColor = 2,
+
+ ///
+ /// Uncompressed Black and white (grayscale) image.
+ ///
+ BlackAndWhite = 3,
+
+ ///
+ /// Run length encoded, color mapped image.
+ ///
+ RleColorMapped = 9,
+
+ ///
+ /// Run length encoded, true color image.
+ ///
+ RleTrueColor = 10,
+
+ ///
+ /// Run length encoded, black and white (grayscale) image.
+ ///
+ RleBlackAndWhite = 11,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
new file mode 100644
index 0000000000..6a30cdddd7
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaImageTypeExtensions.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Extension methods for TgaImageType enum.
+ ///
+ public static class TgaImageTypeExtensions
+ {
+ ///
+ /// Checks if this tga image type is run length encoded.
+ ///
+ /// The tga image type.
+ /// True, if this image type is run length encoded, otherwise false.
+ public static bool IsRunLengthEncoded(this TgaImageType imageType)
+ {
+ if (imageType is TgaImageType.RleColorMapped || imageType is TgaImageType.RleBlackAndWhite || imageType is TgaImageType.RleTrueColor)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks, if the image type has valid value.
+ ///
+ /// The image type.
+ /// true, if its a valid tga image type.
+ public static bool IsValid(this TgaImageType imageType)
+ {
+ switch (imageType)
+ {
+ case TgaImageType.NoImageData:
+ case TgaImageType.ColorMapped:
+ case TgaImageType.TrueColor:
+ case TgaImageType.BlackAndWhite:
+ case TgaImageType.RleColorMapped:
+ case TgaImageType.RleTrueColor:
+ case TgaImageType.RleBlackAndWhite:
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaMetadata.cs b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
new file mode 100644
index 0000000000..4ce61d2e48
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaMetadata.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ ///
+ /// Provides TGA specific metadata information for the image.
+ ///
+ public class TgaMetadata : IDeepCloneable
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TgaMetadata()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The metadata to create an instance from.
+ private TgaMetadata(TgaMetadata other)
+ {
+ this.BitsPerPixel = other.BitsPerPixel;
+ }
+
+ ///
+ /// Gets or sets the number of bits per pixel.
+ ///
+ public TgaBitsPerPixel BitsPerPixel { get; set; } = TgaBitsPerPixel.Pixel24;
+
+ ///
+ public IDeepCloneable DeepClone() => new TgaMetadata(this);
+ }
+}
diff --git a/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
new file mode 100644
index 0000000000..845d009227
--- /dev/null
+++ b/src/ImageSharp/Formats/Tga/TgaThrowHelper.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tga
+{
+ internal static class TgaThrowHelper
+ {
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowImageFormatException(string errorMessage)
+ {
+ throw new ImageFormatException(errorMessage);
+ }
+
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowNotSupportedException(string errorMessage)
+ {
+ throw new NotSupportedException(errorMessage);
+ }
+ }
+}
diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs
index 214b10810a..47b930e654 100644
--- a/src/ImageSharp/GraphicsOptions.cs
+++ b/src/ImageSharp/GraphicsOptions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
@@ -8,170 +8,82 @@ namespace SixLabors.ImageSharp
///
/// Options for influencing the drawing functions.
///
- public struct GraphicsOptions
+ public class GraphicsOptions : IDeepCloneable
{
- ///
- /// Represents the default .
- ///
- public static readonly GraphicsOptions Default = new GraphicsOptions(true);
-
- private float? blendPercentage;
-
- private int? antialiasSubpixelDepth;
-
- private bool? antialias;
-
- private PixelColorBlendingMode colorBlendingMode;
-
- private PixelAlphaCompositionMode alphaCompositionMode;
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// If set to true [enable antialiasing].
- public GraphicsOptions(bool enableAntialiasing)
- {
- this.colorBlendingMode = PixelColorBlendingMode.Normal;
- this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
- this.blendPercentage = 1;
- this.antialiasSubpixelDepth = 16;
- this.antialias = enableAntialiasing;
- }
-
- ///
- /// Initializes a new instance of the struct.
- ///
- /// If set to true [enable antialiasing].
- /// blending percentage to apply to the drawing operation
- public GraphicsOptions(bool enableAntialiasing, float opacity)
- {
- Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
-
- this.colorBlendingMode = PixelColorBlendingMode.Normal;
- this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
- this.blendPercentage = opacity;
- this.antialiasSubpixelDepth = 16;
- this.antialias = enableAntialiasing;
- }
+ private int antialiasSubpixelDepth = 16;
+ private float blendPercentage = 1F;
///
- /// Initializes a new instance of the struct.
+ /// Initializes a new instance of the class.
///
- /// If set to true [enable antialiasing].
- /// blending percentage to apply to the drawing operation
- /// color blending mode to apply to the drawing operation
- public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, float opacity)
+ public GraphicsOptions()
{
- Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
-
- this.colorBlendingMode = blending;
- this.alphaCompositionMode = PixelAlphaCompositionMode.SrcOver;
- this.blendPercentage = opacity;
- this.antialiasSubpixelDepth = 16;
- this.antialias = enableAntialiasing;
}
- ///
- /// Initializes a new instance of the struct.
- ///
- /// If set to true [enable antialiasing].
- /// blending percentage to apply to the drawing operation
- /// color blending mode to apply to the drawing operation
- /// alpha composition mode to apply to the drawing operation
- public GraphicsOptions(bool enableAntialiasing, PixelColorBlendingMode blending, PixelAlphaCompositionMode composition, float opacity)
+ private GraphicsOptions(GraphicsOptions source)
{
- Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
-
- this.colorBlendingMode = blending;
- this.alphaCompositionMode = composition;
- this.blendPercentage = opacity;
- this.antialiasSubpixelDepth = 16;
- this.antialias = enableAntialiasing;
+ this.AlphaCompositionMode = source.AlphaCompositionMode;
+ this.Antialias = source.Antialias;
+ this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
+ this.BlendPercentage = source.BlendPercentage;
+ this.ColorBlendingMode = source.ColorBlendingMode;
}
///
/// Gets or sets a value indicating whether antialiasing should be applied.
+ /// Defaults to true.
///
- public bool Antialias
- {
- get => this.antialias ?? true;
- set => this.antialias = value;
- }
+ public bool Antialias { get; set; } = true;
///
/// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
+ /// Defaults to 16.
///
public int AntialiasSubpixelDepth
{
- get => this.antialiasSubpixelDepth ?? 16;
- set => this.antialiasSubpixelDepth = value;
+ get
+ {
+ return this.antialiasSubpixelDepth;
+ }
+
+ set
+ {
+ Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
+ this.antialiasSubpixelDepth = value;
+ }
}
///
- /// Gets or sets a value indicating the blending percentage to apply to the drawing operation
+ /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation.
+ /// Range 0..1; Defaults to 1.
///
public float BlendPercentage
{
- get => (this.blendPercentage ?? 1).Clamp(0, 1);
- set => this.blendPercentage = value;
- }
+ get
+ {
+ return this.blendPercentage;
+ }
- // In the future we could expose a PixelBlender directly on here
- // or some forms of PixelBlender factory for each pixel type. Will need
- // some API thought post V1.
+ set
+ {
+ Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
+ this.blendPercentage = value;
+ }
+ }
///
- /// Gets or sets a value indicating the color blending mode to apply to the drawing operation
+ /// Gets or sets a value indicating the color blending mode to apply to the drawing operation.
+ /// Defaults to .
///
- public PixelColorBlendingMode ColorBlendingMode
- {
- get => this.colorBlendingMode;
- set => this.colorBlendingMode = value;
- }
+ public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
///
/// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation
+ /// Defaults to .
///
- public PixelAlphaCompositionMode AlphaCompositionMode
- {
- get => this.alphaCompositionMode;
- set => this.alphaCompositionMode = value;
- }
+ public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
- ///
- /// Evaluates if a given SOURCE color can completely replace a BACKDROP color given the current blending and composition settings.
- ///
- /// the color
- /// true if the color can be considered opaque
- ///
- /// Blending and composition is an expensive operation, in some cases, like
- /// filling with a solid color, the blending can be avoided by a plain color replacement.
- /// This method can be useful for such processors to select the fast path.
- ///
- internal bool IsOpaqueColorWithoutBlending(Color color)
- {
- if (this.ColorBlendingMode != PixelColorBlendingMode.Normal)
- {
- return false;
- }
-
- if (this.AlphaCompositionMode != PixelAlphaCompositionMode.SrcOver &&
- this.AlphaCompositionMode != PixelAlphaCompositionMode.Src)
- {
- return false;
- }
-
- if (this.BlendPercentage != 1f)
- {
- return false;
- }
-
- if (color.ToVector4().W != 1f)
- {
- return false;
- }
-
- return true;
- }
+ ///
+ public GraphicsOptions DeepClone() => new GraphicsOptions(this);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
index b596351b5f..a3fa0e1ff1 100644
--- a/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
+++ b/src/ImageSharp/Memory/MemoryAllocatorExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Buffers;
@@ -11,8 +11,18 @@ namespace SixLabors.ImageSharp.Memory
///
/// Extension methods for .
///
- internal static class MemoryAllocatorExtensions
+ public static class MemoryAllocatorExtensions
{
+ ///
+ /// Allocates a buffer of value type objects interpreted as a 2D region
+ /// of x elements.
+ ///
+ /// The type of buffer items to allocate.
+ /// The memory allocator.
+ /// The buffer width.
+ /// The buffer heght.
+ /// The allocation options.
+ /// The .
public static Buffer2D Allocate2D(
this MemoryAllocator memoryAllocator,
int width,
@@ -26,6 +36,15 @@ namespace SixLabors.ImageSharp.Memory
return new Buffer2D(memorySource, width, height);
}
+ ///
+ /// Allocates a buffer of value type objects interpreted as a 2D region
+ /// of width x height elements.
+ ///
+ /// The type of buffer items to allocate.
+ /// The memory allocator.
+ /// The buffer size.
+ /// The allocation options.
+ /// The .
public static Buffer2D Allocate2D(
this MemoryAllocator memoryAllocator,
Size size,
@@ -41,7 +60,7 @@ namespace SixLabors.ImageSharp.Memory
/// The pixel size in bytes, eg. 3 for RGB
/// The padding
/// A
- public static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
+ internal static IManagedByteBuffer AllocatePaddedPixelRowBuffer(
this MemoryAllocator memoryAllocator,
int width,
int pixelSizeInBytes,
@@ -51,4 +70,4 @@ namespace SixLabors.ImageSharp.Memory
return memoryAllocator.AllocateManagedByteBuffer(length);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
index 8603012321..459924c318 100644
--- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
+++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.Generated.tt
@@ -18,7 +18,7 @@
///
/// Converts all pixels in 'source` span of into a span of -s.
///
- /// A to configure internal operations
+ /// A to configure internal operations.
/// The source of data.
/// The to the destination pixels.
internal virtual void From<#=pixelType#>(Configuration configuration, ReadOnlySpan<<#=pixelType#>> source, Span destPixels)
@@ -41,7 +41,7 @@
/// A helper for that expects a byte span.
/// The layout of the data in 'sourceBytes' must be compatible with layout.
///
- /// A to configure internal operations
+ /// A to configure internal operations.
/// The to the source bytes.
/// The to the destination pixels.
/// The number of pixels to convert.
diff --git a/src/ImageSharp/Primitives/DenseMatrix{T}.cs b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
index 170292e29e..cc5e4a90a3 100644
--- a/src/ImageSharp/Primitives/DenseMatrix{T}.cs
+++ b/src/ImageSharp/Primitives/DenseMatrix{T}.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -98,9 +98,9 @@ namespace SixLabors.ImageSharp.Primitives
}
///
- /// Gets a Span wrapping the Data.
+ /// Gets a span wrapping the .
///
- internal Span Span => new Span(this.Data);
+ public Span Span => new Span(this.Data);
///
/// Gets or sets the item at the specified position.
@@ -222,4 +222,4 @@ namespace SixLabors.ImageSharp.Primitives
///
public override int GetHashCode() => this.Data.GetHashCode();
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs b/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs
index dd1cc1ed24..4241721f46 100644
--- a/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/BackgroundColorExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Overlays;
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// The color to set as the background.
/// The to allow chaining of operations.
public static IImageProcessingContext BackgroundColor(this IImageProcessingContext source, Color color) =>
- BackgroundColor(source, GraphicsOptions.Default, color);
+ BackgroundColor(source, new GraphicsOptions(), color);
///
/// Replaces the background color of image with the given one.
@@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
Color color,
Rectangle rectangle) =>
- BackgroundColor(source, GraphicsOptions.Default, color, rectangle);
+ BackgroundColor(source, new GraphicsOptions(), color, rectangle);
///
/// Replaces the background color of image with the given one.
@@ -47,7 +47,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
GraphicsOptions options,
Color color) =>
- source.ApplyProcessor(new BackgroundColorProcessor(color, options));
+ source.ApplyProcessor(new BackgroundColorProcessor(options, color));
///
/// Replaces the background color of image with the given one.
@@ -64,6 +64,6 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
Color color,
Rectangle rectangle) =>
- source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle);
+ source.ApplyProcessor(new BackgroundColorProcessor(options, color), rectangle);
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Extensions/GlowExtensions.cs b/src/ImageSharp/Processing/Extensions/GlowExtensions.cs
index 39734882b0..48ecb5108f 100644
--- a/src/ImageSharp/Processing/Extensions/GlowExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/GlowExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source) =>
- Glow(source, GraphicsOptions.Default);
+ Glow(source, new GraphicsOptions());
///
/// Applies a radial glow effect to an image.
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Processing
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, Color color)
{
- return Glow(source, GraphicsOptions.Default, color);
+ return Glow(source, new GraphicsOptions(), color);
}
///
@@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing
/// The the radius.
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, float radius) =>
- Glow(source, GraphicsOptions.Default, radius);
+ Glow(source, new GraphicsOptions(), radius);
///
/// Applies a radial glow effect to an image.
@@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Glow(this IImageProcessingContext source, Rectangle rectangle) =>
- source.Glow(GraphicsOptions.Default, rectangle);
+ source.Glow(new GraphicsOptions(), rectangle);
///
/// Applies a radial glow effect to an image.
@@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
float radius,
Rectangle rectangle) =>
- source.Glow(GraphicsOptions.Default, color, ValueSize.Absolute(radius), rectangle);
+ source.Glow(new GraphicsOptions(), color, ValueSize.Absolute(radius), rectangle);
///
/// Applies a radial glow effect to an image.
@@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Processing
Color color,
ValueSize radius,
Rectangle rectangle) =>
- source.ApplyProcessor(new GlowProcessor(color, radius, options), rectangle);
+ source.ApplyProcessor(new GlowProcessor(options, color, radius), rectangle);
///
/// Applies a radial glow effect to an image.
@@ -170,6 +170,6 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options,
Color color,
ValueSize radius) =>
- source.ApplyProcessor(new GlowProcessor(color, radius, options));
+ source.ApplyProcessor(new GlowProcessor(options, color, radius));
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs b/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs
index 74a59d3e13..a1f3a6e8a0 100644
--- a/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/VignetteExtensions.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
@@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Processing
/// The image this method extends.
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source) =>
- Vignette(source, GraphicsOptions.Default);
+ Vignette(source, new GraphicsOptions());
///
/// Applies a radial vignette effect to an image.
@@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Processing
/// The color to set as the vignette.
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Color color) =>
- Vignette(source, GraphicsOptions.Default, color);
+ Vignette(source, new GraphicsOptions(), color);
///
/// Applies a radial vignette effect to an image.
@@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Processing
this IImageProcessingContext source,
float radiusX,
float radiusY) =>
- Vignette(source, GraphicsOptions.Default, radiusX, radiusY);
+ Vignette(source, new GraphicsOptions(), radiusX, radiusY);
///
/// Applies a radial vignette effect to an image.
@@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing
///
/// The to allow chaining of operations.
public static IImageProcessingContext Vignette(this IImageProcessingContext source, Rectangle rectangle) =>
- Vignette(source, GraphicsOptions.Default, rectangle);
+ Vignette(source, new GraphicsOptions(), rectangle);
///
/// Applies a radial vignette effect to an image.
@@ -71,7 +71,7 @@ namespace SixLabors.ImageSharp.Processing
float radiusX,
float radiusY,
Rectangle rectangle) =>
- source.Vignette(GraphicsOptions.Default, color, radiusX, radiusY, rectangle);
+ source.Vignette(new GraphicsOptions(), color, radiusX, radiusY, rectangle);
///
/// Applies a radial vignette effect to an image.
@@ -166,7 +166,7 @@ namespace SixLabors.ImageSharp.Processing
ValueSize radiusX,
ValueSize radiusY,
Rectangle rectangle) =>
- source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options), rectangle);
+ source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY), rectangle);
private static IImageProcessingContext VignetteInternal(
this IImageProcessingContext source,
@@ -174,6 +174,6 @@ namespace SixLabors.ImageSharp.Processing
Color color,
ValueSize radiusX,
ValueSize radiusY) =>
- source.ApplyProcessor(new VignetteProcessor(color, radiusX, radiusY, options));
+ source.ApplyProcessor(new VignetteProcessor(options, color, radiusX, radiusY));
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
index f2f11cbfe5..622c133aeb 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
@@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
- int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
+ int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++;
}
}
@@ -366,7 +366,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
{
for (int idx = 0; idx < length; idx++)
{
- int luminance = GetLuminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
+ int luminance = ImageMaths.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)--;
}
}
diff --git a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
index 6e4c16de76..284b9de1f6 100644
--- a/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Normalization/HistogramEqualizationProcessor{TPixel}.cs
@@ -143,16 +143,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
public static int GetLuminance(TPixel sourcePixel, int luminanceLevels)
{
var vector = sourcePixel.ToVector4();
- return GetLuminance(ref vector, luminanceLevels);
+ return ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
}
-
- ///
- /// Convert the pixel values to grayscale using ITU-R Recommendation BT.709.
- ///
- /// The vector to get the luminance from
- /// The number of luminance levels (256 for 8 bit, 65536 for 16 bit grayscale images)
- [MethodImpl(InliningOptions.ShortMethod)]
- public static int GetLuminance(ref Vector4 vector, int luminanceLevels)
- => (int)MathF.Round(((.2126F * vector.X) + (.7152F * vector.Y) + (.0722F * vector.Y)) * (luminanceLevels - 1));
}
}
diff --git a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
index 4b4c537277..e78f7e5e75 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/BackgroundColorProcessor.cs
@@ -14,9 +14,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
/// Initializes a new instance of the class.
///
- /// The to set the background color to.
/// The options defining blending algorithm and amount.
- public BackgroundColorProcessor(Color color, GraphicsOptions options)
+ /// The to set the background color to.
+ public BackgroundColorProcessor(GraphicsOptions options, Color color)
{
this.Color = color;
this.GraphicsOptions = options;
diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
index 0958e3aa9e..4b9a23eff1 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs
@@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
/// Initializes a new instance of the class.
///
- /// The color or the glow.
/// The options effecting blending and composition.
- public GlowProcessor(Color color, GraphicsOptions options)
- : this(color, 0, options)
+ /// The color or the glow.
+ public GlowProcessor(GraphicsOptions options, Color color)
+ : this(options, color, 0)
{
}
@@ -37,17 +37,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
/// The color or the glow.
/// The radius of the glow.
internal GlowProcessor(Color color, ValueSize radius)
- : this(color, radius, GraphicsOptions.Default)
+ : this(new GraphicsOptions(), color, radius)
{
}
///
/// Initializes a new instance of the class.
///
+ /// The options effecting blending and composition.
/// The color or the glow.
/// The radius of the glow.
- /// The options effecting blending and composition.
- internal GlowProcessor(Color color, ValueSize radius, GraphicsOptions options)
+ internal GlowProcessor(GraphicsOptions options, Color color, ValueSize radius)
{
this.GlowColor = color;
this.Radius = radius;
@@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
/// Gets the the radius.
///
- internal ValueSize Radius { get; }
+ internal ValueSize Radius { get; }
///
public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle)
diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
index 2365318f3d..3cf48e5a40 100644
--- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs
@@ -17,16 +17,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
/// The color of the vignette.
public VignetteProcessor(Color color)
- : this(color, GraphicsOptions.Default)
+ : this(new GraphicsOptions(), color)
{
}
///
/// Initializes a new instance of the class.
///
- /// The color of the vignette.
/// The options effecting blending and composition.
- public VignetteProcessor(Color color, GraphicsOptions options)
+ /// The color of the vignette.
+ public VignetteProcessor(GraphicsOptions options, Color color)
{
this.VignetteColor = color;
this.GraphicsOptions = options;
@@ -35,11 +35,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Overlays
///
/// Initializes a new instance of the class.
///
+ /// The options effecting blending and composition.
/// The color of the vignette.
/// The x-radius.
/// The y-radius.
- /// The options effecting blending and composition.
- internal VignetteProcessor(Color color, ValueSize radiusX, ValueSize radiusY, GraphicsOptions options)
+ internal VignetteProcessor(GraphicsOptions options, Color color, ValueSize radiusX, ValueSize radiusY)
{
this.VignetteColor = color;
this.RadiusX = radiusX;
diff --git a/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
new file mode 100644
index 0000000000..e3c7216102
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/DecodeTga.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using BenchmarkDotNet.Attributes;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs
+{
+ [Config(typeof(Config.ShortClr))]
+ public class DecodeTga : BenchmarkBase
+ {
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Tga.Bit24)]
+ public string TestImage { get; set; }
+
+ [Benchmark(Baseline = true, Description = "ImageMagick Tga")]
+ public Size TgaImageMagick()
+ {
+ using (var magickImage = new MagickImage(this.TestImageFullPath))
+ {
+ return new Size(magickImage.Width, magickImage.Height);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Tga")]
+ public Size TgaCore()
+ {
+ using (var image = Image.Load(this.TestImageFullPath))
+ {
+ return new Size(image.Width, image.Height);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
index 157dadd2c1..7bd1b80447 100644
--- a/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodePng.cs
@@ -1,9 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
+using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
@@ -56,8 +57,9 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
using (var memoryStream = new MemoryStream())
{
- this.bmpCore.SaveAsPng(memoryStream);
+ var encoder = new PngEncoder { FilterMethod = PngFilterMethod.None };
+ this.bmpCore.SaveAsPng(memoryStream, encoder);
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
new file mode 100644
index 0000000000..ddcbec218e
--- /dev/null
+++ b/tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using BenchmarkDotNet.Attributes;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests;
+
+namespace SixLabors.ImageSharp.Benchmarks.Codecs
+{
+ [Config(typeof(Config.ShortClr))]
+ public class EncodeTga : BenchmarkBase
+ {
+ private MagickImage tgaMagick;
+ private Image tgaCore;
+
+ private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
+
+ [Params(TestImages.Tga.Bit24)]
+ public string TestImage { get; set; }
+
+ [GlobalSetup]
+ public void ReadImages()
+ {
+ if (this.tgaCore == null)
+ {
+ this.tgaCore = Image.Load(TestImageFullPath);
+ this.tgaMagick = new MagickImage(this.TestImageFullPath);
+ }
+ }
+
+ [Benchmark(Baseline = true, Description = "Magick Tga")]
+ public void BmpSystemDrawing()
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ this.tgaMagick.Write(memoryStream, MagickFormat.Tga);
+ }
+ }
+
+ [Benchmark(Description = "ImageSharp Tga")]
+ public void BmpCore()
+ {
+ using (var memoryStream = new MemoryStream())
+ {
+ this.tgaCore.SaveAsBmp(memoryStream);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs
index 0982db3340..c199613900 100644
--- a/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs
+++ b/tests/ImageSharp.Benchmarks/Drawing/DrawText.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
@@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks
graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
{
- graphics.DrawString(TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780));
+ graphics.DrawString(this.TextToRender, font, System.Drawing.Brushes.HotPink, new RectangleF(10, 10, 780, 780));
}
}
}
@@ -42,7 +42,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
- image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))));
+ image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10))));
}
}
@@ -52,7 +52,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
- image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)));
+ image.Mutate(x => DrawTextOldVersion(x, new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, Processing.Brushes.Solid(Rgba32.HotPink), null, new SixLabors.Primitives.PointF(10, 10)));
}
IImageProcessingContext DrawTextOldVersion(
@@ -93,4 +93,4 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs
index c5c1ba5ac1..7d8b776598 100644
--- a/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs
+++ b/tests/ImageSharp.Benchmarks/Drawing/DrawTextOutline.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
@@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks
[Params(10, 100)]
public int TextIterations { get; set; }
public string TextPhrase { get; set; } = "Hello World";
- public string TextToRender => string.Join(" ", Enumerable.Repeat(TextPhrase, TextIterations));
+ public string TextToRender => string.Join(" ", Enumerable.Repeat(this.TextPhrase, this.TextIterations));
[Benchmark(Baseline = true, Description = "System.Drawing Draw Text Outline")]
public void DrawTextSystemDrawing()
@@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var font = new Font("Arial", 12, GraphicsUnit.Point))
using (var gp = new GraphicsPath())
{
- gp.AddString(TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat());
+ gp.AddString(this.TextToRender, font.FontFamily, (int)font.Style, font.Size, new RectangleF(10, 10, 780, 780), new StringFormat());
graphics.DrawPath(pen, gp);
}
}
@@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Benchmarks
using (var image = new Image(800, 800))
{
var font = SixLabors.Fonts.SystemFonts.CreateFont("Arial", 12);
- image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions(true) { WrapTextWidth = 780 }, TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))));
+ image.Mutate(x => x.ApplyProcessor(new DrawTextProcessor(new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 }, this.TextToRender, font, null, Processing.Pens.Solid(Rgba32.HotPink, 10), new SixLabors.Primitives.PointF(10, 10))));
}
}
@@ -56,8 +56,8 @@ namespace SixLabors.ImageSharp.Benchmarks
image.Mutate(
x => DrawTextOldVersion(
x,
- new TextGraphicsOptions(true) { WrapTextWidth = 780 },
- TextToRender,
+ new TextGraphicsOptions { Antialias = true, WrapTextWidth = 780 },
+ this.TextToRender,
font,
null,
Processing.Pens.Solid(Rgba32.HotPink, 10),
@@ -99,4 +99,4 @@ namespace SixLabors.ImageSharp.Benchmarks
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index 14ad5635cd..a57d388a95 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -16,6 +16,7 @@
+
diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs
index a0e552aebf..6b35bbb972 100644
--- a/tests/ImageSharp.Tests/ConfigurationTests.cs
+++ b/tests/ImageSharp.Tests/ConfigurationTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -20,6 +20,8 @@ namespace SixLabors.ImageSharp.Tests
public Configuration ConfigurationEmpty { get; }
public Configuration DefaultConfiguration { get; }
+ private readonly int expectedDefaultConfigurationCount = 5;
+
public ConfigurationTests()
{
// the shallow copy of configuration should behave exactly like the default configuration,
@@ -108,14 +110,13 @@ namespace SixLabors.ImageSharp.Tests
[Fact]
public void ConfigurationCannotAddDuplicates()
{
- const int count = 4;
Configuration config = this.DefaultConfiguration;
- Assert.Equal(count, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
config.ImageFormatsManager.AddImageFormat(BmpFormat.Instance);
- Assert.Equal(count, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
@@ -123,7 +124,7 @@ namespace SixLabors.ImageSharp.Tests
{
Configuration config = Configuration.CreateDefaultInstance();
- Assert.Equal(4, config.ImageFormats.Count());
+ Assert.Equal(expectedDefaultConfigurationCount, config.ImageFormats.Count());
}
[Fact]
diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
index 86c1c28504..61b45729d3 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
@@ -190,7 +190,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
void Test()
{
- background.Mutate(context => context.DrawImage(overlay, new Point(x, y), GraphicsOptions.Default));
+ background.Mutate(context => context.DrawImage(overlay, new Point(x, y), new GraphicsOptions()));
}
}
}
diff --git a/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs
index 2836f8a38d..b45fc620b2 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawLinesTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -24,10 +24,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = new Pen(color, thickness);
-
+
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
-
+
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "White", 1f, 5, false)]
public void DrawLines_Dash(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias)
@@ -35,10 +35,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dash(color, thickness);
-
+
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
-
+
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "LightGreen", 1f, 5, false)]
public void DrawLines_Dot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias)
@@ -46,10 +46,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.Dot(color, thickness);
-
+
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
-
+
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, "Yellow", 1f, 5, false)]
public void DrawLines_DashDot(TestImageProvider provider, string colorName, float alpha, float thickness, bool antialias)
@@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDot(color, thickness);
-
+
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
@@ -68,11 +68,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
Pen pen = Pens.DashDotDot(color, thickness);
-
+
DrawLinesImpl(provider, colorName, alpha, thickness, antialias, pen);
}
-
+
private static void DrawLinesImpl(
TestImageProvider provider,
string colorName,
@@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
SixLabors.Primitives.PointF[] simplePath = { new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300) };
- GraphicsOptions options = new GraphicsOptions(antialias);
+ GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";
diff --git a/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs
index 18fde6ad8f..4a6cb430a8 100644
--- a/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/DrawPolygonTests.cs
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
- GraphicsOptions options = new GraphicsOptions(antialias);
+ GraphicsOptions options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A({alpha})_T({thickness}){aa}";
diff --git a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
index 361e7e70d1..031e732eaa 100644
--- a/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -16,6 +16,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+ using SixLabors.Primitives;
+ using SixLabors.Shapes;
[GroupOutput("Drawing/GradientBrushes")]
public class FillLinearGradientBrushTests
@@ -392,5 +394,44 @@ namespace SixLabors.ImageSharp.Tests.Drawing
false,
false);
}
+
+ [Theory]
+ [WithBlankImages(200, 200, PixelTypes.Rgba32)]
+ public void GradientsWithTransparencyOnExistingBackground(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.VerifyOperation(
+ image =>
+ {
+ image.Mutate(i => i.Fill(Color.Red));
+ image.Mutate(ApplyGloss);
+
+ });
+
+ void ApplyGloss(IImageProcessingContext ctx)
+ {
+ Size size = ctx.GetCurrentSize();
+ IPathCollection glossPath = BuildGloss(size.Width, size.Height);
+ var graphicsOptions = new GraphicsOptions
+ {
+ Antialias = true,
+ ColorBlendingMode = PixelColorBlendingMode.Normal,
+ AlphaCompositionMode = PixelAlphaCompositionMode.SrcAtop
+ };
+ var linearGradientBrush = new LinearGradientBrush(new Point(0, 0), new Point(0, size.Height / 2), GradientRepetitionMode.Repeat, new ColorStop(0, Color.White.WithAlpha(0.5f)), new ColorStop(1, Color.White.WithAlpha(0.25f)));
+ ctx.Fill(graphicsOptions, linearGradientBrush, glossPath);
+ }
+
+ IPathCollection BuildGloss(int imageWidth, int imageHeight)
+ {
+ var pathBuilder = new PathBuilder();
+ pathBuilder.AddLine(new PointF(0, 0), new PointF(imageWidth, 0));
+ pathBuilder.AddLine(new PointF(imageWidth, 0), new PointF(imageWidth, imageHeight * 0.4f));
+ pathBuilder.AddBezier(new PointF(imageWidth, imageHeight * 0.4f), new PointF(imageWidth / 2, imageHeight * 0.6f), new PointF(0, imageHeight * 0.4f));
+ pathBuilder.CloseFigure();
+ return new PathCollection(pathBuilder.Build());
+ }
+ }
+
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
index 104237ec3e..22294e76df 100644
--- a/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillPolygonTests.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
};
Color color = TestUtils.GetColorByName(colorName).WithAlpha(alpha);
- var options = new GraphicsOptions(antialias);
+ var options = new GraphicsOptions { Antialias = antialias };
string aa = antialias ? "" : "_NoAntialias";
FormattableString outputDetails = $"{colorName}_A{alpha}{aa}";
diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
index c0388ea2d4..e259d29d9c 100644
--- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
@@ -16,8 +16,6 @@ using SixLabors.Shapes;
namespace SixLabors.ImageSharp.Tests.Drawing
{
-
-
public class FillRegionProcessorTests
{
@@ -35,11 +33,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
var brush = new Mock();
var region = new MockRegion2(bounds);
- var options = new GraphicsOptions(antialias)
+ var options = new GraphicsOptions
{
+ Antialias = antialias,
AntialiasSubpixelDepth = 1
};
- var processor = new FillRegionProcessor(brush.Object, region, options);
+ var processor = new FillRegionProcessor(options, brush.Object, region);
var img = new Image(1, 1);
processor.Execute(img, bounds);
@@ -51,8 +50,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
var bounds = new Rectangle(-100, -10, 10, 10);
var brush = new Mock();
- var options = new GraphicsOptions(true);
- var processor = new FillRegionProcessor(brush.Object, new MockRegion1(), options);
+ var options = new GraphicsOptions { Antialias = true };
+ var processor = new FillRegionProcessor(options, brush.Object, new MockRegion1());
var img = new Image(10, 10);
processor.Execute(img, bounds);
}
@@ -73,11 +72,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
public void DoesNotThrowForIssue928()
{
var rectText = new RectangleF(0, 0, 2000, 2000);
- using (Image img = new Image((int)rectText.Width, (int)rectText.Height))
+ using (var img = new Image((int)rectText.Width, (int)rectText.Height))
{
img.Mutate(x => x.Fill(Rgba32.Transparent));
- img.Mutate(ctx => {
+ img.Mutate(ctx =>
+ {
ctx.DrawLines(
Rgba32.Red,
0.984252f,
@@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new PointF(104.782608f, 1075.13245f),
new PointF(104.782608f, 1075.13245f)
);
- }
+ }
);
}
}
@@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
[Fact]
public void DoesNotThrowFillingTriangle()
{
- using(var image = new Image(28, 28))
+ using (var image = new Image(28, 28))
{
var path = new Polygon(
new LinearLineSegment(new PointF(17.11f, 13.99659f), new PointF(14.01433f, 27.06201f)),
diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
index a5e7450839..1e3688fead 100644
--- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
@@ -156,10 +156,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
{
TPixel bgColor = image[0, 0];
- var options = new GraphicsOptions(false)
- {
- ColorBlendingMode = blenderMode, BlendPercentage = blendPercentage
- };
+ var options = new GraphicsOptions
+ {
+ Antialias = false,
+ ColorBlendingMode = blenderMode,
+ BlendPercentage = blendPercentage
+ };
if (triggerFillRegion)
{
@@ -173,13 +175,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}
var testOutputDetails = new
- {
- triggerFillRegion = triggerFillRegion,
- newColorName = newColorName,
- alpha = alpha,
- blenderMode = blenderMode,
- blendPercentage = blendPercentage
- };
+ {
+ triggerFillRegion = triggerFillRegion,
+ newColorName = newColorName,
+ alpha = alpha,
+ blenderMode = blenderMode,
+ blendPercentage = blendPercentage
+ };
image.DebugSave(
provider,
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
index 3691b54ce3..36c11035c6 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/DrawPathCollection.cs
@@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class DrawPathCollection : BaseImageOperationsExtensionTest
{
- GraphicsOptions noneDefault = new GraphicsOptions();
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
+ GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
Pen pen = Pens.Solid(Rgba32.HotPink, 1);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@@ -46,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType(processor.Region);
@@ -60,13 +63,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
- this.operations.Draw(this.noneDefault, this.pen, this.pathCollection);
+ this.operations.Draw(this.nonDefault, this.pen, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType(processor.Region);
Assert.IsType(region.Shape);
@@ -84,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType(processor.Region);
Assert.IsType(region.Shape);
@@ -97,13 +100,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
- this.operations.Draw(this.noneDefault, this.color, 1, this.pathCollection);
+ this.operations.Draw(this.nonDefault, this.color, 1, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapePath region = Assert.IsType(processor.Region);
Assert.IsType(region.Shape);
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
index 160ff22a3e..cea59e15e5 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPath.cs
@@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPath : BaseImageOperationsExtensionTest
{
- GraphicsOptions noneDefault = new GraphicsOptions();
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
+ GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@@ -30,7 +33,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.brush, this.path);
var processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
@@ -44,10 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
- this.operations.Fill(this.noneDefault, this.brush, this.path);
+ this.operations.Fill(this.nonDefault, this.brush, this.path);
var processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -62,7 +65,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.color, this.path);
var processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -75,10 +78,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
- this.operations.Fill(this.noneDefault, this.color, this.path);
+ this.operations.Fill(this.nonDefault, this.color, this.path);
var processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs
index b76ee8ffcd..2a9c04a89f 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs
@@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPathCollection : BaseImageOperationsExtensionTest
{
- GraphicsOptions noneDefault = new GraphicsOptions();
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
+ GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
IPath path1 = new Path(new LinearLineSegment(new SixLabors.Primitives.PointF[] {
@@ -46,7 +49,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
@@ -61,13 +64,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathOptions()
{
- this.operations.Fill(this.noneDefault, this.brush, this.pathCollection);
+ this.operations.Fill(this.nonDefault, this.brush, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -86,7 +89,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -100,13 +103,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
- this.operations.Fill(this.noneDefault, this.color, this.pathCollection);
+ this.operations.Fill(this.nonDefault, this.color, this.pathCollection);
for (int i = 0; i < 2; i++)
{
FillRegionProcessor processor = this.Verify(i);
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs
index c62a871481..8dacd1e7f6 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPolygon.cs
@@ -7,6 +7,7 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Shapes;
using Xunit;
@@ -14,7 +15,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillPolygon : BaseImageOperationsExtensionTest
{
- GraphicsOptions noneDefault = new GraphicsOptions();
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
+ GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
Color color = Color.HotPink;
SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
SixLabors.Primitives.PointF[] path = {
@@ -32,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
FillRegionProcessor processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -44,10 +47,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushPathAndOptions()
{
- this.operations.FillPolygon(this.noneDefault, this.brush, this.path);
+ this.operations.FillPolygon(this.nonDefault, this.brush, this.path);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -63,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
FillRegionProcessor processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
@@ -76,10 +79,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorPathAndOptions()
{
- this.operations.FillPolygon(this.noneDefault, this.color, this.path);
+ this.operations.FillPolygon(this.nonDefault, this.color, this.path);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Polygon polygon = Assert.IsType(region.Shape);
diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
index 17a2b87c0e..6b08323b68 100644
--- a/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
+++ b/tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
@@ -6,17 +6,19 @@ using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.ImageSharp.Tests.Processing;
-
+using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillRectangle : BaseImageOperationsExtensionTest
{
- GraphicsOptions noneDefault = new GraphicsOptions();
- Color color = Color.HotPink;
- SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
- SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76);
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
+ private GraphicsOptions nonDefault = new GraphicsOptions { Antialias = false };
+ private Color color = Color.HotPink;
+ private SolidBrush brush = Brushes.Solid(Rgba32.HotPink);
+ private SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76);
[Fact]
public void CorrectlySetsBrushAndRectangle()
@@ -24,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.brush, this.rectangle);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType(region.Shape);
@@ -39,10 +41,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsBrushRectangleAndOptions()
{
- this.operations.Fill(this.noneDefault, this.brush, this.rectangle);
+ this.operations.Fill(this.nonDefault, this.brush, this.rectangle);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType(region.Shape);
@@ -60,7 +62,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
this.operations.Fill(this.color, this.rectangle);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.Options);
+ Assert.Equal(new GraphicsOptions(), processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType(region.Shape);
@@ -76,10 +78,10 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
[Fact]
public void CorrectlySetsColorRectangleAndOptions()
{
- this.operations.Fill(this.noneDefault, this.color, this.rectangle);
+ this.operations.Fill(this.nonDefault, this.color, this.rectangle);
FillRegionProcessor processor = this.Verify();
- Assert.Equal(this.noneDefault, processor.Options);
+ Assert.Equal(this.nonDefault, processor.Options, graphicsOptionsComparer);
ShapeRegion region = Assert.IsType(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType(region.Shape);
diff --git a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
index da7c865b96..f1a62cf292 100644
--- a/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}
}
}
-
+
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
@@ -46,7 +46,8 @@ namespace SixLabors.ImageSharp.Tests.Drawing
Color.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)
)
- .Fill(new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode=composition },
+ .Fill(
+ new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))
);
@@ -73,12 +74,12 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
- new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
+ new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(
x => x.Fill(
- new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
+ new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.Transparent,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
@@ -105,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
- new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
+ new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
Color.HotPink,
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
@@ -113,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
img.Mutate(
x => x.Fill(
- new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition },
+ new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition },
transparentRed,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
@@ -130,7 +131,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
PixelAlphaCompositionMode composition)
where TPixel : struct, IPixel
{
- using(Image dstImg = provider.GetImage(), srcImg = provider.GetImage())
+ using (Image dstImg = provider.GetImage(), srcImg = provider.GetImage())
{
int scaleX = (dstImg.Width / 100);
int scaleY = (dstImg.Height / 100);
@@ -146,13 +147,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
dstImg.Mutate(
- x => x.DrawImage(srcImg, new GraphicsOptions(true) { ColorBlendingMode = blending, AlphaCompositionMode = composition })
- );
+ x => x.DrawImage(srcImg, new GraphicsOptions { Antialias = true, ColorBlendingMode = blending, AlphaCompositionMode = composition })
+ );
VerifyImage(provider, blending, composition, dstImg);
}
}
-
+
private static void VerifyImage(
TestImageProvider provider,
PixelColorBlendingMode blending,
@@ -165,13 +166,13 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new { composition, blending },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
-
+
var comparer = ImageComparer.TolerantPercentage(0.01f, 3);
img.CompareFirstFrameToReferenceOutput(comparer,
provider,
new { composition, blending },
appendPixelTypeToFileName: false,
- appendSourceFileOrDescription: false);
+ appendSourceFileOrDescription: false);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
index e6866c6579..2a39e18cb6 100644
--- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
+++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs
@@ -27,7 +27,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
public void FillsForEachACharacterWhenBrushSetAndNotPen()
{
this.operations.DrawText(
- new TextGraphicsOptions(true),
+ new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
Brushes.Solid(Color.Red),
@@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
[Fact]
public void FillsForEachACharacterWhenBrushSet()
{
- this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero);
+ this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero);
this.Verify(0);
}
@@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
[Fact]
public void FillsForEachACharacterWhenColorSet()
{
- this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Color.Red, Vector2.Zero);
+ this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Color.Red, Vector2.Zero);
var processor = this.Verify(0);
@@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
public void DrawForEachACharacterWhenPenSetAndNotBrush()
{
this.operations.DrawText(
- new TextGraphicsOptions(true),
+ new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
null,
@@ -108,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
[Fact]
public void DrawForEachACharacterWhenPenSet()
{
- this.operations.DrawText(new TextGraphicsOptions(true), "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero);
+ this.operations.DrawText(new TextGraphicsOptions { Antialias = true }, "123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero);
this.Verify(0);
}
@@ -132,7 +132,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
public void DrawForEachACharacterWhenPenSetAndFillFroEachWhenBrushSet()
{
this.operations.DrawText(
- new TextGraphicsOptions(true),
+ new TextGraphicsOptions { Antialias = true },
"123",
this.Font,
Brushes.Solid(Color.Red),
diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
index a767a686ed..281a516509 100644
--- a/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
@@ -55,8 +55,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
var scaledFont = new Font(font, scalingFactor * font.Size);
var center = new PointF(img.Width / 2, img.Height / 2);
- var textGraphicOptions = new TextGraphicsOptions(true)
+ var textGraphicOptions = new TextGraphicsOptions
{
+ Antialias = true,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
@@ -222,7 +223,11 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
string text = Repeat("Beware the Jabberwock, my son! The jaws that bite, the claws that catch! Beware the Jubjub bird, and shun The frumious Bandersnatch!\n",
20);
- var textOptions = new TextGraphicsOptions(true) { WrapTextWidth = 1000 };
+ var textOptions = new TextGraphicsOptions
+ {
+ Antialias = true,
+ WrapTextWidth = 1000
+ };
string details = fontName.Replace(" ", "");
diff --git a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs
index 0885611c67..a59afb271d 100644
--- a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs
+++ b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs
@@ -1,6 +1,8 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using SixLabors.Fonts;
+using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
@@ -9,16 +11,184 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
{
public class TextGraphicsOptionsTests
{
+ private readonly TextGraphicsOptions newTextGraphicsOptions = new TextGraphicsOptions();
+ private readonly TextGraphicsOptions cloneTextGraphicsOptions = new TextGraphicsOptions().DeepClone();
+
+ [Fact]
+ public void CloneTextGraphicsOptionsIsNotNull() => Assert.True(this.cloneTextGraphicsOptions != null);
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsAntialias()
+ {
+ Assert.True(this.newTextGraphicsOptions.Antialias);
+ Assert.True(this.cloneTextGraphicsOptions.Antialias);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsAntialiasSuppixelDepth()
+ {
+ const int Expected = 16;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.AntialiasSubpixelDepth);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.AntialiasSubpixelDepth);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsBlendPercentage()
+ {
+ const float Expected = 1F;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.BlendPercentage);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.BlendPercentage);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsColorBlendingMode()
+ {
+ const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.ColorBlendingMode);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.ColorBlendingMode);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsAlphaCompositionMode()
+ {
+ const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.AlphaCompositionMode);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.AlphaCompositionMode);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsApplyKerning()
+ {
+ const bool Expected = true;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.ApplyKerning);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.ApplyKerning);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsHorizontalAlignment()
+ {
+ const HorizontalAlignment Expected = HorizontalAlignment.Left;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.HorizontalAlignment);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.HorizontalAlignment);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsVerticalAlignment()
+ {
+ const VerticalAlignment Expected = VerticalAlignment.Top;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.VerticalAlignment);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.VerticalAlignment);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsDpiX()
+ {
+ const float Expected = 72F;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.DpiX);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiX);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsDpiY()
+ {
+ const float Expected = 72F;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.DpiY);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.DpiY);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsTabWidth()
+ {
+ const float Expected = 4F;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.TabWidth);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.TabWidth);
+ }
+
+ [Fact]
+ public void DefaultTextGraphicsOptionsWrapTextWidth()
+ {
+ const float Expected = 0F;
+ Assert.Equal(Expected, this.newTextGraphicsOptions.WrapTextWidth);
+ Assert.Equal(Expected, this.cloneTextGraphicsOptions.WrapTextWidth);
+ }
+
+ [Fact]
+ public void NonDefaultClone()
+ {
+ var expected = new TextGraphicsOptions
+ {
+ AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop,
+ Antialias = false,
+ AntialiasSubpixelDepth = 23,
+ ApplyKerning = false,
+ BlendPercentage = .25F,
+ ColorBlendingMode = PixelColorBlendingMode.HardLight,
+ DpiX = 46F,
+ DpiY = 52F,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ TabWidth = 3F,
+ VerticalAlignment = VerticalAlignment.Bottom,
+ WrapTextWidth = 42F
+ };
+
+ TextGraphicsOptions actual = expected.DeepClone();
+
+ Assert.Equal(expected.AlphaCompositionMode, actual.AlphaCompositionMode);
+ Assert.Equal(expected.Antialias, actual.Antialias);
+ Assert.Equal(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth);
+ Assert.Equal(expected.ApplyKerning, actual.ApplyKerning);
+ Assert.Equal(expected.BlendPercentage, actual.BlendPercentage);
+ Assert.Equal(expected.ColorBlendingMode, actual.ColorBlendingMode);
+ Assert.Equal(expected.DpiX, actual.DpiX);
+ Assert.Equal(expected.DpiY, actual.DpiY);
+ Assert.Equal(expected.HorizontalAlignment, actual.HorizontalAlignment);
+ Assert.Equal(expected.TabWidth, actual.TabWidth);
+ Assert.Equal(expected.VerticalAlignment, actual.VerticalAlignment);
+ Assert.Equal(expected.WrapTextWidth, actual.WrapTextWidth);
+ }
+
+ [Fact]
+ public void CloneIsDeep()
+ {
+ var expected = new TextGraphicsOptions();
+ TextGraphicsOptions actual = expected.DeepClone();
+
+ actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop;
+ actual.Antialias = false;
+ actual.AntialiasSubpixelDepth = 23;
+ actual.ApplyKerning = false;
+ actual.BlendPercentage = .25F;
+ actual.ColorBlendingMode = PixelColorBlendingMode.HardLight;
+ actual.DpiX = 46F;
+ actual.DpiY = 52F;
+ actual.HorizontalAlignment = HorizontalAlignment.Center;
+ actual.TabWidth = 3F;
+ actual.VerticalAlignment = VerticalAlignment.Bottom;
+ actual.WrapTextWidth = 42F;
+
+ Assert.NotEqual(expected.AlphaCompositionMode, actual.AlphaCompositionMode);
+ Assert.NotEqual(expected.Antialias, actual.Antialias);
+ Assert.NotEqual(expected.AntialiasSubpixelDepth, actual.AntialiasSubpixelDepth);
+ Assert.NotEqual(expected.ApplyKerning, actual.ApplyKerning);
+ Assert.NotEqual(expected.BlendPercentage, actual.BlendPercentage);
+ Assert.NotEqual(expected.ColorBlendingMode, actual.ColorBlendingMode);
+ Assert.NotEqual(expected.DpiX, actual.DpiX);
+ Assert.NotEqual(expected.DpiY, actual.DpiY);
+ Assert.NotEqual(expected.HorizontalAlignment, actual.HorizontalAlignment);
+ Assert.NotEqual(expected.TabWidth, actual.TabWidth);
+ Assert.NotEqual(expected.VerticalAlignment, actual.VerticalAlignment);
+ Assert.NotEqual(expected.WrapTextWidth, actual.WrapTextWidth);
+ }
+
[Fact]
public void ExplicitCastOfGraphicsOptions()
{
- var opt = new GraphicsOptions(false)
+ TextGraphicsOptions textOptions = new GraphicsOptions
{
+ Antialias = false,
AntialiasSubpixelDepth = 99
};
- TextGraphicsOptions textOptions = opt;
-
Assert.False(textOptions.Antialias);
Assert.Equal(99, textOptions.AntialiasSubpixelDepth);
}
@@ -26,8 +196,9 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
[Fact]
public void ImplicitCastToGraphicsOptions()
{
- var textOptions = new TextGraphicsOptions(false)
+ var textOptions = new TextGraphicsOptions
{
+ Antialias = false,
AntialiasSubpixelDepth = 99
};
@@ -37,4 +208,4 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Text
Assert.Equal(99, opt.AntialiasSubpixelDepth);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
index 25cf29406e..4c3fe31493 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpFileHeaderTests.cs
@@ -1,4 +1,11 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
using System;
+using System.IO;
+using System.Linq;
+
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp;
using Xunit;
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
index e976d5a768..660d5b7246 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.Chunks.cs
@@ -54,7 +54,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
[InlineData((uint)PngChunkType.Header)] // IHDR
[InlineData((uint)PngChunkType.Palette)] // PLTE
// [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this
- [InlineData((uint)PngChunkType.End)] // IEND
public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(uint chunkType)
{
string chunkName = GetChunkTypeName(chunkType);
@@ -74,26 +73,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
}
}
- [Theory]
- [InlineData((uint)PngChunkType.Gamma)] // gAMA
- [InlineData((uint)PngChunkType.Transparency)] // tRNS
- [InlineData((uint)PngChunkType.Physical)] // pHYs: It's ok to test physical as we don't throw for duplicate chunks.
- //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this
- public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(uint chunkType)
- {
- string chunkName = GetChunkTypeName(chunkType);
-
- using (var memStream = new MemoryStream())
- {
- WriteHeaderChunk(memStream);
- WriteChunk(memStream, chunkName);
- WriteDataChunk(memStream);
-
- var decoder = new PngDecoder();
- decoder.Decode(null, memStream);
- }
- }
-
private static string GetChunkTypeName(uint value)
{
var data = new byte[4];
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 2a76310fcd..bdd84038e3 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
@@ -4,11 +4,11 @@
// ReSharper disable InconsistentNaming
using System.IO;
-
+using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
-
+using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
@@ -42,6 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
TestImages.Png.Bad.ZlibOverflow,
TestImages.Png.Bad.ZlibOverflow2,
TestImages.Png.Bad.ZlibZtxtBadHeader,
+ TestImages.Png.Bad.Issue1047_BadEndChunk
};
public static readonly string[] TestImages48Bpp =
@@ -90,7 +91,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image image = provider.GetImage(new PngDecoder()))
{
image.DebugSave(provider);
- image.CompareToOriginal(provider, ImageComparer.Exact);
+
+ // We don't have another x-plat reference decoder that can be compared for this image.
+ if (provider.Utility.SourceFileOrDescription == TestImages.Png.Bad.Issue1047_BadEndChunk)
+ {
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider, ImageComparer.Exact, (IImageDecoder)SystemDrawingReferenceDecoder.Instance);
+ }
+ }
+ else
+ {
+ image.CompareToOriginal(provider, ImageComparer.Exact);
+ }
}
}
@@ -218,7 +231,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
using (Image image = provider.GetImage(new PngDecoder()))
{
image.DebugSave(provider);
- // TODO: compare to expected output
+ image.CompareToOriginal(provider, ImageComparer.Exact);
}
});
Assert.Null(ex);
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 2584391bb7..8a0cdbfbaf 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
///
public static readonly TheoryData CompressionLevels = new TheoryData
{
- 1, 2, 3, 4, 5, 6, 7, 8, 9
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
};
public static readonly TheoryData PaletteSizes = new TheoryData
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
new file mode 100644
index 0000000000..03ad10de40
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.PixelFormats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ using static TestImages.Tga;
+
+ public class TgaDecoderTests
+ {
+ [Theory]
+ [WithFile(Grey, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_MonoChrome(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit15, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_15Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit15Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_15Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16PalRle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithPalette_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24RleTopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_WithTopLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24TopLeft, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Palette_WithTopLeftOrigin_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_Uncompressed_32Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(GreyRle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_MonoChrome(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32Rle, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_RunLengthEncoded_32Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16Pal, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithPalette_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit24Pal, PixelTypes.Rgba32)]
+ public void TgaDecoder_CanDecode_WithPalette_24Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new TgaDecoder()))
+ {
+ image.DebugSave(provider);
+ TgaTestUtils.CompareWithReferenceDecoder(provider, image);
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
new file mode 100644
index 0000000000..e946729a15
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+// ReSharper disable InconsistentNaming
+
+using System.IO;
+
+using SixLabors.ImageSharp.Formats.Tga;
+using SixLabors.ImageSharp.PixelFormats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ using static TestImages.Tga;
+
+ public class TgaEncoderTests
+ {
+ public static readonly TheoryData BitsPerPixel =
+ new TheoryData
+ {
+ TgaBitsPerPixel.Pixel24,
+ TgaBitsPerPixel.Pixel32
+ };
+
+ public static readonly TheoryData TgaBitsPerPixelFiles =
+ new TheoryData
+ {
+ { Grey, TgaBitsPerPixel.Pixel8 },
+ { Bit32, TgaBitsPerPixel.Pixel32 },
+ { Bit24, TgaBitsPerPixel.Pixel24 },
+ { Bit16, TgaBitsPerPixel.Pixel16 },
+ };
+
+ [Theory]
+ [MemberData(nameof(TgaBitsPerPixelFiles))]
+ public void Encode_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
+ {
+ var options = new TgaEncoder();
+
+ TestFile testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateRgba32Image())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+ memStream.Position = 0;
+ using (Image output = Image.Load(memStream))
+ {
+ TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance);
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(TgaBitsPerPixelFiles))]
+ public void Encode_WithCompression_PreserveBitsPerPixel(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
+ {
+ var options = new TgaEncoder()
+ {
+ Compression = TgaCompression.RunLength
+ };
+
+ TestFile testFile = TestFile.Create(imagePath);
+ using (Image input = testFile.CreateRgba32Image())
+ {
+ using (var memStream = new MemoryStream())
+ {
+ input.Save(memStream, options);
+ memStream.Position = 0;
+ using (Image output = Image.Load(memStream))
+ {
+ TgaMetadata meta = output.Metadata.GetFormatMetadata(TgaFormat.Instance);
+ Assert.Equal(bmpBitsPerPixel, meta.BitsPerPixel);
+ }
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit8_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+ // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false, compareTolerance: 0.03f);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit16_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None, useExactComparer: false);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit24_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit32_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.None);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit8_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel8)
+ // using tolerant comparer here. The results from magick differ slightly. Maybe a different ToGrey method is used. The image looks otherwise ok.
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false, compareTolerance: 0.03f);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit16_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel16)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength, useExactComparer: false);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit24_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel24)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
+
+ [Theory]
+ [WithFile(Bit32, PixelTypes.Rgba32)]
+ public void Encode_Bit32_WithRunLengthEncoding_Works(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel = TgaBitsPerPixel.Pixel32)
+ where TPixel : struct, IPixel => TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength);
+
+ private static void TestTgaEncoderCore(
+ TestImageProvider provider,
+ TgaBitsPerPixel bitsPerPixel,
+ TgaCompression compression = TgaCompression.None,
+ bool useExactComparer = true,
+ float compareTolerance = 0.01f)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ {
+ var encoder = new TgaEncoder { BitsPerPixel = bitsPerPixel, Compression = compression};
+
+ using (var memStream = new MemoryStream())
+ {
+ image.Save(memStream, encoder);
+ memStream.Position = 0;
+ using (var encodedImage = (Image)Image.Load(memStream))
+ {
+ TgaTestUtils.CompareWithReferenceDecoder(provider, encodedImage, useExactComparer, compareTolerance);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
new file mode 100644
index 0000000000..c227b79576
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaFileHeaderTests.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using SixLabors.ImageSharp.Formats;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ public class TgaFileHeaderTests
+ {
+ private static readonly byte[] Data = {
+ 0,
+ 0,
+ 15 // invalid tga image type
+ };
+
+ private MemoryStream Stream { get; } = new MemoryStream(Data);
+
+ [Fact]
+ public void ImageLoad_WithInvalidImageType_Throws_UnknownImageFormatException()
+ {
+ Assert.Throws(() =>
+ {
+ using (Image.Load(Configuration.Default, this.Stream, out IImageFormat _))
+ {
+ }
+ });
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
new file mode 100644
index 0000000000..a2f2e86d7d
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Tga/TgaTestUtils.cs
@@ -0,0 +1,61 @@
+using System;
+using System.IO;
+
+using ImageMagick;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Tga
+{
+ public static class TgaTestUtils
+ {
+ public static void CompareWithReferenceDecoder(TestImageProvider provider,
+ Image image,
+ bool useExactComparer = true,
+ float compareTolerance = 0.01f)
+ where TPixel : struct, IPixel
+ {
+ string path = TestImageProvider.GetFilePathOrNull(provider);
+ if (path == null)
+ {
+ throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
+ }
+
+ TestFile testFile = TestFile.Create(path);
+ Image magickImage = DecodeWithMagick(Configuration.Default, new FileInfo(testFile.FullPath));
+ if (useExactComparer)
+ {
+ ImageComparer.Exact.VerifySimilarity(magickImage, image);
+ }
+ else
+ {
+ ImageComparer.Tolerant(compareTolerance).VerifySimilarity(magickImage, image);
+ }
+ }
+
+ public static Image DecodeWithMagick(Configuration configuration, FileInfo fileInfo)
+ where TPixel : struct, IPixel
+ {
+ using (var magickImage = new MagickImage(fileInfo))
+ {
+ var result = new Image(configuration, magickImage.Width, magickImage.Height);
+ Span resultPixels = result.GetPixelSpan();
+
+ using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
+ {
+ byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
+
+ PixelOperations.Instance.FromRgba32Bytes(
+ configuration,
+ data,
+ resultPixels,
+ resultPixels.Length);
+ }
+
+ return result;
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs
index 6ff38626d6..69f904f1cb 100644
--- a/tests/ImageSharp.Tests/GraphicsOptionsTests.cs
+++ b/tests/ImageSharp.Tests/GraphicsOptionsTests.cs
@@ -1,21 +1,100 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public class GraphicsOptionsTests
{
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+ private readonly GraphicsOptions newGraphicsOptions = new GraphicsOptions();
+ private readonly GraphicsOptions cloneGraphicsOptions = new GraphicsOptions().DeepClone();
+
+ [Fact]
+ public void CloneGraphicsOptionsIsNotNull() => Assert.True(this.cloneGraphicsOptions != null);
+
+ [Fact]
+ public void DefaultGraphicsOptionsAntialias()
+ {
+ Assert.True(this.newGraphicsOptions.Antialias);
+ Assert.True(this.cloneGraphicsOptions.Antialias);
+ }
+
+ [Fact]
+ public void DefaultGraphicsOptionsAntialiasSuppixelDepth()
+ {
+ const int Expected = 16;
+ Assert.Equal(Expected, this.newGraphicsOptions.AntialiasSubpixelDepth);
+ Assert.Equal(Expected, this.cloneGraphicsOptions.AntialiasSubpixelDepth);
+ }
+
+ [Fact]
+ public void DefaultGraphicsOptionsBlendPercentage()
+ {
+ const float Expected = 1F;
+ Assert.Equal(Expected, this.newGraphicsOptions.BlendPercentage);
+ Assert.Equal(Expected, this.cloneGraphicsOptions.BlendPercentage);
+ }
+
+ [Fact]
+ public void DefaultGraphicsOptionsColorBlendingMode()
+ {
+ const PixelColorBlendingMode Expected = PixelColorBlendingMode.Normal;
+ Assert.Equal(Expected, this.newGraphicsOptions.ColorBlendingMode);
+ Assert.Equal(Expected, this.cloneGraphicsOptions.ColorBlendingMode);
+ }
+
+ [Fact]
+ public void DefaultGraphicsOptionsAlphaCompositionMode()
+ {
+ const PixelAlphaCompositionMode Expected = PixelAlphaCompositionMode.SrcOver;
+ Assert.Equal(Expected, this.newGraphicsOptions.AlphaCompositionMode);
+ Assert.Equal(Expected, this.cloneGraphicsOptions.AlphaCompositionMode);
+ }
+
+ [Fact]
+ public void NonDefaultClone()
+ {
+ var expected = new GraphicsOptions
+ {
+ AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop,
+ Antialias = false,
+ AntialiasSubpixelDepth = 23,
+ BlendPercentage = .25F,
+ ColorBlendingMode = PixelColorBlendingMode.HardLight,
+ };
+
+ GraphicsOptions actual = expected.DeepClone();
+
+ Assert.Equal(expected, actual, graphicsOptionsComparer);
+ }
+
+ [Fact]
+ public void CloneIsDeep()
+ {
+ var expected = new GraphicsOptions();
+ GraphicsOptions actual = expected.DeepClone();
+
+ actual.AlphaCompositionMode = PixelAlphaCompositionMode.DestAtop;
+ actual.Antialias = false;
+ actual.AntialiasSubpixelDepth = 23;
+ actual.BlendPercentage = .25F;
+ actual.ColorBlendingMode = PixelColorBlendingMode.HardLight;
+
+ Assert.NotEqual(expected, actual, graphicsOptionsComparer);
+ }
+
[Fact]
public void IsOpaqueColor()
{
- Assert.True(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Red));
- Assert.False(new GraphicsOptions(true, 0.5f).IsOpaqueColorWithoutBlending(Rgba32.Red));
- Assert.False(new GraphicsOptions(true).IsOpaqueColorWithoutBlending(Rgba32.Transparent));
- Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Lighten, 1).IsOpaqueColorWithoutBlending(Rgba32.Red));
- Assert.False(new GraphicsOptions(true, PixelColorBlendingMode.Normal, PixelAlphaCompositionMode.DestOver, 1).IsOpaqueColorWithoutBlending(Rgba32.Red));
+ Assert.True(new GraphicsOptions().IsOpaqueColorWithoutBlending(Rgba32.Red));
+ Assert.False(new GraphicsOptions { BlendPercentage = .5F }.IsOpaqueColorWithoutBlending(Rgba32.Red));
+ Assert.False(new GraphicsOptions().IsOpaqueColorWithoutBlending(Rgba32.Transparent));
+ Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Lighten, BlendPercentage = 1F }.IsOpaqueColorWithoutBlending(Rgba32.Red));
+ Assert.False(new GraphicsOptions { ColorBlendingMode = PixelColorBlendingMode.Normal, AlphaCompositionMode = PixelAlphaCompositionMode.DestOver, BlendPercentage = 1f }.IsOpaqueColorWithoutBlending(Rgba32.Red));
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
index 018fabd982..817672f34a 100644
--- a/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
+++ b/tests/ImageSharp.Tests/Helpers/ImageMathsTests.cs
@@ -2,6 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Memory;
+
using Xunit;
namespace SixLabors.ImageSharp.Tests.Helpers
@@ -131,6 +136,23 @@ namespace SixLabors.ImageSharp.Tests.Helpers
Assert.Equal(expected, actual);
}
+ [Theory]
+ [InlineData(0.2f, 0.7f, 0.1f, 256, 140)]
+ [InlineData(0.5f, 0.5f, 0.5f, 256, 128)]
+ [InlineData(0.5f, 0.5f, 0.5f, 65536, 32768)]
+ [InlineData(0.2f, 0.7f, 0.1f, 65536, 36069)]
+ public void GetBT709Luminance_WithVector4(float x, float y, float z, int luminanceLevels, int expected)
+ {
+ // arrange
+ var vector = new Vector4(x, y, z, 0.0f);
+
+ // act
+ int actual = ImageMaths.GetBT709Luminance(ref vector, luminanceLevels);
+
+ // assert
+ Assert.Equal(expected, actual);
+ }
+
// TODO: We need to test all ImageMaths methods!
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Issues/Issue412.cs b/tests/ImageSharp.Tests/Issues/Issue412.cs
index 5a590018e5..53c65b643a 100644
--- a/tests/ImageSharp.Tests/Issues/Issue412.cs
+++ b/tests/ImageSharp.Tests/Issues/Issue412.cs
@@ -20,14 +20,14 @@ namespace SixLabors.ImageSharp.Tests.Issues
for (var i = 0; i < 40; ++i)
{
context.DrawLines(
- new GraphicsOptions(false),
+ new GraphicsOptions { Antialias = false },
Color.Black,
1,
new PointF(i, 0.1066f),
new PointF(i, 10.1066f));
context.DrawLines(
- new GraphicsOptions(false),
+ new GraphicsOptions { Antialias = false },
Color.Red,
1,
new PointF(i, 15.1066f),
diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
index 09a78a6aa1..693dd6bd81 100644
--- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
@@ -36,9 +36,10 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
using (Image src = srcFile.CreateRgba32Image())
using (Image dest = provider.GetImage())
{
- GraphicsOptions options = new GraphicsOptions
- {
- AlphaCompositionMode = mode
+ var options = new GraphicsOptions
+ {
+ Antialias = false,
+ AlphaCompositionMode = mode
};
using (Image res = dest.Clone(x => x.DrawImage(src, options)))
@@ -53,4 +54,4 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats.PixelBlenders
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
index 140af9563a..cfac8645ff 100644
--- a/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
+++ b/tests/ImageSharp.Tests/Processing/BaseImageOperationsExtensionTest.cs
@@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Processing
public BaseImageOperationsExtensionTest()
{
- this.options = new GraphicsOptions(false);
+ this.options = new GraphicsOptions { Antialias = false };
this.source = new Image(91 + 324, 123 + 56);
this.rect = new Rectangle(91, 123, 324, 56); // make this random?
this.internalOperations = new FakeImageOperationsProvider.FakeImageOperations(this.source, false);
diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs
index 1b5bd656dc..a137a9f438 100644
--- a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs
@@ -1,22 +1,24 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Overlays;
-
+using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Effects
{
public class BackgroundColorTest : BaseImageOperationsExtensionTest
{
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
[Fact]
public void BackgroundColor_amount_BackgroundColorProcessorDefaultsSet()
{
this.operations.BackgroundColor(Color.BlanchedAlmond);
- var processor = this.Verify();
+ BackgroundColorProcessor processor = this.Verify();
- Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.BlanchedAlmond, processor.Color);
}
@@ -24,9 +26,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
public void BackgroundColor_amount_rect_BackgroundColorProcessorDefaultsSet()
{
this.operations.BackgroundColor(Color.BlanchedAlmond, this.rect);
- var processor = this.Verify(this.rect);
+ BackgroundColorProcessor processor = this.Verify(this.rect);
- Assert.Equal(GraphicsOptions.Default, processor.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), processor.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.BlanchedAlmond, processor.Color);
}
@@ -34,9 +36,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
public void BackgroundColor_amount_options_BackgroundColorProcessorDefaultsSet()
{
this.operations.BackgroundColor(this.options, Color.BlanchedAlmond);
- var processor = this.Verify();
+ BackgroundColorProcessor processor = this.Verify();
- Assert.Equal(this.options, processor.GraphicsOptions);
+ Assert.Equal(this.options, processor.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.BlanchedAlmond, processor.Color);
}
@@ -44,10 +46,10 @@ namespace SixLabors.ImageSharp.Tests.Processing.Effects
public void BackgroundColor_amount_rect_options_BackgroundColorProcessorDefaultsSet()
{
this.operations.BackgroundColor(this.options, Color.BlanchedAlmond, this.rect);
- var processor = this.Verify(this.rect);
+ BackgroundColorProcessor processor = this.Verify(this.rect);
- Assert.Equal(this.options, processor.GraphicsOptions);
+ Assert.Equal(this.options, processor.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.BlanchedAlmond, processor.Color);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
index 978fd416bc..32c4c6fe74 100644
--- a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs
@@ -1,10 +1,11 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Overlays;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Primitives;
using Xunit;
@@ -12,13 +13,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{
public class GlowTest : BaseImageOperationsExtensionTest
{
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
[Fact]
public void Glow_GlowProcessorWithDefaultValues()
{
this.operations.Glow();
- var p = this.Verify();
+ GlowProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
}
@@ -27,9 +30,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
public void Glow_Color_GlowProcessorWithDefaultValues()
{
this.operations.Glow(Rgba32.Aquamarine);
- var p = this.Verify();
+ GlowProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Aquamarine, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
}
@@ -38,9 +41,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
public void Glow_Radux_GlowProcessorWithDefaultValues()
{
this.operations.Glow(3.5f);
- var p = this.Verify();
+ GlowProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.Absolute(3.5f), p.Radius);
}
@@ -50,11 +53,11 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{
var rect = new Rectangle(12, 123, 43, 65);
this.operations.Glow(rect);
- var p = this.Verify(rect);
+ GlowProcessor p = this.Verify(rect);
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.GlowColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.Radius);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs
index 2484cf0cb8..ebf4fee317 100644
--- a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs
+++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs
@@ -1,9 +1,10 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Overlays;
+using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.Primitives;
using Xunit;
@@ -11,13 +12,15 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{
public class VignetteTest : BaseImageOperationsExtensionTest
{
+ private static readonly GraphicsOptionsComparer graphicsOptionsComparer = new GraphicsOptionsComparer();
+
[Fact]
public void Vignette_VignetteProcessorWithDefaultValues()
{
this.operations.Vignette();
- var p = this.Verify();
+ VignetteProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@@ -27,9 +30,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
public void Vignette_Color_VignetteProcessorWithDefaultValues()
{
this.operations.Vignette(Color.Aquamarine);
- var p = this.Verify();
+ VignetteProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Aquamarine, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
@@ -39,9 +42,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
public void Vignette_Radux_VignetteProcessorWithDefaultValues()
{
this.operations.Vignette(3.5f, 12123f);
- var p = this.Verify();
+ VignetteProcessor p = this.Verify();
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.Absolute(3.5f), p.RadiusX);
Assert.Equal(ValueSize.Absolute(12123f), p.RadiusY);
@@ -52,12 +55,12 @@ namespace SixLabors.ImageSharp.Tests.Processing.Overlays
{
var rect = new Rectangle(12, 123, 43, 65);
this.operations.Vignette(rect);
- var p = this.Verify(rect);
+ VignetteProcessor p = this.Verify(rect);
- Assert.Equal(GraphicsOptions.Default, p.GraphicsOptions);
+ Assert.Equal(new GraphicsOptions(), p.GraphicsOptions, graphicsOptionsComparer);
Assert.Equal(Color.Black, p.VignetteColor);
Assert.Equal(ValueSize.PercentageOfWidth(.5f), p.RadiusX);
Assert.Equal(ValueSize.PercentageOfHeight(.5f), p.RadiusY);
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 146f2efcdb..d19dbe8341 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -99,6 +99,7 @@ namespace SixLabors.ImageSharp.Tests
public const string ZlibOverflow = "Png/zlib-overflow.png";
public const string ZlibOverflow2 = "Png/zlib-overflow2.png";
public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png";
+ public const string Issue1047_BadEndChunk = "Png/issues/Issue_1047.png";
}
public static readonly string[] All =
@@ -365,5 +366,24 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] All = { Rings, Giphy, Cheers, Trans, Kumin, Leo, Ratio4x1, Ratio1x4 };
}
+
+ public static class Tga
+ {
+ public const string Bit15 = "Tga/rgb15.tga";
+ public const string Bit15Rle = "Tga/rgb15rle.tga";
+ public const string Bit16 = "Tga/targa_16bit.tga";
+ public const string Bit16PalRle = "Tga/ccm8.tga";
+ public const string Bit24 = "Tga/targa_24bit.tga";
+ public const string Bit24TopLeft = "Tga/targa_24bit_pal_origin_topleft.tga";
+ public const string Bit24RleTopLeft = "Tga/targa_24bit_rle_origin_topleft.tga";
+ public const string Bit32 = "Tga/targa_32bit.tga";
+ public const string Grey = "Tga/targa_8bit.tga";
+ public const string GreyRle = "Tga/targa_8bit_rle.tga";
+ public const string Bit16Rle = "Tga/targa_16bit_rle.tga";
+ public const string Bit24Rle = "Tga/targa_24bit_rle.tga";
+ public const string Bit32Rle = "Tga/targa_32bit_rle.tga";
+ public const string Bit16Pal = "Tga/targa_16bit_pal.tga";
+ public const string Bit24Pal = "Tga/targa_24bit_pal.tga";
+ }
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs
new file mode 100644
index 0000000000..248755ea36
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/GraphicsOptionsComparer.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Tests.TestUtilities
+{
+ public class GraphicsOptionsComparer : IEqualityComparer
+ {
+ public bool Equals(GraphicsOptions x, GraphicsOptions y)
+ {
+ return x.AlphaCompositionMode == y.AlphaCompositionMode
+ && x.Antialias == y.Antialias
+ && x.AntialiasSubpixelDepth == y.AntialiasSubpixelDepth
+ && x.BlendPercentage == y.BlendPercentage
+ && x.ColorBlendingMode == y.ColorBlendingMode;
+ }
+
+ public int GetHashCode(GraphicsOptions obj) => obj.GetHashCode();
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
index 7d06847223..e09b27c714 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
@@ -8,6 +8,7 @@ using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
namespace SixLabors.ImageSharp.Tests
@@ -53,7 +54,8 @@ namespace SixLabors.ImageSharp.Tests
{
var cfg = new Configuration(
new JpegConfigurationModule(),
- new GifConfigurationModule()
+ new GifConfigurationModule(),
+ new TgaConfigurationModule()
);
// Magick codecs should work on all platforms
@@ -75,4 +77,4 @@ namespace SixLabors.ImageSharp.Tests
return cfg;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/Images/External b/tests/Images/External
index 563ec6f777..ca4cf8318f 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit 563ec6f7774734ba39924174c8961705a1ea6fa2
+Subproject commit ca4cf8318fe4d09f0fc825686dcd477ebfb5e3e5
diff --git a/tests/Images/Input/Png/issues/Issue_1047.png b/tests/Images/Input/Png/issues/Issue_1047.png
new file mode 100644
index 0000000000..7d5a53a9e5
--- /dev/null
+++ b/tests/Images/Input/Png/issues/Issue_1047.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4768d4bc3a4aaddb8e3e5cbff2beb706abacfd5448d658564f001811dafd320a
+size 44638
diff --git a/tests/Images/Input/Tga/ccm8.tga b/tests/Images/Input/Tga/ccm8.tga
new file mode 100644
index 0000000000..ab92516355
--- /dev/null
+++ b/tests/Images/Input/Tga/ccm8.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:67b3ffaaa75561d8b959258d6b26a1f9ca3228b02a3df98a614ea43241aaea52
+size 9271
diff --git a/tests/Images/Input/Tga/rgb15.tga b/tests/Images/Input/Tga/rgb15.tga
new file mode 100644
index 0000000000..870295b45a
--- /dev/null
+++ b/tests/Images/Input/Tga/rgb15.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:390cfff190bc41386fa134eca70ea0d3ffdc32a285c73278ed34046b09c46c9d
+size 80537
diff --git a/tests/Images/Input/Tga/rgb15rle.tga b/tests/Images/Input/Tga/rgb15rle.tga
new file mode 100644
index 0000000000..a45940fc98
--- /dev/null
+++ b/tests/Images/Input/Tga/rgb15rle.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3219186fc9a9f859c99c2b31cf81e7f0ab4292956d22fc659e714d0cdb51cfa7
+size 19941
diff --git a/tests/Images/Input/Tga/targa.png b/tests/Images/Input/Tga/targa.png
new file mode 100644
index 0000000000..c4933c0ebd
--- /dev/null
+++ b/tests/Images/Input/Tga/targa.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:abc382cec34a04815bd53ad30707c6cdeeceece8731244244e2ab91026d60957
+size 106139
diff --git a/tests/Images/Input/Tga/targa_16bit.tga b/tests/Images/Input/Tga/targa_16bit.tga
new file mode 100644
index 0000000000..6c4143c2ee
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_16bit.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f3adea897f8843b73d0042e23bdfbd0115a7f534df90699134e768df57061f46
+size 70518
diff --git a/tests/Images/Input/Tga/targa_16bit_pal.tga b/tests/Images/Input/Tga/targa_16bit_pal.tga
new file mode 100644
index 0000000000..b25def7798
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_16bit_pal.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:97a4ac0cecfe69e1b5c74db5288fb8ca3bf29968e3b5288c4e5ce03bb4f06915
+size 35780
diff --git a/tests/Images/Input/Tga/targa_16bit_rle.tga b/tests/Images/Input/Tga/targa_16bit_rle.tga
new file mode 100644
index 0000000000..49ef0e998b
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_16bit_rle.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:47d7ebf37672ea846ce071155733697e34083de36aeaafaebd78317708feffde
+size 19566
diff --git a/tests/Images/Input/Tga/targa_24bit.tga b/tests/Images/Input/Tga/targa_24bit.tga
new file mode 100644
index 0000000000..82c22e2425
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_24bit.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:35921b6250e43ba8e1fb125ebe4939a57a67efb0aa9eac0d3605bf90e93309b1
+size 105768
diff --git a/tests/Images/Input/Tga/targa_24bit_pal.tga b/tests/Images/Input/Tga/targa_24bit_pal.tga
new file mode 100644
index 0000000000..abfbf588a6
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_24bit_pal.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4926969e5ae6c9af38d33fa18429de756c48d06edd87c5d27cb8d5232b066ab2
+size 36036
diff --git a/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga
new file mode 100644
index 0000000000..b8c4071745
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_24bit_pal_origin_topleft.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b1c52e538a7d134b20ff57e44b7e304d1b5effacac03a4481d169702796fb195
+size 36062
diff --git a/tests/Images/Input/Tga/targa_24bit_rle.tga b/tests/Images/Input/Tga/targa_24bit_rle.tga
new file mode 100644
index 0000000000..d6af44c0a6
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_24bit_rle.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:56a79ab92d84bbe8c7efbc2711051938fa3ba97b48830aea0cb1dafd7d1fe222
+size 37711
diff --git a/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga
new file mode 100644
index 0000000000..9310c51a70
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_24bit_rle_origin_topleft.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:30e8b6d01ebf9d227d2e9dcdd7b2641bf8f335107110dfff780351870217d4f4
+size 37102
diff --git a/tests/Images/Input/Tga/targa_32bit.tga b/tests/Images/Input/Tga/targa_32bit.tga
new file mode 100644
index 0000000000..8b2a57c810
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_32bit.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e3a220619e25e86bab01b01a2e231ee64fd004e047fa86016bf68de576877352
+size 141018
diff --git a/tests/Images/Input/Tga/targa_32bit_rle.tga b/tests/Images/Input/Tga/targa_32bit_rle.tga
new file mode 100644
index 0000000000..b021a2cc15
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_32bit_rle.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f415d6a246909c18fe604248ab5fe27c74aff9a63df58d8cdeab7c4c3cbe056a
+size 49994
diff --git a/tests/Images/Input/Tga/targa_8bit.tga b/tests/Images/Input/Tga/targa_8bit.tga
new file mode 100644
index 0000000000..9b0512971e
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_8bit.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6aaae46d0e55f32a72732fbe48ed9dc4044c53432999ab66e9475e45e40f0133
+size 35268
diff --git a/tests/Images/Input/Tga/targa_8bit_rle.tga b/tests/Images/Input/Tga/targa_8bit_rle.tga
new file mode 100644
index 0000000000..d6a66def15
--- /dev/null
+++ b/tests/Images/Input/Tga/targa_8bit_rle.tga
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a18d7fd98bc9ab62276103b4e7b474be93b3d7241f4f06aa564e32150e205a71
+size 13145