diff --git a/README.md b/README.md index ca2427546..561768199 100644 --- a/README.md +++ b/README.md @@ -106,17 +106,22 @@ using (Image image = Image.Load(stream)) } ``` -Setting individual pixel values is perfomed as follows: +Setting individual pixel values can be perfomed as follows: ```csharp +// Individual pixels using (Image image = new Image(400, 400) -using (PixelAccessor pixels = image.Lock()) { - pixels[200, 200] = Rgba32.White; + image[200, 200] = Rgba32.White; } ``` -For advanced usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For optimized access within a loop it is recommended that the following methods are used. + +1. `image.GetRowSpan(y)` +2. `image.GetRowSpan(x, y)` + +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index 9534c7a88..8cb731782 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -22,7 +22,7 @@ namespace ImageSharp.Drawing /// /// Creates the applicator for this brush. /// - /// The pixel source. + /// The source image. /// The region the brush will be applied to. /// The graphic options /// @@ -32,6 +32,6 @@ namespace ImageSharp.Drawing /// 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 /// - BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 6f851e5c3..59dbd3926 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Brushes { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -22,21 +21,21 @@ namespace ImageSharp.Drawing.Brushes /// /// The image to paint. /// - private readonly IImageBase image; + private readonly ImageBase image; /// /// Initializes a new instance of the class. /// /// The image. - public ImageBrush(IImageBase image) + public ImageBrush(ImageBase image) { this.image = image; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new ImageBrushApplicator(sourcePixels, this.image, region, options); + return new ImageBrushApplicator(source, this.image, region, options); } /// @@ -45,9 +44,9 @@ namespace ImageSharp.Drawing.Brushes private class ImageBrushApplicator : BrushApplicator { /// - /// The source pixel accessor. + /// The source image. /// - private readonly PixelAccessor source; + private readonly ImageBase source; /// /// The y-length. @@ -72,20 +71,14 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// - /// The image. - /// - /// - /// The region. - /// + /// The target image. + /// The image. + /// The region. /// The options - /// - /// The sourcePixels. - /// - public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region, GraphicsOptions options) - : base(sourcePixels, options) + public ImageBrushApplicator(ImageBase target, ImageBase image, RectangleF region, GraphicsOptions options) + : base(target, options) { - this.source = image.Lock(); + this.source = image; this.xLength = image.Width; this.yLength = image.Height; this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); @@ -119,9 +112,9 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - // create a span for colors - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + // Create a span for colors + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 90990e54a..5dd57bda6 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -62,8 +62,8 @@ namespace ImageSharp.Drawing.Brushes /// The pattern. internal PatternBrush(TPixel foreColor, TPixel backColor, Fast2DArray pattern) { - Vector4 foreColorVector = foreColor.ToVector4(); - Vector4 backColorVector = backColor.ToVector4(); + var foreColorVector = foreColor.ToVector4(); + var backColorVector = backColor.ToVector4(); this.pattern = new Fast2DArray(pattern.Width, pattern.Height); this.patternVector = new Fast2DArray(pattern.Width, pattern.Height); for (int i = 0; i < pattern.Data.Length; i++) @@ -92,9 +92,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options); + return new PatternBrushApplicator(source, this.pattern, this.patternVector, options); } /// @@ -111,12 +111,12 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The sourcePixels. + /// The source image. /// The pattern. /// The patternVector. /// The options - public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) - : base(sourcePixels, options) + public PatternBrushApplicator(ImageBase source, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) + : base(source, options) { this.pattern = pattern; this.patternVector = patternVector; @@ -152,8 +152,8 @@ namespace ImageSharp.Drawing.Brushes internal override void Apply(Span scanline, int x, int y) { int patternY = y % this.pattern.Height; - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 29629324a..29c625d7f 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Drawing.Processors { using System; - using System.Numerics; using ImageSharp.Memory; using ImageSharp.PixelFormats; @@ -24,7 +23,7 @@ namespace ImageSharp.Drawing.Processors /// /// The target. /// The options. - internal BrushApplicator(PixelAccessor target, GraphicsOptions options) + internal BrushApplicator(ImageBase target, GraphicsOptions options) { this.Target = target; @@ -41,7 +40,7 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the destinaion /// - protected PixelAccessor Target { get; } + protected ImageBase Target { get; } /// /// Gets the blend percentage @@ -68,8 +67,8 @@ namespace ImageSharp.Drawing.Processors /// 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) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 64b91e384..96d824b2f 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -57,9 +57,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel TargeTPixel { get; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options); + return new RecolorBrushApplicator(source, this.SourceColor, this.TargeTPixel, this.Threshold, options); } /// @@ -87,22 +87,22 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// - /// The source pixels. + /// The source image. /// Color of the source. /// Color of the target. /// The threshold . /// The options - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) - : base(sourcePixels, options) + public RecolorBrushApplicator(ImageBase source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) + : base(source, options) { this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); this.targetColorPixel = targetColor; - // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) - TPixel maxColor = default(TPixel); + // Lets hack a min max extreams for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) + var maxColor = default(TPixel); maxColor.PackFromVector4(new Vector4(float.MaxValue)); - TPixel minColor = default(TPixel); + var minColor = default(TPixel); minColor.PackFromVector4(new Vector4(float.MinValue)); this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; } @@ -121,7 +121,7 @@ namespace ImageSharp.Drawing.Brushes { // Offset the requested pixel by the value in the rectangle (the shapes position) TPixel result = this.Target[x, y]; - Vector4 background = result.ToVector4(); + var background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) { @@ -144,8 +144,8 @@ namespace ImageSharp.Drawing.Brushes /// internal override void Apply(Span scanline, int x, int y) { - using (Buffer amountBuffer = new Buffer(scanline.Length)) - using (Buffer overlay = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) + using (var overlay = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 28f7b0e45..453b4d29e 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -42,9 +42,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel Color => this.color; /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) + public BrushApplicator CreateApplicator(ImageBase source, RectangleF region, GraphicsOptions options) { - return new SolidBrushApplicator(sourcePixels, this.color, options); + return new SolidBrushApplicator(source, this.color, options); } /// @@ -55,13 +55,13 @@ namespace ImageSharp.Drawing.Brushes /// /// Initializes a new instance of the class. /// + /// The source image. /// The color. /// The options - /// The sourcePixels. - public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) - : base(sourcePixels, options) + public SolidBrushApplicator(ImageBase source, TPixel color, GraphicsOptions options) + : base(source, options) { - this.Colors = new Buffer(sourcePixels.Width); + this.Colors = new Buffer(source.Width); for (int i = 0; i < this.Colors.Length; i++) { this.Colors[i] = color; @@ -94,7 +94,7 @@ namespace ImageSharp.Drawing.Brushes { Span destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (var amountBuffer = new Buffer(scanline.Length)) { for (int i = 0; i < scanline.Length; i++) { diff --git a/src/ImageSharp.Drawing/Drawable.cs b/src/ImageSharp.Drawing/Drawable.cs deleted file mode 100644 index 62f5e62c1..000000000 --- a/src/ImageSharp.Drawing/Drawable.cs +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - /// - /// Represents a path or set of paths that can be drawn as an outline. - /// - public abstract class Drawable - { - /// - /// Gets the maximum number of intersections to could be returned. - /// - public abstract int MaxIntersections { get; } - - /// - /// Gets the bounds. - /// - public abstract Rectangle Bounds { get; } - - /// - /// Gets the point information for the specified x and y location. - /// - /// The x. - /// The y. - /// Information about the point in relation to a drawable edge - public abstract PointInfo GetPointInfo(int x, int y); - - /// - /// Scans the X axis for intersections. - /// - /// The x. - /// The buffer. - /// The length. - /// The offset. - /// The number of intersections found. - public abstract int ScanX(int x, float[] buffer, int length, int offset); - - /// - /// Scans the Y axis for intersections. - /// - /// The position along the y axis to find intersections. - /// The buffer. - /// The length. - /// The offset. - /// The number of intersections found. - public abstract int ScanY(int y, float[] buffer, int length, int offset); - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index a3552a09c..13500b65a 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -36,11 +36,10 @@ + All - - ..\..\ImageSharp.ruleset diff --git a/src/ImageSharp.Drawing/Paths/DrawPath.cs b/src/ImageSharp.Drawing/Paths/DrawPath.cs index 176539663..1fba06370 100644 --- a/src/ImageSharp.Drawing/Paths/DrawPath.cs +++ b/src/ImageSharp.Drawing/Paths/DrawPath.cs @@ -28,7 +28,7 @@ namespace ImageSharp public static Image Draw(this Image source, IPen pen, IPath path, GraphicsOptions options) where TPixel : struct, IPixel { - return source.Draw(pen, new ShapePath(path), options); + return source.Fill(pen.StrokeFill, new ShapePath(path, pen), options); } /// diff --git a/src/ImageSharp.Drawing/DrawPath.cs b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs similarity index 79% rename from src/ImageSharp.Drawing/DrawPath.cs rename to src/ImageSharp.Drawing/Paths/DrawPathCollection.cs index 09d3dbb02..877737653 100644 --- a/src/ImageSharp.Drawing/DrawPath.cs +++ b/src/ImageSharp.Drawing/Paths/DrawPathCollection.cs @@ -8,8 +8,8 @@ namespace ImageSharp using Drawing; using Drawing.Brushes; using Drawing.Pens; - using Drawing.Processors; using ImageSharp.PixelFormats; + using SixLabors.Shapes; /// /// Extension methods for the type. @@ -17,18 +17,23 @@ namespace ImageSharp public static partial class ImageExtensions { /// - /// Draws the outline of the region with the provided pen. + /// Draws the outline of the polygon with the provided pen. /// /// The type of the color. /// The image this method extends. /// The pen. - /// The path. + /// The paths. /// The options. /// The . - public static Image Draw(this Image source, IPen pen, Drawable path, GraphicsOptions options) + public static Image Draw(this Image source, IPen pen, IPathCollection paths, GraphicsOptions options) where TPixel : struct, IPixel { - return source.Apply(new DrawPathProcessor(pen, path, options)); + foreach (IPath path in paths) + { + source.Draw(pen, path, options); + } + + return source; } /// @@ -37,12 +42,12 @@ namespace ImageSharp /// The type of the color. /// The image this method extends. /// The pen. - /// The path. + /// The paths. /// The . - public static Image Draw(this Image source, IPen pen, Drawable path) + public static Image Draw(this Image source, IPen pen, IPathCollection paths) where TPixel : struct, IPixel { - return source.Draw(pen, path, GraphicsOptions.Default); + return source.Draw(pen, paths, GraphicsOptions.Default); } /// @@ -52,13 +57,13 @@ namespace ImageSharp /// The image this method extends. /// The brush. /// The thickness. - /// The path. + /// The shapes. /// The options. /// The . - public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path, GraphicsOptions options) + public static Image Draw(this Image source, IBrush brush, float thickness, IPathCollection paths, GraphicsOptions options) where TPixel : struct, IPixel { - return source.Draw(new Pen(brush, thickness), path, options); + return source.Draw(new Pen(brush, thickness), paths, options); } /// @@ -68,12 +73,12 @@ namespace ImageSharp /// The image this method extends. /// The brush. /// The thickness. - /// The path. + /// The paths. /// The . - public static Image Draw(this Image source, IBrush brush, float thickness, Drawable path) + public static Image Draw(this Image source, IBrush brush, float thickness, IPathCollection paths) where TPixel : struct, IPixel { - return source.Draw(new Pen(brush, thickness), path); + return source.Draw(new Pen(brush, thickness), paths); } /// @@ -83,13 +88,13 @@ namespace ImageSharp /// The image this method extends. /// The color. /// The thickness. - /// The path. + /// The paths. /// The options. /// The . - public static Image Draw(this Image source, TPixel color, float thickness, Drawable path, GraphicsOptions options) + public static Image Draw(this Image source, TPixel color, float thickness, IPathCollection paths, GraphicsOptions options) where TPixel : struct, IPixel { - return source.Draw(new SolidBrush(color), thickness, path, options); + return source.Draw(new SolidBrush(color), thickness, paths, options); } /// @@ -99,12 +104,12 @@ namespace ImageSharp /// The image this method extends. /// The color. /// The thickness. - /// The path. + /// The paths. /// The . - public static Image Draw(this Image source, TPixel color, float thickness, Drawable path) + public static Image Draw(this Image source, TPixel color, float thickness, IPathCollection paths) where TPixel : struct, IPixel { - return source.Draw(new SolidBrush(color), thickness, path); + return source.Draw(new SolidBrush(color), thickness, paths); } } } diff --git a/src/ImageSharp.Drawing/Paths/FillPathCollection.cs b/src/ImageSharp.Drawing/Paths/FillPathCollection.cs new file mode 100644 index 000000000..3ea9fb94b --- /dev/null +++ b/src/ImageSharp.Drawing/Paths/FillPathCollection.cs @@ -0,0 +1,81 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using Drawing; + using Drawing.Brushes; + using ImageSharp.PixelFormats; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The shapes. + /// The graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + foreach (IPath s in paths) + { + source.Fill(brush, s, options); + } + + return source; + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The brush. + /// The paths. + /// The . + public static Image Fill(this Image source, IBrush brush, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Fill(brush, paths, GraphicsOptions.Default); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The paths. + /// The options. + /// The . + public static Image Fill(this Image source, TPixel color, IPathCollection paths, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Fill(new SolidBrush(color), paths, options); + } + + /// + /// Flood fills the image in the shape of the provided polygon with the specified brush.. + /// + /// The type of the color. + /// The image this method extends. + /// The color. + /// The paths. + /// The . + public static Image Fill(this Image source, TPixel color, IPathCollection paths) + where TPixel : struct, IPixel + { + return source.Fill(new SolidBrush(color), paths); + } + } +} diff --git a/src/ImageSharp.Drawing/Paths/ShapePath.cs b/src/ImageSharp.Drawing/Paths/ShapePath.cs index d0376ccc1..63c814f2d 100644 --- a/src/ImageSharp.Drawing/Paths/ShapePath.cs +++ b/src/ImageSharp.Drawing/Paths/ShapePath.cs @@ -1,12 +1,12 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // namespace ImageSharp.Drawing { + using System; using System.Buffers; - using System.Collections.Immutable; using System.Numerics; using SixLabors.Shapes; @@ -14,91 +14,19 @@ namespace ImageSharp.Drawing using Rectangle = ImageSharp.Rectangle; /// - /// A drawable mapping between a and a drawable region. + /// A mapping between a and a region. /// - internal class ShapePath : Drawable + internal class ShapePath : ShapeRegion { /// /// Initializes a new instance of the class. /// - /// The path. - public ShapePath(IPath path) + /// The shape. + /// The pen to apply to the shape. + // SixLabors.shape willbe moving to a Span/ReadOnlySpan based API shortly use ToArray for now. + public ShapePath(IPath shape, Pens.IPen pen) + : base(shape.GenerateOutline(pen.StrokeWidth, pen.StrokePattern.ToArray())) { - this.Path = path; - this.Bounds = path.Bounds.Convert(); - } - - /// - /// Gets the fillable shape - /// - public IPath Path { get; } - - /// - public override int MaxIntersections => this.Path.MaxIntersections; - - /// - public override Rectangle Bounds { get; } - - /// - public override int ScanX(int x, float[] buffer, int length, int offset) - { - Vector2 start = new Vector2(x, this.Bounds.Top - 1); - Vector2 end = new Vector2(x, this.Bounds.Bottom + 1); - Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); - try - { - int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); - - for (int i = 0; i < count; i++) - { - buffer[i + offset] = innerbuffer[i].Y; - } - - return count; - } - finally - { - ArrayPool.Shared.Return(innerbuffer); - } - } - - /// - public override int ScanY(int y, float[] buffer, int length, int offset) - { - Vector2 start = new Vector2(this.Bounds.Left - 1, y); - Vector2 end = new Vector2(this.Bounds.Right + 1, y); - Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); - try - { - int count = this.Path.FindIntersections(start, end, innerbuffer, length, 0); - - for (int i = 0; i < count; i++) - { - buffer[i + offset] = innerbuffer[i].X; - } - - return count; - } - finally - { - ArrayPool.Shared.Return(innerbuffer); - } - } - - /// - public override PointInfo GetPointInfo(int x, int y) - { - Vector2 point = new Vector2(x, y); - SixLabors.Shapes.PointInfo dist = this.Path.Distance(point); - - return new PointInfo - { - DistanceAlongPath = dist.DistanceAlongPath, - DistanceFromPath = - dist.DistanceFromPath < 0 - ? -dist.DistanceFromPath - : dist.DistanceFromPath - }; } } } diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index 086894531..9dbf52285 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -40,18 +40,18 @@ namespace ImageSharp.Drawing public override Rectangle Bounds { get; } /// - public override int Scan(float y, float[] buffer, int length, int offset) + public override int Scan(float y, Span buffer) { Vector2 start = new Vector2(this.Bounds.Left - 1, y); Vector2 end = new Vector2(this.Bounds.Right + 1, y); - Vector2[] innerbuffer = ArrayPool.Shared.Rent(length); + Vector2[] innerbuffer = ArrayPool.Shared.Rent(buffer.Length); try { - int count = this.Shape.FindIntersections(start, end, innerbuffer, length, 0); + int count = this.Shape.FindIntersections(start, end, innerbuffer, buffer.Length, 0); for (int i = 0; i < count; i++) { - buffer[i + offset] = innerbuffer[i].X; + buffer[i] = innerbuffer[i].X; } return count; diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index d488dbfb0..20ac53daf 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -12,21 +12,28 @@ namespace ImageSharp.Drawing.Pens /// Interface representing a Pen /// /// The type of the color. - public interface IPen + public interface IPen : IPen where TPixel : struct, IPixel { /// - /// Creates the applicator for applying this pen to an Image + /// Gets the stroke fill. /// - /// The pixel source. - /// The region the pen will be applied to. - /// The currently active graphic options. - /// - /// Returns a the applicator for the pen. - /// - /// - /// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image. - /// - PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); + IBrush StrokeFill { get; } + } + + /// + /// Iterface represting the pattern and size of the stroke to apply with a Pen. + /// + public interface IPen + { + /// + /// Gets the width to apply to the stroke + /// + float StrokeWidth { get; } + + /// + /// Gets the stoke pattern. + /// + System.ReadOnlySpan StrokePattern { get; } } } diff --git a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs index 1da50e0d6..37cfff9e9 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Drawing.Pens { + using System; using System.Numerics; using ImageSharp.Drawing.Brushes; @@ -48,8 +49,8 @@ namespace ImageSharp.Drawing.Pens /// The pattern. public Pen(IBrush brush, float width, float[] pattern) { - this.Brush = brush; - this.Width = width; + this.StrokeFill = brush; + this.StrokeWidth = width; this.pattern = pattern; } @@ -73,195 +74,13 @@ namespace ImageSharp.Drawing.Pens { } - /// - /// Initializes a new instance of the class. - /// - /// The pen. - internal Pen(Pen pen) - : this(pen.Brush, pen.Width, pen.pattern) - { - } - - /// - /// Gets the brush. - /// - /// - /// The brush. - /// - public IBrush Brush { get; } - - /// - /// Gets the width. - /// - /// - /// The width. - /// - public float Width { get; } - - /// - /// Creates the applicator for applying this pen to an Image - /// - /// The source pixels. - /// The region the pen will be applied to. - /// The Graphics options - /// - /// Returns a the applicator for the pen. - /// - /// - /// The when being applied to things like shapes would ussually be the - /// bounding box of the shape not necorserrally the shape of the whole image - /// - public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) - { - if (this.pattern == null || this.pattern.Length < 2) - { - // if there is only one item in the pattern then 100% of it will - // be solid so use the quicker applicator - return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options); - } - - return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options); - } - - private class SolidPenApplicator : PenApplicator - { - private readonly BrushApplicator brush; - private readonly float halfWidth; - - public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) - { - this.brush = brush.CreateApplicator(sourcePixels, region, options); - this.halfWidth = width / 2; - this.RequiredRegion = RectangleF.Outset(region, width); - } - - public override RectangleF RequiredRegion - { - get; - } - - public override void Dispose() - { - this.brush.Dispose(); - } + /// + public IBrush StrokeFill { get; } - public override ColoredPointInfo GetColor(int x, int y, PointInfo info) - { - ColoredPointInfo result = default(ColoredPointInfo); - result.Color = this.brush[x, y]; + /// + public float StrokeWidth { get; } - if (info.DistanceFromPath < this.halfWidth) - { - // inside strip - result.DistanceFromElement = 0; - } - else - { - result.DistanceFromElement = info.DistanceFromPath - this.halfWidth; - } - - return result; - } - } - - private class PatternPenApplicator : PenApplicator - { - private readonly BrushApplicator brush; - private readonly float halfWidth; - private readonly float[] pattern; - private readonly float totalLength; - - public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) - { - this.brush = brush.CreateApplicator(sourcePixels, region, options); - this.halfWidth = width / 2; - this.totalLength = 0; - - this.pattern = new float[pattern.Length + 1]; - this.pattern[0] = 0; - for (int i = 0; i < pattern.Length; i++) - { - this.totalLength += pattern[i] * width; - this.pattern[i + 1] = this.totalLength; - } - - this.RequiredRegion = RectangleF.Outset(region, width); - } - - public override RectangleF RequiredRegion - { - get; - } - - public override void Dispose() - { - this.brush.Dispose(); - } - - public override ColoredPointInfo GetColor(int x, int y, PointInfo info) - { - ColoredPointInfo infoResult = default(ColoredPointInfo); - infoResult.DistanceFromElement = float.MaxValue; // is really outside the element - - float length = info.DistanceAlongPath % this.totalLength; - - // we can treat the DistanceAlongPath and DistanceFromPath as x,y coords for the pattern - // we need to calcualte the distance from the outside edge of the pattern - // and set them on the ColoredPointInfo along with the color. - infoResult.Color = this.brush[x, y]; - - float distanceWAway = 0; - - if (info.DistanceFromPath < this.halfWidth) - { - // inside strip - distanceWAway = 0; - } - else - { - distanceWAway = info.DistanceFromPath - this.halfWidth; - } - - for (int i = 0; i < this.pattern.Length - 1; i++) - { - float start = this.pattern[i]; - float end = this.pattern[i + 1]; - - if (length >= start && length < end) - { - // in section - if (i % 2 == 0) - { - // solid part return the maxDistance - infoResult.DistanceFromElement = distanceWAway; - return infoResult; - } - else - { - // this is a none solid part - float distanceFromStart = length - start; - float distanceFromEnd = end - length; - - float closestEdge = MathF.Min(distanceFromStart, distanceFromEnd); - - float distanceAcross = closestEdge; - - if (distanceWAway > 0) - { - infoResult.DistanceFromElement = new Vector2(distanceAcross, distanceWAway).Length(); - } - else - { - infoResult.DistanceFromElement = closestEdge; - } - - return infoResult; - } - } - } - - return infoResult; - } - } + /// + public ReadOnlySpan StrokePattern => this.pattern; } } diff --git a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs b/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs deleted file mode 100644 index 65a8a6131..000000000 --- a/src/ImageSharp.Drawing/Pens/Processors/ColoredPointInfo.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Processors -{ - using ImageSharp.PixelFormats; - - /// - /// Returns details about how far away from the inside of a shape and the color the pixel could be. - /// - /// The type of the color. - public struct ColoredPointInfo - where TPixel : struct, IPixel - { - /// - /// The color - /// - public TPixel Color; - - /// - /// The distance from element - /// - public float DistanceFromElement; - } -} diff --git a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs b/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs deleted file mode 100644 index ac1889068..000000000 --- a/src/ImageSharp.Drawing/Pens/Processors/PenApplicator.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Processors -{ - using System; - using ImageSharp.PixelFormats; - - /// - /// primitive that converts a into a color and a distance away from the drawable part of the path. - /// - /// The type of the color. - public abstract class PenApplicator : IDisposable - where TPixel : struct, IPixel - { - /// - /// Gets the required region. - /// - /// - /// The required region. - /// - public abstract RectangleF RequiredRegion { get; } - - /// - public abstract void Dispose(); - - /// - /// Gets a from a point represented by a . - /// - /// The x. - /// The y. - /// The information to extract color details about. - /// - /// Returns the color details and distance from a solid bit of the line. - /// - public abstract ColoredPointInfo GetColor(int x, int y, PointInfo info); - } -} diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs deleted file mode 100644 index d1332c435..000000000 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ /dev/null @@ -1,142 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - using ImageSharp.Memory; - using ImageSharp.PixelFormats; - using ImageSharp.Processing; - using Pens; - - /// - /// Draws a path using the processor pipeline - /// - /// The type of the color. - /// - internal class DrawPathProcessor : ImageProcessor - where TPixel : struct, IPixel - { - private const float AntialiasFactor = 1f; - private const int PaddingFactor = 1; // needs to been the same or greater than AntialiasFactor - - /// - /// Initializes a new instance of the class. - /// - /// The details how to draw the outline/path. - /// The details of the paths and outlines to draw. - /// The drawing configuration options. - public DrawPathProcessor(IPen pen, Drawable drawable, GraphicsOptions options) - { - this.Path = drawable; - this.Pen = pen; - this.Options = options; - } - - /// - /// Gets the graphics options. - /// - public GraphicsOptions Options { get; } - - /// - /// Gets the pen. - /// - public IPen Pen { get; } - - /// - /// Gets the path. - /// - public Drawable Path { get; } - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options)) - { - Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); - - int polyStartY = rect.Y - PaddingFactor; - int polyEndY = rect.Bottom + PaddingFactor; - int startX = rect.X - PaddingFactor; - int endX = rect.Right + PaddingFactor; - - int minX = Math.Max(sourceRectangle.Left, startX); - int maxX = Math.Min(sourceRectangle.Right, endX); - int minY = Math.Max(sourceRectangle.Top, polyStartY); - int maxY = Math.Min(sourceRectangle.Bottom, polyEndY); - - // Align start/end positions. - minX = Math.Max(0, minX); - maxX = Math.Min(source.Width, maxX); - minY = Math.Max(0, minY); - maxY = Math.Min(source.Height, maxY); - - // Reset offset if necessary. - if (minX > 0) - { - startX = 0; - } - - if (minY > 0) - { - polyStartY = 0; - } - - int width = maxX - minX; - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.Options.BlenderMode); - - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - polyStartY; - - using (Buffer amount = new Buffer(width)) - using (Buffer colors = new Buffer(width)) - { - for (int i = 0; i < width; i++) - { - int x = i + minX; - int offsetX = x - startX; - PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1); - colors[i] = color.Color; - } - - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - blender.Blend(destination, destination, colors, amount); - } - }); - } - } - - /// - /// Returns the correct opacity for the given distance. - /// - /// Thw distance from the central point. - /// The - private float Opacity(float distance) - { - if (distance <= 0) - { - return 1; - } - - if (this.Options.Antialias && distance < AntialiasFactor) - { - return 1 - (distance / AntialiasFactor); - } - - return 0; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index fa6f48156..8c7cd4e8c 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -66,12 +66,11 @@ namespace ImageSharp.Drawing.Processors int width = maxX - minX; - // we could possibly do some optermising by having knowledge about the individual brushes operate + // We could possibly do some optimization by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer amount = new Buffer(width)) - using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) + using (var amount = new Buffer(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator(source, sourceRectangle, this.options)) { for (int i = 0; i < width; i++) { diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index a57be3a5a..f2e09d289 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors { using System; using System.Buffers; + using System.Runtime.CompilerServices; using Drawing; using ImageSharp.Memory; @@ -89,12 +90,12 @@ namespace ImageSharp.Drawing.Processors } } - using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(source, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); + Span bufferSpan = buffer.AsSpan().Slice(0, maxIntersections); int scanlineWidth = maxX - minX; - using (Buffer scanline = new Buffer(scanlineWidth)) + using (var scanline = new Buffer(scanlineWidth)) { try { @@ -116,14 +117,14 @@ namespace ImageSharp.Drawing.Processors float subpixelFractionPoint = subpixelFraction / subpixelCount; for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); + int pointsFound = region.Scan(subPixel, bufferSpan); if (pointsFound == 0) { // nothing on this line skip continue; } - QuickSort(buffer, pointsFound); + QuickSort(bufferSpan.Slice(0, pointsFound)); for (int point = 0; point < pointsFound; point += 2) { @@ -153,13 +154,11 @@ namespace ImageSharp.Drawing.Processors int nextX = startX + 1; endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - if (nextX >= 0) + nextX = Math.Max(nextX, 0); + for (int x = nextX; x < endX; x++) { - for (int x = nextX; x < endX; x++) - { - scanline[x] += subpixelFraction; - scanlineDirty = true; - } + scanline[x] += subpixelFraction; + scanlineDirty = true; } } } @@ -193,20 +192,21 @@ namespace ImageSharp.Drawing.Processors } } - private static void Swap(float[] data, int left, int right) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Swap(Span data, int left, int right) { float tmp = data[left]; data[left] = data[right]; data[right] = tmp; } - private static void QuickSort(float[] data, int size) + private static void QuickSort(Span data) { - int hi = Math.Min(data.Length - 1, size - 1); + int hi = Math.Min(data.Length - 1, data.Length - 1); QuickSort(data, 0, hi); } - private static void QuickSort(float[] data, int lo, int hi) + private static void QuickSort(Span data, int lo, int hi) { if (lo < hi) { @@ -216,7 +216,7 @@ namespace ImageSharp.Drawing.Processors } } - private static int Partition(float[] data, int lo, int hi) + private static int Partition(Span data, int lo, int hi) { float pivot = data[lo]; int i = lo - 1; diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index 8cab88502..687ee23fd 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Drawing { + using System; + /// /// Represents a region of an image. /// @@ -19,7 +21,7 @@ namespace ImageSharp.Drawing /// Gets the bounding box that entirely surrounds this region. /// /// - /// This should always contains all possible points returned from . + /// This should always contains all possible points returned from . /// public abstract Rectangle Bounds { get; } @@ -28,9 +30,7 @@ namespace ImageSharp.Drawing /// /// The position along the y axis to find intersections. /// The buffer. - /// The length. - /// The offset. /// The number of intersections found. - public abstract int Scan(float y, float[] buffer, int length, int offset); + public abstract int Scan(float y, Span buffer); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Text/DrawText.Path.cs b/src/ImageSharp.Drawing/Text/DrawText.Path.cs new file mode 100644 index 000000000..2bc23b64b --- /dev/null +++ b/src/ImageSharp.Drawing/Text/DrawText.Path.cs @@ -0,0 +1,200 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + using Drawing; + using Drawing.Brushes; + using Drawing.Pens; + using ImageSharp.PixelFormats; + using SixLabors.Fonts; + using SixLabors.Shapes; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TPixel color, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, color, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The color. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, TPixel color, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, Brushes.Solid(color), null, path, options); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The location. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, null, path, options); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, pen, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The pen. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IPen pen, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, null, pen, path, options); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The path. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, IPath path) + where TPixel : struct, IPixel + { + return source.DrawText(text, font, brush, pen, path, TextGraphicsOptions.Default); + } + + /// + /// Draws the text onto the the image filled via the brush then outlined via the pen. + /// + /// The type of the color. + /// The image this method extends. + /// The text. + /// The font. + /// The brush. + /// The pen. + /// The path. + /// The options. + /// + /// The . + /// + public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, IPath path, TextGraphicsOptions options) + where TPixel : struct, IPixel + { + Vector2 dpi = DefaultTextDpi; + if (options.UseImageResolution) + { + dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + } + + var style = new FontSpan(font, dpi) + { + ApplyKerning = options.ApplyKerning, + TabWidth = options.TabWidth, + WrappingWidth = options.WrapTextWidth, + HorizontalAlignment = options.HorizontalAlignment, + VerticalAlignment = options.VerticalAlignment + }; + + IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, path, style); + + var pathOptions = (GraphicsOptions)options; + if (brush != null) + { + source.Fill(brush, glyphs, pathOptions); + } + + if (pen != null) + { + source.Draw(pen, glyphs, pathOptions); + } + + return source; + } + } +} diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs index bd33289fa..3b0d3db41 100644 --- a/src/ImageSharp.Drawing/Text/DrawText.cs +++ b/src/ImageSharp.Drawing/Text/DrawText.cs @@ -12,6 +12,7 @@ namespace ImageSharp using Drawing.Pens; using ImageSharp.PixelFormats; using SixLabors.Fonts; + using SixLabors.Shapes; /// /// Extension methods for the type. @@ -167,43 +168,32 @@ namespace ImageSharp public static Image DrawText(this Image source, string text, Font font, IBrush brush, IPen pen, Vector2 location, TextGraphicsOptions options) where TPixel : struct, IPixel { - GlyphBuilder glyphBuilder = new GlyphBuilder(location); - - TextRenderer renderer = new TextRenderer(glyphBuilder); - Vector2 dpi = DefaultTextDpi; if (options.UseImageResolution) { dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); } - FontSpan style = new FontSpan(font, dpi) + var style = new FontSpan(font, dpi) { ApplyKerning = options.ApplyKerning, TabWidth = options.TabWidth, WrappingWidth = options.WrapTextWidth, - Alignment = options.TextAlignment + HorizontalAlignment = options.HorizontalAlignment, + VerticalAlignment = options.VerticalAlignment }; - renderer.RenderText(text, style); - - System.Collections.Generic.IEnumerable shapesToDraw = glyphBuilder.Paths; + IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, location, style); - GraphicsOptions pathOptions = (GraphicsOptions)options; + var pathOptions = (GraphicsOptions)options; if (brush != null) { - foreach (SixLabors.Shapes.IPath s in shapesToDraw) - { - source.Fill(brush, s, pathOptions); - } + source.Fill(brush, glyphs, pathOptions); } if (pen != null) { - foreach (SixLabors.Shapes.IPath s in shapesToDraw) - { - source.Draw(pen, s, pathOptions); - } + source.Draw(pen, glyphs, pathOptions); } return source; diff --git a/src/ImageSharp.Drawing/Text/GlyphBuilder.cs b/src/ImageSharp.Drawing/Text/GlyphBuilder.cs deleted file mode 100644 index 0033a608c..000000000 --- a/src/ImageSharp.Drawing/Text/GlyphBuilder.cs +++ /dev/null @@ -1,127 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - using System.Collections.Generic; - using System.Numerics; - - using SixLabors.Fonts; - using SixLabors.Shapes; - - /// - /// rendering surface that Fonts can use to generate Shapes. - /// - internal class GlyphBuilder : IGlyphRenderer - { - private readonly PathBuilder builder = new PathBuilder(); - private readonly List paths = new List(); - private Vector2 currentPoint = default(Vector2); - - /// - /// Initializes a new instance of the class. - /// - public GlyphBuilder() - : this(Vector2.Zero) - { - // glyphs are renderd realative to bottom left so invert the Y axis to allow it to render on top left origin surface - this.builder = new PathBuilder(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The origin. - public GlyphBuilder(Vector2 origin) - { - this.builder = new PathBuilder(); - this.builder.SetOrigin(origin); - } - - /// - /// Gets the paths that have been rendered by this. - /// - public IEnumerable Paths => this.paths; - - /// - /// Begins the glyph. - /// - /// The offset that the glyph will be rendered at. - void IGlyphRenderer.BeginGlyph(Vector2 location) - { - this.builder.Clear(); - } - - /// - /// Begins the figure. - /// - void IGlyphRenderer.BeginFigure() - { - this.builder.StartFigure(); - } - - /// - /// Draws a cubic bezier from the current point to the - /// - /// The second control point. - /// The third control point. - /// The point. - void IGlyphRenderer.CubicBezierTo(Vector2 secondControlPoint, Vector2 thirdControlPoint, Vector2 point) - { - this.builder.AddBezier(this.currentPoint, secondControlPoint, thirdControlPoint, point); - this.currentPoint = point; - } - - /// - /// Ends the glyph. - /// - void IGlyphRenderer.EndGlyph() - { - this.paths.Add(this.builder.Build()); - } - - /// - /// Ends the figure. - /// - void IGlyphRenderer.EndFigure() - { - this.builder.CloseFigure(); - } - - /// - /// Draws a line from the current point to the . - /// - /// The point. - void IGlyphRenderer.LineTo(Vector2 point) - { - this.builder.AddLine(this.currentPoint, point); - this.currentPoint = point; - } - - /// - /// Moves to current point to the supplied vector. - /// - /// The point. - void IGlyphRenderer.MoveTo(Vector2 point) - { - this.builder.StartFigure(); - this.currentPoint = point; - } - - /// - /// Draws a quadratics bezier from the current point to the - /// - /// The second control point. - /// The point. - void IGlyphRenderer.QuadraticBezierTo(Vector2 secondControlPoint, Vector2 point) - { - Vector2 c1 = (((secondControlPoint - this.currentPoint) * 2) / 3) + this.currentPoint; - Vector2 c2 = (((secondControlPoint - point) * 2) / 3) + point; - - this.builder.AddBezier(this.currentPoint, c1, c2, point); - this.currentPoint = point; - } - } -} diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index 388b39bcc..593ac36d4 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -33,7 +33,8 @@ namespace ImageSharp.Drawing private float wrapTextWidth; - private SixLabors.Fonts.TextAlignment? textAlignment; + private SixLabors.Fonts.HorizontalAlignment? horizontalAlignment; + private SixLabors.Fonts.VerticalAlignment? verticalAlignment; /// /// Initializes a new instance of the struct. @@ -45,7 +46,8 @@ namespace ImageSharp.Drawing this.tabWidth = 4; this.useImageResolution = false; this.wrapTextWidth = 0; - this.textAlignment = SixLabors.Fonts.TextAlignment.Left; + this.horizontalAlignment = HorizontalAlignment.Left; + this.verticalAlignment = VerticalAlignment.Top; this.antialiasSubpixelDepth = 16; this.blenderMode = PixelBlenderMode.Normal; @@ -104,7 +106,12 @@ namespace ImageSharp.Drawing /// defined by the location and width, if equals zero, and thus /// wrapping disabled, then the alignment is relative to the drawing location. /// - public TextAlignment TextAlignment { get => this.textAlignment ?? TextAlignment.Left; set => this.textAlignment = value; } + public HorizontalAlignment HorizontalAlignment { get => this.horizontalAlignment ?? HorizontalAlignment.Left; set => this.horizontalAlignment = value; } + + /// + /// Gets or sets a value indicating how to align the text relative to the rendering space. + /// + public VerticalAlignment VerticalAlignment { get => this.verticalAlignment ?? VerticalAlignment.Top; set => this.verticalAlignment = value; } /// /// Performs an implicit conversion from to . diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index 6c8c62039..9c4dee503 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -157,10 +157,10 @@ namespace ImageSharp { int width = bitmap.Width; int height = bitmap.Height; - Point topLeft = default(Point); - Point bottomRight = default(Point); + var topLeft = default(Point); + var bottomRight = default(Point); - Func, int, int, float, bool> delegateFunc; + Func, int, int, float, bool> delegateFunc; // Determine which channel to check against switch (channel) @@ -182,7 +182,7 @@ namespace ImageSharp break; } - int GetMinY(PixelAccessor pixels) + int GetMinY(ImageBase pixels) { for (int y = 0; y < height; y++) { @@ -198,7 +198,7 @@ namespace ImageSharp return 0; } - int GetMaxY(PixelAccessor pixels) + int GetMaxY(ImageBase pixels) { for (int y = height - 1; y > -1; y--) { @@ -214,7 +214,7 @@ namespace ImageSharp return height; } - int GetMinX(PixelAccessor pixels) + int GetMinX(ImageBase pixels) { for (int x = 0; x < width; x++) { @@ -230,7 +230,7 @@ namespace ImageSharp return 0; } - int GetMaxX(PixelAccessor pixels) + int GetMaxX(ImageBase pixels) { for (int x = width - 1; x > -1; x--) { @@ -246,13 +246,10 @@ namespace ImageSharp return height; } - using (PixelAccessor bitmapPixels = bitmap.Lock()) - { - topLeft.Y = GetMinY(bitmapPixels); - topLeft.X = GetMinX(bitmapPixels); - bottomRight.Y = (GetMaxY(bitmapPixels) + 1).Clamp(0, height); - bottomRight.X = (GetMaxX(bitmapPixels) + 1).Clamp(0, width); - } + topLeft.Y = GetMinY(bitmap); + topLeft.X = GetMinX(bitmap); + bottomRight.Y = (GetMaxY(bitmap) + 1).Clamp(0, height); + bottomRight.X = (GetMaxX(bitmap) + 1).Clamp(0, width); return GetBoundingRectangle(topLeft, bottomRight); } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 7a5fabdb3..408e6c383 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Dithering { + using System; using System.Numerics; using System.Runtime.CompilerServices; @@ -71,7 +72,7 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + public void Dither(ImageBase pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel { this.Dither(pixels, source, transformed, x, y, width, height, true); @@ -79,13 +80,13 @@ namespace ImageSharp.Dithering /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + public void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel { if (replacePixel) { // Assign the transformed pixel to the array. - pixels[x, y] = transformed; + image[x, y] = transformed; } // Calculate the error @@ -95,30 +96,33 @@ namespace ImageSharp.Dithering for (int row = 0; row < this.matrixHeight; row++) { int matrixY = y + row; - - for (int col = 0; col < this.matrixWidth; col++) + if (matrixY > 0 && matrixY < height) { - int matrixX = x + (col - this.startingOffset); + Span rowSpan = image.GetRowSpan(matrixY); - if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) + for (int col = 0; col < this.matrixWidth; col++) { - float coefficient = this.matrix[row, col]; + int matrixX = x + (col - this.startingOffset); - // Good to disable here as we are not comparing mathematical output. - // ReSharper disable once CompareOfFloatsByEqualityOperator - if (coefficient == 0) + if (matrixX > 0 && matrixX < width) { - continue; + float coefficient = this.matrix[row, col]; + + // Good to disable here as we are not comparing mathematical output. + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (coefficient == 0) + { + continue; + } + + ref TPixel pixel = ref rowSpan[matrixX]; + var offsetColor = pixel.ToVector4(); + var coefficientVector = new Vector4(coefficient); + + Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; + result.W = offsetColor.W; + pixel.PackFromVector4(result); } - - Vector4 coefficientVector = new Vector4(coefficient); - Vector4 offsetColor = pixels[matrixX, matrixY].ToVector4(); - Vector4 result = ((error * coefficientVector) / this.divisorVector) + offsetColor; - result.W = offsetColor.W; - - TPixel packed = default(TPixel); - packed.PackFromVector4(result); - pixels[matrixX, matrixY] = packed; } } } diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index f49e7e62d..bc785e897 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -23,13 +23,13 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height) where TPixel : struct, IPixel; /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The transformed pixel /// The column index. @@ -41,7 +41,7 @@ namespace ImageSharp.Dithering /// Generally this would be true for standard two-color dithering but when used in conjunction with color quantization this should be false. /// /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) + void Dither(ImageBase image, TPixel source, TPixel transformed, int x, int y, int width, int height, bool replacePixel) where TPixel : struct, IPixel; } } diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 3f7cf4988..c69cddefe 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Dithering /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// - /// The pixel accessor + /// The image /// The source pixel /// The color to apply to the pixels above the threshold. /// The color to apply to the pixels below the threshold. @@ -26,7 +26,7 @@ namespace ImageSharp.Dithering /// The image width. /// The image height. /// The pixel format. - void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel; } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs index 48d6c3f6a..a180888f7 100644 --- a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs @@ -28,14 +28,14 @@ namespace ImageSharp.Dithering.Ordered } /// - public void Dither(PixelAccessor pixels, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) + public void Dither(ImageBase image, TPixel source, TPixel upper, TPixel lower, byte[] bytes, int index, int x, int y, int width, int height) where TPixel : struct, IPixel { // TODO: This doesn't really cut it for me. - // I'd rather be using float but we need to add some sort of movalization vector methods to all IPixel implementations + // I'd rather be using float but we need to add some sort of normalization vector methods to all IPixel implementations // before we can do that as the vectors all cover different ranges. source.ToXyzwBytes(bytes, 0); - pixels[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; + image[x, y] = this.matrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 272e4d075..618d268f7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -191,7 +191,7 @@ namespace ImageSharp.Formats byte packed = this.buffer[8]; - GifImageDescriptor imageDescriptor = new GifImageDescriptor + var imageDescriptor = new GifImageDescriptor { Left = BitConverter.ToInt16(this.buffer, 0), Top = BitConverter.ToInt16(this.buffer, 2), @@ -337,7 +337,7 @@ namespace ImageSharp.Formats private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) { int dataSize = this.currentStream.ReadByte(); - using (LzwDecoder lzwDecoder = new LzwDecoder(this.currentStream)) + using (var lzwDecoder = new LzwDecoder(this.currentStream)) { lzwDecoder.DecodePixels(imageDescriptor.Width, imageDescriptor.Height, dataSize, indices); } @@ -396,62 +396,60 @@ namespace ImageSharp.Formats int interlaceIncrement = 8; // The interlacing line increment int interlaceY = 0; // The current interlaced line - using (PixelAccessor pixelAccessor = image.Lock()) + for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) { - for (int y = descriptor.Top; y < descriptor.Top + descriptor.Height; y++) + // Check if this image is interlaced. + int writeY; // the target y offset to write to + if (descriptor.InterlaceFlag) { - // Check if this image is interlaced. - int writeY; // the target y offset to write to - if (descriptor.InterlaceFlag) + // If so then we read lines at predetermined offsets. + // When an entire image height worth of offset lines has been read we consider this a pass. + // With each pass the number of offset lines changes and the starting line changes. + if (interlaceY >= descriptor.Height) { - // If so then we read lines at predetermined offsets. - // When an entire image height worth of offset lines has been read we consider this a pass. - // With each pass the number of offset lines changes and the starting line changes. - if (interlaceY >= descriptor.Height) + interlacePass++; + switch (interlacePass) { - interlacePass++; - switch (interlacePass) - { - case 1: - interlaceY = 4; - break; - case 2: - interlaceY = 2; - interlaceIncrement = 4; - break; - case 3: - interlaceY = 1; - interlaceIncrement = 2; - break; - } + case 1: + interlaceY = 4; + break; + case 2: + interlaceY = 2; + interlaceIncrement = 4; + break; + case 3: + interlaceY = 1; + interlaceIncrement = 2; + break; } + } - writeY = interlaceY + descriptor.Top; + writeY = interlaceY + descriptor.Top; - interlaceY += interlaceIncrement; - } - else - { - writeY = y; - } + interlaceY += interlaceIncrement; + } + else + { + writeY = y; + } - for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) - { - int index = indices[i]; + Span rowSpan = image.GetRowSpan(writeY); - if (this.graphicsControlExtension == null || - this.graphicsControlExtension.TransparencyFlag == false || - this.graphicsControlExtension.TransparencyIndex != index) - { - int indexOffset = index * 3; + for (int x = descriptor.Left; x < descriptor.Left + descriptor.Width; x++) + { + int index = indices[i]; - TPixel pixel = default(TPixel); - pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); - pixelAccessor[x, writeY] = pixel; - } + if (this.graphicsControlExtension == null || + this.graphicsControlExtension.TransparencyFlag == false || + this.graphicsControlExtension.TransparencyIndex != index) + { + int indexOffset = index * 3; - i++; + ref TPixel pixel = ref rowSpan[x]; + pixel.PackFromBytes(colorTable[indexOffset], colorTable[indexOffset + 1], colorTable[indexOffset + 2], 255); } + + i++; } } @@ -492,7 +490,7 @@ namespace ImageSharp.Formats } else { - using (PixelArea emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) + using (var emptyRow = new PixelArea(this.restoreArea.Value.Width, ComponentOrder.Xyzw)) { using (PixelAccessor pixelAccessor = frame.Lock()) { diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 5dc1a150d..5ef7ca165 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Formats this.Quantizer = this.options.Quantizer ?? new OctreeQuantizer(); // Do not use IDisposable pattern here as we want to preserve the stream. - EndianBinaryWriter writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); + var writer = new EndianBinaryWriter(Endianness.LittleEndian, stream); // Ensure that quality can be set but has a fallback. int quality = this.options.Quality > 0 ? this.options.Quality : image.MetaData.Quality; @@ -82,7 +82,7 @@ namespace ImageSharp.Formats this.hasFrames = image.Frames.Any(); // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. - IQuantizer ditheredQuantizer = (IQuantizer)this.Quantizer; + var ditheredQuantizer = (IQuantizer)this.Quantizer; ditheredQuantizer.Dither = !this.hasFrames; QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); @@ -96,7 +96,7 @@ namespace ImageSharp.Formats this.WriteLogicalScreenDescriptor(image, writer, index); // Write the first frame. - this.WriteGraphicalControlExtension(image, writer, index); + this.WriteGraphicalControlExtension(image.MetaData, writer, index); this.WriteComments(image, writer); this.WriteImageDescriptor(image, writer); this.WriteColorTable(quantized, writer); @@ -113,7 +113,7 @@ namespace ImageSharp.Formats ImageFrame frame = image.Frames[i]; QuantizedImage quantizedFrame = ditheredQuantizer.Quantize(frame, quality); - this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); + this.WriteGraphicalControlExtension(frame.MetaData, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); this.WriteColorTable(quantizedFrame, writer); this.WriteImageData(quantizedFrame, writer); @@ -137,31 +137,20 @@ namespace ImageSharp.Formats private int GetTransparentIndex(QuantizedImage quantized) where TPixel : struct, IPixel { - // Find the lowest alpha value and make it the transparent index. - int index = 255; - byte alpha = 255; - bool hasEmpty = false; - - // Some images may have more than one quantized pixel returned with an alpha value of zero - // so we should always ignore if we have empty pixels present. - for (int i = 0; i < quantized.Palette.Length; i++) + // Transparent pixels are much more likely to be found at the end of a palette + int index = -1; + for (int i = quantized.Palette.Length - 1; i >= 0; i--) { quantized.Palette[i].ToXyzwBytes(this.buffer, 0); - if (!hasEmpty) + if (this.buffer[3] > 0) { - if (this.buffer[0] == 0 && this.buffer[1] == 0 && this.buffer[2] == 0 && this.buffer[3] == 0) - { - alpha = this.buffer[3]; - index = i; - hasEmpty = true; - } - - if (this.buffer[3] < alpha) - { - alpha = this.buffer[3]; - index = i; - } + continue; + } + else + { + index = i; + break; } } @@ -183,23 +172,23 @@ namespace ImageSharp.Formats /// The pixel format. /// The image to encode. /// The writer to write to the stream with. - /// The transparency index to set the default background index to. - private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int tranparencyIndex) + /// The transparency index to set the default background index to. + private void WriteLogicalScreenDescriptor(Image image, EndianBinaryWriter writer, int transparencyIndex) where TPixel : struct, IPixel { - GifLogicalScreenDescriptor descriptor = new GifLogicalScreenDescriptor + var descriptor = new GifLogicalScreenDescriptor { Width = (short)image.Width, Height = (short)image.Height, GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableSize = this.bitDepth - 1, - BackgroundColorIndex = (byte)tranparencyIndex + BackgroundColorIndex = unchecked((byte)transparencyIndex) }; writer.Write((ushort)descriptor.Width); writer.Write((ushort)descriptor.Height); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBit(0, descriptor.GlobalColorTableFlag); // 1 : Global color table flag = 1 || 0 (GCT used/ not used) field.SetBits(1, 3, descriptor.GlobalColorTableSize); // 2-4 : color resolution field.SetBit(4, false); // 5 : GCT sort flag = 0 @@ -251,7 +240,7 @@ namespace ImageSharp.Formats private void WriteComments(Image image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - if (this.options.IgnoreMetadata == true) + if (this.options.IgnoreMetadata) { return; } @@ -278,45 +267,16 @@ namespace ImageSharp.Formats /// /// Writes the graphics control extension to the stream. /// - /// The pixel format. - /// The to encode. - /// The stream to write to. - /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(Image image, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel - { - this.WriteGraphicalControlExtension(image, image.MetaData, writer, transparencyIndex); - } - - /// - /// Writes the graphics control extension to the stream. - /// - /// The pixel format. - /// The to encode. - /// The stream to write to. - /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageFrame imageFrame, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel - { - this.WriteGraphicalControlExtension(imageFrame, imageFrame.MetaData, writer, transparencyIndex); - } - - /// - /// Writes the graphics control extension to the stream. - /// - /// The pixel format. - /// The to encode. /// The metadata of the image or frame. /// The stream to write to. /// The index of the color in the color palette to make transparent. - private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) - where TPixel : struct, IPixel + private void WriteGraphicalControlExtension(IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) { - GifGraphicsControlExtension extension = new GifGraphicsControlExtension() + var extension = new GifGraphicsControlExtension { DisposalMethod = metaData.DisposalMethod, - TransparencyFlag = transparencyIndex < 255, - TransparencyIndex = transparencyIndex, + TransparencyFlag = transparencyIndex > -1, + TransparencyIndex = unchecked((byte)transparencyIndex), DelayTime = metaData.FrameDelay }; @@ -326,7 +286,7 @@ namespace ImageSharp.Formats this.buffer[2] = 4; writer.Write(this.buffer, 0, 3); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBits(3, 3, (int)extension.DisposalMethod); // 1-3 : Reserved, 4-6 : Disposal // TODO: Allow this as an option. @@ -335,7 +295,7 @@ namespace ImageSharp.Formats writer.Write(field.Byte); writer.Write((ushort)extension.DelayTime); - writer.Write((byte)extension.TransparencyIndex); + writer.Write(extension.TransparencyIndex); writer.Write(GifConstants.Terminator); } @@ -356,7 +316,7 @@ namespace ImageSharp.Formats writer.Write((ushort)image.Width); writer.Write((ushort)image.Height); - PackedField field = default(PackedField); + var field = default(PackedField); field.SetBit(0, true); // 1: Local color table flag = 1 (LCT used) field.SetBit(1, false); // 2: Interlace flag 0 field.SetBit(2, false); // 3: Sort flag 0 @@ -409,7 +369,7 @@ namespace ImageSharp.Formats private void WriteImageData(QuantizedImage image, EndianBinaryWriter writer) where TPixel : struct, IPixel { - using (LzwEncoder encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) + using (var encoder = new LzwEncoder(image.Pixels, (byte)this.bitDepth)) { encoder.Encode(writer.BaseStream); } diff --git a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs index 79d98f5fb..503bd4fdf 100644 --- a/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs +++ b/src/ImageSharp/Formats/Gif/Sections/GifGraphicsControlExtension.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Formats /// The Transparency Index is such that when encountered, the corresponding pixel /// of the display device is not modified and processing goes on to the next pixel. /// - public int TransparencyIndex { get; set; } + public byte TransparencyIndex { get; set; } /// /// Gets or sets the delay time. diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs index 211b66dac..f84dc977f 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.cs @@ -5,53 +5,120 @@ // ReSharper disable InconsistentNaming // #pragma warning disable -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - - namespace ImageSharp.Formats.Jpg { + using System.Numerics; + using System.Runtime.CompilerServices; + internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(-128f); - private static readonly Vector4 CMax4 = new Vector4(127f); - private static readonly Vector4 COff4 = new Vector4(128f); + private static readonly Vector4 CMin4 = new Vector4(0F); + private static readonly Vector4 CMax4 = new Vector4(255F); + private static readonly Vector4 COff4 = new Vector4(128F); /// - /// Transpose the block into d + /// Transpose the block into the destination block. /// - /// Destination + /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TransposeInto(ref Block8x8F d) { - d.V0L.X = V0L.X; d.V1L.X = V0L.Y; d.V2L.X = V0L.Z; d.V3L.X = V0L.W; d.V4L.X = V0R.X; d.V5L.X = V0R.Y; d.V6L.X = V0R.Z; d.V7L.X = V0R.W; - d.V0L.Y = V1L.X; d.V1L.Y = V1L.Y; d.V2L.Y = V1L.Z; d.V3L.Y = V1L.W; d.V4L.Y = V1R.X; d.V5L.Y = V1R.Y; d.V6L.Y = V1R.Z; d.V7L.Y = V1R.W; - d.V0L.Z = V2L.X; d.V1L.Z = V2L.Y; d.V2L.Z = V2L.Z; d.V3L.Z = V2L.W; d.V4L.Z = V2R.X; d.V5L.Z = V2R.Y; d.V6L.Z = V2R.Z; d.V7L.Z = V2R.W; - d.V0L.W = V3L.X; d.V1L.W = V3L.Y; d.V2L.W = V3L.Z; d.V3L.W = V3L.W; d.V4L.W = V3R.X; d.V5L.W = V3R.Y; d.V6L.W = V3R.Z; d.V7L.W = V3R.W; - d.V0R.X = V4L.X; d.V1R.X = V4L.Y; d.V2R.X = V4L.Z; d.V3R.X = V4L.W; d.V4R.X = V4R.X; d.V5R.X = V4R.Y; d.V6R.X = V4R.Z; d.V7R.X = V4R.W; - d.V0R.Y = V5L.X; d.V1R.Y = V5L.Y; d.V2R.Y = V5L.Z; d.V3R.Y = V5L.W; d.V4R.Y = V5R.X; d.V5R.Y = V5R.Y; d.V6R.Y = V5R.Z; d.V7R.Y = V5R.W; - d.V0R.Z = V6L.X; d.V1R.Z = V6L.Y; d.V2R.Z = V6L.Z; d.V3R.Z = V6L.W; d.V4R.Z = V6R.X; d.V5R.Z = V6R.Y; d.V6R.Z = V6R.Z; d.V7R.Z = V6R.W; - d.V0R.W = V7L.X; d.V1R.W = V7L.Y; d.V2R.W = V7L.Z; d.V3R.W = V7L.W; d.V4R.W = V7R.X; d.V5R.W = V7R.Y; d.V6R.W = V7R.Z; d.V7R.W = V7R.W; + d.V0L.X = V0L.X; + d.V1L.X = V0L.Y; + d.V2L.X = V0L.Z; + d.V3L.X = V0L.W; + d.V4L.X = V0R.X; + d.V5L.X = V0R.Y; + d.V6L.X = V0R.Z; + d.V7L.X = V0R.W; + + d.V0L.Y = V1L.X; + d.V1L.Y = V1L.Y; + d.V2L.Y = V1L.Z; + d.V3L.Y = V1L.W; + d.V4L.Y = V1R.X; + d.V5L.Y = V1R.Y; + d.V6L.Y = V1R.Z; + d.V7L.Y = V1R.W; + + d.V0L.Z = V2L.X; + d.V1L.Z = V2L.Y; + d.V2L.Z = V2L.Z; + d.V3L.Z = V2L.W; + d.V4L.Z = V2R.X; + d.V5L.Z = V2R.Y; + d.V6L.Z = V2R.Z; + d.V7L.Z = V2R.W; + + d.V0L.W = V3L.X; + d.V1L.W = V3L.Y; + d.V2L.W = V3L.Z; + d.V3L.W = V3L.W; + d.V4L.W = V3R.X; + d.V5L.W = V3R.Y; + d.V6L.W = V3R.Z; + d.V7L.W = V3R.W; + + d.V0R.X = V4L.X; + d.V1R.X = V4L.Y; + d.V2R.X = V4L.Z; + d.V3R.X = V4L.W; + d.V4R.X = V4R.X; + d.V5R.X = V4R.Y; + d.V6R.X = V4R.Z; + d.V7R.X = V4R.W; + + d.V0R.Y = V5L.X; + d.V1R.Y = V5L.Y; + d.V2R.Y = V5L.Z; + d.V3R.Y = V5L.W; + d.V4R.Y = V5R.X; + d.V5R.Y = V5R.Y; + d.V6R.Y = V5R.Z; + d.V7R.Y = V5R.W; + + d.V0R.Z = V6L.X; + d.V1R.Z = V6L.Y; + d.V2R.Z = V6L.Z; + d.V3R.Z = V6L.W; + d.V4R.Z = V6R.X; + d.V5R.Z = V6R.Y; + d.V6R.Z = V6R.Z; + d.V7R.Z = V6R.W; + + d.V0R.W = V7L.X; + d.V1R.W = V7L.Y; + d.V2R.W = V7L.Z; + d.V3R.W = V7L.W; + d.V4R.W = V7R.X; + d.V5R.W = V7R.Y; + d.V6R.W = V7R.Z; + d.V7R.W = V7R.W; } /// /// Level shift by +128, clip to [0, 255] /// - /// Destination + /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) { - d.V0L = Vector4.Max(Vector4.Min(V0L, CMax4), CMin4) + COff4;d.V0R = Vector4.Max(Vector4.Min(V0R, CMax4), CMin4) + COff4; - d.V1L = Vector4.Max(Vector4.Min(V1L, CMax4), CMin4) + COff4;d.V1R = Vector4.Max(Vector4.Min(V1R, CMax4), CMin4) + COff4; - d.V2L = Vector4.Max(Vector4.Min(V2L, CMax4), CMin4) + COff4;d.V2R = Vector4.Max(Vector4.Min(V2R, CMax4), CMin4) + COff4; - d.V3L = Vector4.Max(Vector4.Min(V3L, CMax4), CMin4) + COff4;d.V3R = Vector4.Max(Vector4.Min(V3R, CMax4), CMin4) + COff4; - d.V4L = Vector4.Max(Vector4.Min(V4L, CMax4), CMin4) + COff4;d.V4R = Vector4.Max(Vector4.Min(V4R, CMax4), CMin4) + COff4; - d.V5L = Vector4.Max(Vector4.Min(V5L, CMax4), CMin4) + COff4;d.V5R = Vector4.Max(Vector4.Min(V5R, CMax4), CMin4) + COff4; - d.V6L = Vector4.Max(Vector4.Min(V6L, CMax4), CMin4) + COff4;d.V6R = Vector4.Max(Vector4.Min(V6R, CMax4), CMin4) + COff4; - d.V7L = Vector4.Max(Vector4.Min(V7L, CMax4), CMin4) + COff4;d.V7R = Vector4.Max(Vector4.Min(V7R, CMax4), CMin4) + COff4; + d.V0L = Vector4.Clamp(V0L + COff4, CMin4, CMax4); + d.V0R = Vector4.Clamp(V0R + COff4, CMin4, CMax4); + d.V1L = Vector4.Clamp(V1L + COff4, CMin4, CMax4); + d.V1R = Vector4.Clamp(V1R + COff4, CMin4, CMax4); + d.V2L = Vector4.Clamp(V2L + COff4, CMin4, CMax4); + d.V2R = Vector4.Clamp(V2R + COff4, CMin4, CMax4); + d.V3L = Vector4.Clamp(V3L + COff4, CMin4, CMax4); + d.V3R = Vector4.Clamp(V3R + COff4, CMin4, CMax4); + d.V4L = Vector4.Clamp(V4L + COff4, CMin4, CMax4); + d.V4R = Vector4.Clamp(V4R + COff4, CMin4, CMax4); + d.V5L = Vector4.Clamp(V5L + COff4, CMin4, CMax4); + d.V5R = Vector4.Clamp(V5R + COff4, CMin4, CMax4); + d.V6L = Vector4.Clamp(V6L + COff4, CMin4, CMax4); + d.V6R = Vector4.Clamp(V6R + COff4, CMin4, CMax4); + d.V7L = Vector4.Clamp(V7L + COff4, CMin4, CMax4); + d.V7R = Vector4.Clamp(V7R + COff4, CMin4, CMax4); } - - } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt index be198a6fa..03566acbb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Generated.tt @@ -11,31 +11,29 @@ <#@ output extension=".cs" #> // #pragma warning disable -using System; -using System.Numerics; -using System.Runtime.CompilerServices; - <# char[] coordz = {'X', 'Y', 'Z', 'W'}; #> - namespace ImageSharp.Formats.Jpg { + using System.Numerics; + using System.Runtime.CompilerServices; + internal partial struct Block8x8F { - private static readonly Vector4 CMin4 = new Vector4(-128f); - private static readonly Vector4 CMax4 = new Vector4(127f); - private static readonly Vector4 COff4 = new Vector4(128f); + private static readonly Vector4 CMin4 = new Vector4(0F); + private static readonly Vector4 CMax4 = new Vector4(255F); + private static readonly Vector4 COff4 = new Vector4(128F); /// - /// Transpose the block into d + /// Transpose the block into the destination block. /// - /// Destination + /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] public void TransposeInto(ref Block8x8F d) { <# - PushIndent(" "); + PushIndent(" "); for (int i = 0; i < 8; i++) { @@ -44,13 +42,16 @@ namespace ImageSharp.Formats.Jpg for (int j = 0; j < 8; j++) { + if(i > 0 && j == 0){ + WriteLine(""); + } + char srcCoord = coordz[j % 4]; char srcSide = (j / 4) % 2 == 0 ? 'L' : 'R'; - - string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord}; "; + + string expression = $"d.V{j}{destSide}.{destCoord} = V{i}{srcSide}.{srcCoord};\r\n"; Write(expression); } - WriteLine(""); } PopIndent(); #> @@ -59,27 +60,24 @@ namespace ImageSharp.Formats.Jpg /// /// Level shift by +128, clip to [0, 255] /// - /// Destination + /// The destination block [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void TransformByteConvetibleColorValuesInto(ref Block8x8F d) { <# - PushIndent(" "); + PushIndent(" "); for (int i = 0; i < 8; i++) { for (int j = 0; j < 2; j++) { char side = j == 0 ? 'L' : 'R'; - Write($"d.V{i}{side} = Vector4.Max(Vector4.Min(V{i}{side}, CMax4), CMin4) + COff4;"); + Write($"d.V{i}{side} = Vector4.Clamp(V{i}{side} + COff4, CMin4, CMax4);\r\n"); } - WriteLine(""); } PopIndent(); #> } - - } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 56466d7a0..130b5856c 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -57,6 +57,9 @@ namespace ImageSharp.Formats.Jpg public Vector4 V7R; #pragma warning restore SA1600 // ElementsMustBeDocumented + private static readonly Vector4 NegativeOne = new Vector4(-1); + private static readonly Vector4 Offset = new Vector4(.5F); + /// /// Get/Set scalar elements at a given index /// @@ -402,12 +405,11 @@ namespace ImageSharp.Formats.Jpg [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor) { - // sign(v) = max(min(v, 1), -1) - Vector4 sign = Vector4.Min(dividend, Vector4.One); - sign = Vector4.Max(sign, new Vector4(-1)); + // sign(dividend) = max(min(dividend, 1), -1) + var sign = Vector4.Clamp(dividend, NegativeOne, Vector4.One); // AlmostRound(dividend/divisor) = dividend/divisior + 0.5*sign(dividend) - return (dividend / divisor) + (sign * new Vector4(0.5f)); + return (dividend / divisor) + (sign * Offset); } } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 13c51db24..d65c095da 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -982,6 +982,7 @@ namespace ImageSharp.Formats byte[] identifier = new byte[Icclength]; this.InputProcessor.ReadFull(identifier, 0, Icclength); + remaining -= Icclength; // we have read it by this point if (identifier[0] == 'I' && identifier[1] == 'C' && @@ -996,7 +997,6 @@ namespace ImageSharp.Formats identifier[10] == 'E' && identifier[11] == '\0') { - remaining -= Icclength; byte[] profile = new byte[remaining]; this.InputProcessor.ReadFull(profile, 0, remaining); @@ -1009,6 +1009,11 @@ namespace ImageSharp.Formats metadata.IccProfile.Extend(profile); } } + else + { + // not an ICC profile we can handle read the remaining so we can carry on and ignore this. + this.InputProcessor.Skip(remaining); + } } /// diff --git a/src/ImageSharp/Formats/Jpeg/itu-t81.pdf b/src/ImageSharp/Formats/Jpeg/itu-t81.pdf new file mode 100644 index 000000000..83fba254b Binary files /dev/null and b/src/ImageSharp/Formats/Jpeg/itu-t81.pdf differ diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index f8be0f6cc..b18845aaa 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -178,7 +178,7 @@ namespace ImageSharp.Formats /// /// Thrown if the stream does not contain and end chunk. /// - /// + /// /// Thrown if the image is larger than the maximum allowable size. /// /// The decoded image @@ -189,7 +189,6 @@ namespace ImageSharp.Formats this.currentStream = stream; this.currentStream.Skip(8); Image image = null; - PixelAccessor pixels = null; try { using (var deframeStream = new ZlibInflateStream(this.currentStream)) @@ -211,11 +210,11 @@ namespace ImageSharp.Formats case PngChunkTypes.Data: if (image == null) { - this.InitializeImage(metadata, out image, out pixels); + this.InitializeImage(metadata, out image); } deframeStream.AllocateNewBytes(currentChunk.Length); - this.ReadScanlines(deframeStream.CompressedStream, pixels); + this.ReadScanlines(deframeStream.CompressedStream, image); stream.Read(this.crcBuffer, 0, 4); break; case PngChunkTypes.Palette: @@ -252,7 +251,6 @@ namespace ImageSharp.Formats } finally { - pixels?.Dispose(); this.scanline?.Dispose(); this.previousScanline?.Dispose(); } @@ -324,8 +322,7 @@ namespace ImageSharp.Formats /// The type the pixels will be /// The metadata information for the image /// The image that we will populate - /// The pixel accessor - private void InitializeImage(ImageMetaData metadata, out Image image, out PixelAccessor pixels) + private void InitializeImage(ImageMetaData metadata, out Image image) where TPixel : struct, IPixel { if (this.header.Width > Image.MaxWidth || this.header.Height > Image.MaxHeight) @@ -334,7 +331,6 @@ namespace ImageSharp.Formats } image = new Image(this.configuration, this.header.Width, this.header.Height, metadata); - pixels = image.Lock(); this.bytesPerPixel = this.CalculateBytesPerPixel(); this.bytesPerScanline = this.CalculateScanlineLength(this.header.Width) + 1; this.bytesPerSample = 1; @@ -398,17 +394,17 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The containing data. - /// The pixel data. - private void ReadScanlines(Stream dataStream, PixelAccessor pixels) + /// The pixel data. + private void ReadScanlines(Stream dataStream, Image image) where TPixel : struct, IPixel { if (this.header.InterlaceMethod == PngInterlaceMode.Adam7) { - this.DecodeInterlacedPixelData(dataStream, pixels); + this.DecodeInterlacedPixelData(dataStream, image); } else { - this.DecodePixelData(dataStream, pixels); + this.DecodePixelData(dataStream, image); } } @@ -417,8 +413,8 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The compressed pixel data stream. - /// The image pixel accessor. - private void DecodePixelData(Stream compressedStream, PixelAccessor pixels) + /// The image to decode to. + private void DecodePixelData(Stream compressedStream, Image image) where TPixel : struct, IPixel { while (this.currentRow < this.header.Height) @@ -462,7 +458,7 @@ namespace ImageSharp.Formats throw new ImageFormatException("Unknown filter type."); } - this.ProcessDefilteredScanline(this.scanline.Array, pixels); + this.ProcessDefilteredScanline(this.scanline.Array, image); Swap(ref this.scanline, ref this.previousScanline); this.currentRow++; @@ -475,8 +471,8 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The compressed pixel data stream. - /// The image pixel accessor. - private void DecodeInterlacedPixelData(Stream compressedStream, PixelAccessor pixels) + /// The current image. + private void DecodeInterlacedPixelData(Stream compressedStream, Image image) where TPixel : struct, IPixel { while (true) @@ -537,7 +533,8 @@ namespace ImageSharp.Formats throw new ImageFormatException("Unknown filter type."); } - this.ProcessInterlacedDefilteredScanline(this.scanline.Array, this.currentRow, pixels, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); + Span rowSpan = image.GetRowSpan(this.currentRow); + this.ProcessInterlacedDefilteredScanline(this.scanline.Array, rowSpan, Adam7FirstColumn[this.pass], Adam7ColumnIncrement[this.pass]); Swap(ref this.scanline, ref this.previousScanline); @@ -561,12 +558,12 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The de-filtered scanline - /// The image pixels - private void ProcessDefilteredScanline(byte[] defilteredScanline, PixelAccessor pixels) + /// The image + private void ProcessDefilteredScanline(byte[] defilteredScanline, Image pixels) where TPixel : struct, IPixel { var color = default(TPixel); - Span pixelBuffer = pixels.GetRowSpan(this.currentRow); + Span rowSpan = pixels.GetRowSpan(this.currentRow); var scanlineBuffer = new Span(defilteredScanline, 1); switch (this.PngColorType) @@ -578,7 +575,7 @@ namespace ImageSharp.Formats { byte intensity = (byte)(newScanline1[x] * factor); color.PackFromBytes(intensity, intensity, intensity, 255); - pixels[x, this.currentRow] = color; + rowSpan[x] = color; } break; @@ -593,26 +590,26 @@ namespace ImageSharp.Formats byte alpha = defilteredScanline[offset + this.bytesPerSample]; color.PackFromBytes(intensity, intensity, intensity, alpha); - pixels[x, this.currentRow] = color; + rowSpan[x] = color; } break; case PngColorType.Palette: - this.ProcessScanlineFromPalette(defilteredScanline, pixels); + this.ProcessScanlineFromPalette(defilteredScanline, rowSpan); break; case PngColorType.Rgb: - PixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, rowSpan, this.header.Width); break; case PngColorType.RgbWithAlpha: - PixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, rowSpan, this.header.Width); break; } @@ -623,8 +620,8 @@ namespace ImageSharp.Formats /// /// The type of pixel we are expanding to /// The scanline - /// The output pixels - private void ProcessScanlineFromPalette(byte[] defilteredScanline, PixelAccessor pixels) + /// Thecurrent output image row + private void ProcessScanlineFromPalette(byte[] defilteredScanline, Span row) where TPixel : struct, IPixel { byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); @@ -654,7 +651,7 @@ namespace ImageSharp.Formats color.PackFromBytes(0, 0, 0, 0); } - pixels[x, this.currentRow] = color; + row[x] = color; } } else @@ -669,7 +666,7 @@ namespace ImageSharp.Formats byte b = palette[pixelOffset + 2]; color.PackFromBytes(r, g, b, 255); - pixels[x, this.currentRow] = color; + row[x] = color; } } } @@ -679,11 +676,10 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The de-filtered scanline - /// The current image row. - /// The image pixels + /// The current image row. /// The column start index. Always 0 for none interlaced images. /// The column increment. Always 1 for none interlaced images. - private void ProcessInterlacedDefilteredScanline(byte[] defilteredScanline, int row, PixelAccessor pixels, int pixelOffset = 0, int increment = 1) + private void ProcessInterlacedDefilteredScanline(byte[] defilteredScanline, Span rowSpan, int pixelOffset = 0, int increment = 1) where TPixel : struct, IPixel { var color = default(TPixel); @@ -697,7 +693,7 @@ namespace ImageSharp.Formats { byte intensity = (byte)(newScanline1[o] * factor); color.PackFromBytes(intensity, intensity, intensity, 255); - pixels[x, row] = color; + rowSpan[x] = color; } break; @@ -710,7 +706,7 @@ namespace ImageSharp.Formats byte alpha = defilteredScanline[o + this.bytesPerSample]; color.PackFromBytes(intensity, intensity, intensity, alpha); - pixels[x, row] = color; + rowSpan[x] = color; } break; @@ -742,7 +738,7 @@ namespace ImageSharp.Formats color.PackFromBytes(0, 0, 0, 0); } - pixels[x, row] = color; + rowSpan[x] = color; } } else @@ -757,7 +753,7 @@ namespace ImageSharp.Formats byte b = this.palette[offset + 2]; color.PackFromBytes(r, g, b, 255); - pixels[x, row] = color; + rowSpan[x] = color; } } @@ -772,7 +768,7 @@ namespace ImageSharp.Formats byte b = defilteredScanline[o + (2 * this.bytesPerSample)]; color.PackFromBytes(r, g, b, 255); - pixels[x, row] = color; + rowSpan[x] = color; } break; @@ -787,7 +783,7 @@ namespace ImageSharp.Formats byte a = defilteredScanline[o + (3 * this.bytesPerSample)]; color.PackFromBytes(r, g, b, a); - pixels[x, row] = color; + rowSpan[x] = color; } break; diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 0482b2691..3fcf1fc81 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -220,11 +220,7 @@ namespace ImageSharp.Formats this.WritePhysicalChunk(stream, image); this.WriteGammaChunk(stream); - using (PixelAccessor pixels = image.Lock()) - { - this.WriteDataChunks(pixels, stream); - } - + this.WriteDataChunks(image, stream); this.WriteEndChunk(stream); stream.Flush(); } @@ -302,9 +298,8 @@ namespace ImageSharp.Formats /// Collects a row of grayscale pixels. /// /// The pixel format. - /// The image pixels accessor. - /// The row index. - private void CollectGrayscaleBytes(PixelAccessor pixels, int row) + /// The image row span. + private void CollectGrayscaleBytes(Span rowSpan) where TPixel : struct, IPixel { byte[] rawScanlineArray = this.rawScanline.Array; @@ -316,7 +311,7 @@ namespace ImageSharp.Formats // Convert the color to YCbCr and store the luminance // Optionally store the original color alpha. int offset = x * this.bytesPerPixel; - pixels[x, row].ToXyzwBytes(this.chunkTypeBuffer, 0); + rowSpan[x].ToXyzwBytes(this.chunkTypeBuffer, 0); byte luminance = (byte)((0.299F * this.chunkTypeBuffer[0]) + (0.587F * this.chunkTypeBuffer[1]) + (0.114F * this.chunkTypeBuffer[2])); for (int i = 0; i < this.bytesPerPixel; i++) @@ -337,13 +332,10 @@ namespace ImageSharp.Formats /// Collects a row of true color pixel data. /// /// The pixel format. - /// The image pixel accessor. - /// The row index. - private void CollecTPixelBytes(PixelAccessor pixels, int row) + /// The row span. + private void CollecTPixelBytes(Span rowSpan) where TPixel : struct, IPixel { - Span rowSpan = pixels.GetRowSpan(row); - if (this.bytesPerPixel == 4) { PixelOperations.Instance.ToXyzwBytes(rowSpan, this.rawScanline, this.width); @@ -359,10 +351,10 @@ namespace ImageSharp.Formats /// Each scanline is encoded in the most optimal manner to improve compression. /// /// The pixel format. - /// The image pixel accessor. + /// The row span. /// The row. /// The - private Buffer EncodePixelRow(PixelAccessor pixels, int row) + private Buffer EncodePixelRow(Span rowSpan, int row) where TPixel : struct, IPixel { switch (this.pngColorType) @@ -372,10 +364,10 @@ namespace ImageSharp.Formats break; case PngColorType.Grayscale: case PngColorType.GrayscaleWithAlpha: - this.CollectGrayscaleBytes(pixels, row); + this.CollectGrayscaleBytes(rowSpan); break; default: - this.CollecTPixelBytes(pixels, row); + this.CollecTPixelBytes(rowSpan); break; } @@ -637,17 +629,17 @@ namespace ImageSharp.Formats /// Writes the pixel information to the stream. /// /// The pixel format. - /// The pixel accessor. + /// The image. /// The stream. - private void WriteDataChunks(PixelAccessor pixels, Stream stream) + private void WriteDataChunks(Image pixels, Stream stream) where TPixel : struct, IPixel { this.bytesPerScanline = this.width * this.bytesPerPixel; int resultLength = this.bytesPerScanline + 1; - this.previousScanline = new Buffer(this.bytesPerScanline); - this.rawScanline = new Buffer(this.bytesPerScanline); - this.result = new Buffer(resultLength); + this.previousScanline = Buffer.CreateClean(this.bytesPerScanline); + this.rawScanline = Buffer.CreateClean(this.bytesPerScanline); + this.result = Buffer.CreateClean(resultLength); if (this.pngColorType != PngColorType.Palette) { @@ -667,7 +659,7 @@ namespace ImageSharp.Formats { for (int y = 0; y < this.height; y++) { - Buffer r = this.EncodePixelRow(pixels, y); + Buffer r = this.EncodePixelRow(pixels.GetRowSpan(y), y); deflateStream.Write(r.Array, 0, resultLength); Swap(ref this.rawScanline, ref this.previousScanline); diff --git a/src/ImageSharp/Image/IImageBase{TPixel}.cs b/src/ImageSharp/Image/IImageBase{TPixel}.cs index 08d25709b..8b4977b7d 100644 --- a/src/ImageSharp/Image/IImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/IImageBase{TPixel}.cs @@ -16,19 +16,8 @@ namespace ImageSharp where TPixel : struct, IPixel { /// - /// Gets the pixels as an array of the given packed pixel format. - /// Important. Due to the nature in the way this is constructed do not rely on the length - /// of the array for calculations. Use Width * Height. + /// Gets the representation of the pixels as an area of contiguous memory in the given pixel format. /// - TPixel[] Pixels { get; } - - /// - /// Locks the image providing access to the pixels. - /// - /// It is imperative that the accessor is correctly disposed off after use. - /// - /// - /// The - PixelAccessor Lock(); + Span Pixels { get; } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/Image.LoadPixelData.cs b/src/ImageSharp/Image/Image.LoadPixelData.cs new file mode 100644 index 000000000..7b6a4d668 --- /dev/null +++ b/src/ImageSharp/Image/Image.LoadPixelData.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using System.Runtime.CompilerServices; + using System.Threading.Tasks; + using Formats; + using ImageSharp.Memory; + using ImageSharp.PixelFormats; + + /// + /// Adds static methods allowing the creation of new image from raw pixel data. + /// + public static partial class Image + { + /// + /// Create a new instance of the class from the raw data. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(Configuration.Default, data, width, height); + + /// + /// Create a new instance of the class from the given byte array in format. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, Span data, int width, int height) + where TPixel : struct, IPixel + => LoadPixelData(config, data.NonPortableCast(), width, height); + + /// + /// Create a new instance of the class from the raw data. + /// + /// The config for the decoder. + /// The Span containing the image Pixel data. + /// The width of the final image. + /// The height of the final image. + /// The pixel format. + /// A new . + public static Image LoadPixelData(Configuration config, Span data, int width, int height) + where TPixel : struct, IPixel + { + int count = width * height; + Guard.MustBeGreaterThanOrEqualTo(data.Length, count, nameof(data)); + + var image = new Image(config, width, height); + SpanHelper.Copy(data, image.Pixels, count); + + return image; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageBase{TPixel}.cs b/src/ImageSharp/Image/ImageBase{TPixel}.cs index 4fd9d26cb..647d60075 100644 --- a/src/ImageSharp/Image/ImageBase{TPixel}.cs +++ b/src/ImageSharp/Image/ImageBase{TPixel}.cs @@ -7,10 +7,11 @@ namespace ImageSharp { using System; using System.Diagnostics; + using System.Runtime.CompilerServices; using ImageSharp.Memory; using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; /// /// The base class of all images. Encapsulates the basic properties and methods required to manipulate @@ -31,10 +32,12 @@ namespace ImageSharp /// public const int MaxHeight = int.MaxValue; +#pragma warning disable SA1401 // Fields must be private /// - /// The image pixels + /// The image pixels. Not private as Buffer2D requires an array in its constructor. /// - private TPixel[] pixelBuffer; + internal TPixel[] PixelBuffer; +#pragma warning restore SA1401 // Fields must be private /// /// A value indicating whether this instance of the given entity has been disposed. @@ -110,7 +113,7 @@ namespace ImageSharp } /// - public TPixel[] Pixels => this.pixelBuffer; + public Span Pixels => new Span(this.PixelBuffer, 0, this.Width * this.Height); /// public int Width { get; private set; } @@ -129,6 +132,67 @@ namespace ImageSharp /// public Configuration Configuration { get; private set; } + /// + /// Gets or sets the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + public TPixel this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + this.CheckCoordinates(x, y); + return this.PixelBuffer[(y * this.Width) + x]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + this.CheckCoordinates(x, y); + this.PixelBuffer[(y * this.Width) + x] = value; + } + } + + /// + /// Gets a reference to the pixel at the specified position. + /// + /// The x-coordinate of the pixel. Must be greater than or equal to zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than or equal to zero and less than the height of the image. + /// The at the specified position. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref TPixel GetPixelReference(int x, int y) + { + this.CheckCoordinates(x, y); + return ref this.PixelBuffer[(y * this.Width) + x]; + } + + /// + /// Gets a representing the row 'y' beginning from the the first pixel on that row. + /// + /// The y-coordinate of the pixel row. Must be greater than or equal to zero and less than the height of the image. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int y) + { + this.CheckCoordinates(y); + return this.Pixels.Slice(y * this.Width, this.Width); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The x coordinate (position in the row) + /// The y (row) coordinate + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetRowSpan(int x, int y) + { + this.CheckCoordinates(x, y); + return this.Pixels.Slice((y * this.Width) + x, this.Width - x); + } + /// /// Applies the processor. /// @@ -152,12 +216,27 @@ namespace ImageSharp GC.SuppressFinalize(this); } - /// - public PixelAccessor Lock() + /// + /// Locks the image providing access to the pixels. + /// + /// It is imperative that the accessor is correctly disposed off after use. + /// + /// + /// The + internal PixelAccessor Lock() { return new PixelAccessor(this); } + /// + /// Copies the pixels to another of the same size. + /// + /// The target pixel buffer accessor. + internal void CopyTo(PixelAccessor target) + { + SpanHelper.Copy(this.Pixels, target.PixelBuffer.Span); + } + /// /// Switches the buffers used by the image and the PixelAccessor meaning that the Image will "own" the buffer from the PixelAccessor and the PixelAccessor will now own the Images buffer. /// @@ -169,11 +248,11 @@ namespace ImageSharp int newWidth = pixelSource.Width; int newHeight = pixelSource.Height; - // Push my memory into the accessor (which in turn unpins the old puffer ready for the images use) - TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.pixelBuffer); + // Push my memory into the accessor (which in turn unpins the old buffer ready for the images use) + TPixel[] newPixels = pixelSource.ReturnCurrentColorsAndReplaceThemInternally(this.Width, this.Height, this.PixelBuffer); this.Width = newWidth; this.Height = newHeight; - this.pixelBuffer = newPixels; + this.PixelBuffer = newPixels; } /// @@ -224,7 +303,7 @@ namespace ImageSharp /// private void RentPixels() { - this.pixelBuffer = PixelDataPool.Rent(this.Width * this.Height); + this.PixelBuffer = PixelDataPool.Rent(this.Width * this.Height); } /// @@ -232,8 +311,8 @@ namespace ImageSharp /// private void ReturnPixels() { - PixelDataPool.Return(this.pixelBuffer); - this.pixelBuffer = null; + PixelDataPool.Return(this.PixelBuffer); + this.PixelBuffer = null; } /// @@ -241,7 +320,45 @@ namespace ImageSharp /// private void ClearPixels() { - Array.Clear(this.pixelBuffer, 0, this.Width * this.Height); + Array.Clear(this.PixelBuffer, 0, this.Width * this.Height); + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int y) + { + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } + } + + /// + /// Checks the coordinates to ensure they are within bounds. + /// + /// The x-coordinate of the pixel. Must be greater than zero and less than the width of the image. + /// The y-coordinate of the pixel. Must be greater than zero and less than the height of the image. + /// + /// Thrown if the coordinates are not within the bounds of the image. + /// + [Conditional("DEBUG")] + private void CheckCoordinates(int x, int y) + { + if (x < 0 || x >= this.Width) + { + throw new ArgumentOutOfRangeException(nameof(x), x, $"{x} is outwith the image bounds."); + } + + if (y < 0 || y >= this.Height) + { + throw new ArgumentOutOfRangeException(nameof(y), y, $"{y} is outwith the image bounds."); + } } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/ImageProcessingExtensions.cs b/src/ImageSharp/Image/ImageProcessingExtensions.cs index c9577ac15..8eed103d1 100644 --- a/src/ImageSharp/Image/ImageProcessingExtensions.cs +++ b/src/ImageSharp/Image/ImageProcessingExtensions.cs @@ -7,7 +7,7 @@ namespace ImageSharp { using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; /// /// Extension methods for the type. diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index 092706e41..059ccb9a0 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -15,7 +15,7 @@ namespace ImageSharp using Formats; using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; /// /// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes. diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index a54c03b63..4baae8615 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -17,9 +17,16 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public sealed class PixelAccessor : IDisposable, IBuffer2D + internal sealed class PixelAccessor : IDisposable, IBuffer2D where TPixel : struct, IPixel { +#pragma warning disable SA1401 // Fields must be private + /// + /// The containing the pixel data. + /// + internal Buffer2D PixelBuffer; +#pragma warning restore SA1401 // Fields must be private + /// /// A value indicating whether this instance of the given entity has been disposed. /// @@ -31,11 +38,6 @@ namespace ImageSharp /// private bool isDisposed; - /// - /// The containing the pixel data. - /// - private Buffer2D pixelBuffer; - /// /// Initializes a new instance of the class. /// @@ -46,7 +48,7 @@ namespace ImageSharp Guard.MustBeGreaterThan(image.Width, 0, "image width"); Guard.MustBeGreaterThan(image.Height, 0, "image height"); - this.SetPixelBufferUnsafe(image.Width, image.Height, image.Pixels); + this.SetPixelBufferUnsafe(image.Width, image.Height, image.PixelBuffer); this.ParallelOptions = image.Configuration.ParallelOptions; } @@ -88,7 +90,7 @@ namespace ImageSharp /// /// Gets the pixel buffer array. /// - public TPixel[] PixelArray => this.pixelBuffer.Array; + public TPixel[] PixelArray => this.PixelBuffer.Array; /// /// Gets the size of a single pixel in the number of bytes. @@ -116,7 +118,7 @@ namespace ImageSharp public ParallelOptions ParallelOptions { get; } /// - Span IBuffer2D.Span => this.pixelBuffer; + Span IBuffer2D.Span => this.PixelBuffer; private static PixelOperations Operations => PixelOperations.Instance; @@ -128,12 +130,14 @@ namespace ImageSharp /// The at the specified position. public TPixel this[int x, int y] { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { this.CheckCoordinates(x, y); return this.PixelArray[(y * this.Width) + x]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { this.CheckCoordinates(x, y); @@ -154,7 +158,7 @@ namespace ImageSharp // Note disposing is done. this.isDisposed = true; - this.pixelBuffer.Dispose(); + this.PixelBuffer.Dispose(); // This object will be cleaned up by the Dispose method. // Therefore, you should call GC.SuppressFinalize to @@ -169,7 +173,7 @@ namespace ImageSharp /// public void Reset() { - this.pixelBuffer.Clear(); + this.PixelBuffer.Clear(); } /// @@ -241,7 +245,7 @@ namespace ImageSharp /// If is true then caller is responsible for ensuring is called. internal TPixel[] ReturnCurrentColorsAndReplaceThemInternally(int width, int height, TPixel[] pixels) { - TPixel[] oldPixels = this.pixelBuffer.TakeArrayOwnership(); + TPixel[] oldPixels = this.PixelBuffer.TakeArrayOwnership(); this.SetPixelBufferUnsafe(width, height, pixels); return oldPixels; } @@ -252,7 +256,7 @@ namespace ImageSharp /// The target pixel buffer accessor. internal void CopyTo(PixelAccessor target) { - SpanHelper.Copy(this.pixelBuffer.Span, target.pixelBuffer.Span); + SpanHelper.Copy(this.PixelBuffer.Span, target.PixelBuffer.Span); } /// @@ -423,7 +427,7 @@ namespace ImageSharp /// The pixel buffer private void SetPixelBufferUnsafe(int width, int height, Buffer2D pixels) { - this.pixelBuffer = pixels; + this.PixelBuffer = pixels; this.Width = width; this.Height = height; diff --git a/src/ImageSharp/Memory/Buffer2D.cs b/src/ImageSharp/Memory/Buffer2D.cs index e5ccfbd19..59cabb1bd 100644 --- a/src/ImageSharp/Memory/Buffer2D.cs +++ b/src/ImageSharp/Memory/Buffer2D.cs @@ -69,7 +69,7 @@ namespace ImageSharp.Memory /// The instance public static Buffer2D CreateClean(int width, int height) { - Buffer2D buffer = new Buffer2D(width, height); + var buffer = new Buffer2D(width, height); buffer.Clear(); return buffer; } diff --git a/src/ImageSharp/Memory/Buffer2DExtensions.cs b/src/ImageSharp/Memory/Buffer2DExtensions.cs index 51e558281..046bfd81f 100644 --- a/src/ImageSharp/Memory/Buffer2DExtensions.cs +++ b/src/ImageSharp/Memory/Buffer2DExtensions.cs @@ -29,7 +29,7 @@ namespace ImageSharp.Memory } /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// Gets a to the row 'y' beginning from the pixel at the first pixel on that row. /// /// The buffer /// The y (row) coordinate diff --git a/src/ImageSharp/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Memory/PixelDataPool{T}.cs index a8b5501cc..643f1c6ca 100644 --- a/src/ImageSharp/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Memory/PixelDataPool{T}.cs @@ -49,12 +49,13 @@ namespace ImageSharp.Memory // ReSharper disable once SuspiciousTypeConversion.Global if (default(T) is IPixel) { - const int MaximumExpectedImageSize = 16384; - return MaximumExpectedImageSize * MaximumExpectedImageSize; + const int MaximumExpectedImageSize = 16384 * 16384; + return MaximumExpectedImageSize; } else { - return int.MaxValue; + const int MaxArrayLength = 1024 * 1024; // Match default pool. + return MaxArrayLength; } } } diff --git a/src/ImageSharp/Memory/SpanHelper.cs b/src/ImageSharp/Memory/SpanHelper.cs index 57b771591..0e794e1b5 100644 --- a/src/ImageSharp/Memory/SpanHelper.cs +++ b/src/ImageSharp/Memory/SpanHelper.cs @@ -70,7 +70,7 @@ namespace ImageSharp.Memory public static void Copy(Span source, Span destination) where T : struct { - Copy(source, destination, source.Length); + Copy(source, destination, Math.Min(source.Length, destination.Length)); } /// diff --git a/src/ImageSharp/Numerics/Matrix3x2Extensions.cs b/src/ImageSharp/Numerics/Matrix3x2Extensions.cs new file mode 100644 index 000000000..f42462402 --- /dev/null +++ b/src/ImageSharp/Numerics/Matrix3x2Extensions.cs @@ -0,0 +1,72 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Extension methods for the struct + /// + public static class Matrix3x2Extensions + { + /// + /// Creates a rotation matrix for the given rotation in degrees and a center point. + /// + /// The angle in degrees + /// The center point + /// The rotation + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix3x2 CreateRotation(float degree, Point centerPoint) + { + float radian = MathF.DegreeToRadian(degree); + return Matrix3x2.CreateRotation(radian, new Vector2(centerPoint.X, centerPoint.Y)); + } + + /// + /// Creates a rotation matrix for the given rotation in degrees and a center point. + /// + /// The angle in degrees + /// The center point + /// The rotation + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix3x2 CreateRotation(float degree, PointF centerPoint) + { + float radian = MathF.DegreeToRadian(degree); + return Matrix3x2.CreateRotation(radian, centerPoint); + } + + /// + /// Creates a skew matrix for the given angle in degrees and a center point. + /// + /// The x-angle in degrees + /// The y-angle in degrees + /// The center point + /// The rotation + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix3x2 CreateSkew(float degreesX, float degreesY, Point centerPoint) + { + float radiansX = MathF.DegreeToRadian(degreesX); + float radiansY = MathF.DegreeToRadian(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y)); + } + + /// + /// Creates a skew matrix for the given angle in degrees and a center point. + /// + /// The x-angle in degrees + /// The y-angle in degrees + /// The rotation + /// The center point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Matrix3x2 CreateSkew(float degreesX, float degreesY, PointF centerPoint) + { + float radiansX = MathF.DegreeToRadian(degreesX); + float radiansY = MathF.DegreeToRadian(degreesY); + return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(centerPoint.X, centerPoint.Y)); + } + } +} diff --git a/src/ImageSharp/Numerics/Point.cs b/src/ImageSharp/Numerics/Point.cs index 8d523895f..9bff27d2a 100644 --- a/src/ImageSharp/Numerics/Point.cs +++ b/src/ImageSharp/Numerics/Point.cs @@ -25,6 +25,17 @@ namespace ImageSharp /// public static readonly Point Empty = default(Point); + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal and vertical position of the point. + public Point(int value) + : this() + { + this.X = LowInt16(value); + this.Y = HighInt16(value); + } + /// /// Initializes a new instance of the struct. /// @@ -38,15 +49,13 @@ namespace ImageSharp } /// - /// Initializes a new instance of the struct. + /// Initializes a new instance of the struct from the given . /// - /// - /// The vector representing the width and height. - /// - public Point(Vector2 vector) + /// The size + public Point(Size size) { - this.X = (int)Math.Round(vector.X); - this.Y = (int)Math.Round(vector.Y); + this.X = size.Width; + this.Y = size.Height; } /// @@ -66,176 +75,160 @@ namespace ImageSharp public bool IsEmpty => this.Equals(Empty); /// - /// Computes the sum of adding two points. + /// Creates a with the coordinates of the specified . /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. - /// - /// The - /// + /// The point [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator +(Point left, Point right) - { - return new Point(left.X + right.X, left.Y + right.Y); - } + public static implicit operator PointF(Point point) => new PointF(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(Point point) => new Vector2(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(Point point) => new Size(point.X, point.Y); /// - /// Computes the difference left by subtracting one point from another. + /// Translates a by a given . /// - /// The point on the left hand of the operand. - /// The point on the right hand of the operand. + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. /// /// The /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Point operator -(Point left, Point right) - { - return new Point(left.X - right.X, left.Y - right.Y); - } + public static Point operator +(Point point, Size size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point operator -(Point point, Size size) => Subtract(point, size); /// /// Compares two objects for equality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// The on the left side of the operand. + /// The on the right side of the operand. /// /// True if the current left is equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Point left, Point right) - { - return left.Equals(right); - } + public static bool operator ==(Point left, Point right) => left.Equals(right); /// /// Compares two objects for inequality. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// + /// The on the left side of the operand. + /// The on the right side of the operand. /// /// True if the current left is unequal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Point left, Point right) - { - return !left.Equals(right); - } + public static bool operator !=(Point left, Point right) => !left.Equals(right); /// - /// Creates a rotation matrix for the given point and angle. + /// Translates a by the negative of a given . /// - /// The origin point to rotate around - /// Rotation in degrees - /// The rotation - public static Matrix3x2 CreateRotation(Point origin, float degrees) - { - float radians = MathF.DegreeToRadian(degrees); - return Matrix3x2.CreateRotation(radians, new Vector2(origin.X, origin.Y)); - } + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Add(Point point, Size size) => new Point(unchecked(point.X + size.Width), unchecked(point.Y + size.Height)); /// - /// Rotates a point around a given a rotation matrix. + /// Translates a by the negative of a given . /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Rotate(Point point, Matrix3x2 rotation) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), rotation)); - } + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Subtract(Point point, Size size) => new Point(unchecked(point.X - size.Width), unchecked(point.Y - size.Height)); /// - /// Rotates a point around a given origin by the specified angle in degrees. + /// Converts a to a by performing a ceiling operation on all the coordinates. /// - /// The point to rotate - /// The center point to rotate around. - /// The angle in degrees. - /// The rotated - public static Point Rotate(Point point, Point origin, float degrees) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateRotation(origin, degrees))); - } + /// The point + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Ceiling(PointF point) => new Point(unchecked((int)MathF.Ceiling(point.X)), unchecked((int)MathF.Ceiling(point.Y))); /// - /// Creates a skew matrix for the given point and angle. + /// Converts a to a by performing a round operation on all the coordinates. /// - /// The origin point to rotate around - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The rotation - public static Matrix3x2 CreateSkew(Point origin, float degreesX, float degreesY) - { - float radiansX = MathF.DegreeToRadian(degreesX); - float radiansY = MathF.DegreeToRadian(degreesY); - return Matrix3x2.CreateSkew(radiansX, radiansY, new Vector2(origin.X, origin.Y)); - } + /// The point + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(PointF point) => new Point(unchecked((int)MathF.Round(point.X)), unchecked((int)MathF.Round(point.Y))); /// - /// Skews a point using a given a skew matrix. + /// Converts a to a by performing a truncate operation on all the coordinates. /// - /// The point to rotate - /// Rotation matrix used - /// The rotated - public static Point Skew(Point point, Matrix3x2 skew) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), skew)); - } + /// The point + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Truncate(PointF point) => new Point(unchecked((int)point.X), unchecked((int)point.Y)); /// - /// Skews a point around a given origin by the specified angles in degrees. + /// Converts a to a by performing a round operation on all the coordinates. /// - /// The point to skew. - /// The center point to rotate around. - /// The x-angle in degrees. - /// The y-angle in degrees. - /// The skewed - public static Point Skew(Point point, Point origin, float degreesX, float degreesY) - { - return new Point(Vector2.Transform(new Vector2(point.X, point.Y), CreateSkew(origin, degreesX, degreesY))); - } + /// The vector + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Round(Vector2 vector) => new Point(unchecked((int)MathF.Round(vector.X)), unchecked((int)MathF.Round(vector.Y))); /// - /// Gets a representation for this . + /// Rotates a point around the given rotation matrix. /// - /// A representation for this object. - public Vector2 ToVector2() - { - return new Vector2(this.X, this.Y); - } + /// The point to rotate + /// Rotation matrix used + /// The rotated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Rotate(Point point, Matrix3x2 rotation) => Round(Vector2.Transform(new Vector2(point.X, point.Y), rotation)); + + /// + /// Skews a point using the given skew matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Skew(Point point, Matrix3x2 skew) => Round(Vector2.Transform(new Vector2(point.X, point.Y), skew)); /// /// Translates this by the specified amount. /// /// The amount to offset the x-coordinate. /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Offset(int dx, int dy) { - this.X += dx; - this.Y += dy; + unchecked + { + this.X += dx; + this.Y += dy; + } } /// /// Translates this by the specified amount. /// - /// The used offset this . - public void Offset(Point p) - { - this.Offset(p.X, p.Y); - } + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() @@ -249,34 +242,16 @@ namespace ImageSharp } /// - public override bool Equals(object obj) - { - if (obj is Point) - { - return this.Equals((Point)obj); - } - - return false; - } + public override bool Equals(object obj) => obj is Point && this.Equals((Point)obj); /// - public bool Equals(Point other) - { - return this.X == other.X && this.Y == other.Y; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Point other) => this.X == other.X && this.Y == other.Y; - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// - private int GetHashCode(Point point) - { - return point.X ^ point.Y; - } + private static short HighInt16(int n) => unchecked((short)((n >> 16) & 0xffff)); + + private static short LowInt16(int n) => unchecked((short)(n & 0xffff)); + + private int GetHashCode(Point point) => point.X ^ point.Y; } } \ No newline at end of file diff --git a/src/ImageSharp/Numerics/PointF.cs b/src/ImageSharp/Numerics/PointF.cs new file mode 100644 index 000000000..cbe5c7f48 --- /dev/null +++ b/src/ImageSharp/Numerics/PointF.cs @@ -0,0 +1,233 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Represents an ordered pair of single precision floating point x- and y-coordinates that defines a point in + /// a two-dimensional plane. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct PointF : IEquatable + { + /// + /// Represents a that has X and Y values set to zero. + /// + public static readonly PointF Empty = default(PointF); + + /// + /// Initializes a new instance of the struct. + /// + /// The horizontal position of the point. + /// The vertical position of the point. + public PointF(float x, float y) + : this() + { + this.X = x; + this.Y = y; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The size + public PointF(SizeF size) + { + this.X = size.Width; + this.Y = size.Height; + } + + /// + /// Gets or sets the x-coordinate of this . + /// + public float X { get; set; } + + /// + /// Gets or sets the y-coordinate of this . + /// + public float Y { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The vector. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator PointF(Vector2 vector) => new PointF(vector.X, vector.Y); + + /// + /// Creates a with the coordinates of the specified . + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector2(PointF point) => new Vector2(point.X, point.Y); + + /// + /// Creates a with the coordinates of the specified by truncating each of the coordinates. + /// + /// The point. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(PointF point) => Point.Truncate(point); + + /// + /// Translates a by a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator +(PointF point, SizeF size) => Add(point, size); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF operator -(PointF point, SizeF size) => Subtract(point, size); + + /// + /// Compares two objects for equality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(PointF left, PointF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// + /// The on the left side of the operand. + /// + /// + /// The on the right side of the operand. + /// + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(PointF left, PointF right) => !left.Equals(right); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Add(PointF point, SizeF size) => new PointF(point.X + size.Width, point.Y + size.Height); + + /// + /// Translates a by the negative of a given . + /// + /// The point on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Subtract(PointF point, SizeF size) => new PointF(point.X - size.Width, point.Y - size.Height); + + /// + /// Rotates a point around the given rotation matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Rotate(PointF point, Matrix3x2 rotation) => Vector2.Transform(new Vector2(point.X, point.Y), rotation); + + /// + /// Skews a point using the given skew matrix. + /// + /// The point to rotate + /// Rotation matrix used + /// The rotated + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Skew(PointF point, Matrix3x2 skew) => Vector2.Transform(new Vector2(point.X, point.Y), skew); + + /// + /// Translates this by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) + { + this.X += dx; + this.Y += dy; + } + + /// + /// Translates this by the specified amount. + /// + /// The used offset this . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + public override int GetHashCode() => this.GetHashCode(this); + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "PointF [ Empty ]"; + } + + return $"PointF [ X={this.X}, Y={this.Y} ]"; + } + + /// + public override bool Equals(object obj) => obj is PointF && this.Equals((PointF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(PointF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y); + + /// + /// Returns the hash code for this instance. + /// + /// + /// The instance of to return the hash code for. + /// + /// + /// A 32-bit signed integer that is the hash code for this instance. + /// + private int GetHashCode(PointF point) => point.X.GetHashCode() ^ point.Y.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Numerics/Rectangle.cs b/src/ImageSharp/Numerics/Rectangle.cs index b651eff71..d16b22920 100644 --- a/src/ImageSharp/Numerics/Rectangle.cs +++ b/src/ImageSharp/Numerics/Rectangle.cs @@ -8,6 +8,7 @@ namespace ImageSharp using System; using System.ComponentModel; using System.Numerics; + using System.Runtime.CompilerServices; /// /// Stores a set of four integers that represent the location and size of a rectangle. @@ -23,11 +24,6 @@ namespace ImageSharp /// public static readonly Rectangle Empty = default(Rectangle); - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - /// /// Initializes a new instance of the struct. /// @@ -37,7 +33,10 @@ namespace ImageSharp /// The height of the rectangle. public Rectangle(int x, int y, int width, int height) { - this.backingVector = new Vector4(x, y, width, height); + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; } /// @@ -51,197 +50,325 @@ namespace ImageSharp /// public Rectangle(Point point, Size size) { - this.backingVector = new Vector4(point.X, point.Y, size.Width, size.Height); + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; } /// - /// Initializes a new instance of the struct. + /// Gets or sets the x-coordinate of this . /// - /// - /// The which specifies the rectangles top left point in a two-dimensional plane. - /// - /// - /// The which specifies the rectangles bottom right point in a two-dimensional plane. - /// - public Rectangle(Point topLeft, Point bottomRight) - { - this.backingVector = new Vector4(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y); - } + public int X { get; set; } /// - /// Initializes a new instance of the struct. + /// Gets or sets the y-coordinate of this . /// - /// The vector. - public Rectangle(Vector4 vector) - { - this.backingVector = vector; - } + public int Y { get; set; } /// - /// Gets or sets the x-coordinate of this . + /// Gets or sets the width of this . + /// + public int Width { get; set; } + + /// + /// Gets or sets the height of this . /// - public int X + public int Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public Point Location { - get - { - return (int)this.backingVector.X; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Point(this.X, this.Y); + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.backingVector.X = value; + this.X = value.X; + this.Y = value.Y; } } /// - /// Gets or sets the y-coordinate of this . + /// Gets or sets the size of this . /// - public int Y + [EditorBrowsable(EditorBrowsableState.Never)] + public Size Size { - get - { - return (int)this.backingVector.Y; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new Size(this.Width, this.Height); + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.backingVector.Y = value; + this.Width = value.Width; + this.Height = value.Height; } } /// - /// Gets or sets the width of this . + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Gets the y-coordinate of the top edge of this . /// - public int Width + public int Top { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return (int)this.backingVector.Z; + return this.Y; } + } - set + /// + /// Gets the x-coordinate of the right edge of this . + /// + public int Right + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - this.backingVector.Z = value; + return unchecked(this.X + this.Width); } } /// - /// Gets or sets the height of this . + /// Gets the y-coordinate of the bottom edge of this . /// - public int Height + public int Bottom { + [MethodImpl(MethodImplOptions.AggressiveInlining)] get { - return (int)this.backingVector.W; + return unchecked(this.Y + this.Height); } + } - set + /// + /// Gets the x-coordinate of the left edge of this . + /// + public int Left + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { - this.backingVector.W = value; + return this.X; } } /// - /// Gets the size of this . + /// Creates a with the coordinates of the specified . /// - public Size Size => new Size(this.Width, this.Height); + /// The rectangle + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator RectangleF(Rectangle rectangle) => new RectangleF(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); /// - /// Gets a value indicating whether this is empty. + /// Creates a with the coordinates of the specified . /// - [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + /// The rectangle + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator Vector4(Rectangle rectangle) => new Vector4(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height); /// - /// Gets the y-coordinate of the top edge of this . + /// Compares two objects for equality. /// - public int Top => this.Y; + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(Rectangle left, Rectangle right) => left.Equals(right); /// - /// Gets the x-coordinate of the right edge of this . + /// Compares two objects for inequality. /// - public int Right => this.X + this.Width; + /// The on the left side of the operand. + /// The on the right side of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(Rectangle left, Rectangle right) => !left.Equals(right); /// - /// Gets the y-coordinate of the bottom edge of this . + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle + /// The top coordinate of the rectangle + /// The right coordinate of the rectangle + /// The bottom coordinate of the rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + // ReSharper disable once InconsistentNaming + public static Rectangle FromLTRB(int left, int top, int right, int bottom) => new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + + /// + /// Returns the center point of the given /// - public int Bottom => this.Y + this.Height; + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Point Center(Rectangle rectangle) => new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); /// - /// Gets the x-coordinate of the left edge of this . + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. /// - public int Left => this.X; + /// The first rectangle + /// The second rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Intersect(Rectangle a, Rectangle b) + { + int x1 = Math.Max(a.X, b.X); + int x2 = Math.Min(a.Right, b.Right); + int y1 = Math.Max(a.Y, b.Y); + int y2 = Math.Min(a.Bottom, b.Bottom); + + if (x2 >= x1 && y2 >= y1) + { + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + return Empty; + } /// - /// Computes the sum of adding two rectangles. + /// Creates a that is inflated by the specified amount. /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator +(Rectangle left, Rectangle right) + /// The rectangle + /// The amount to inflate the width by + /// The amount to inflate the height by + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Inflate(Rectangle rectangle, int x, int y) { - return new Rectangle(left.backingVector + right.backingVector); + Rectangle r = rectangle; + r.Inflate(x, y); + return r; } /// - /// Computes the difference left by subtracting one rectangle from another. + /// Converts a to a by performing a ceiling operation on all the coordinates. /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. - /// - /// The - /// - public static Rectangle operator -(Rectangle left, Rectangle right) + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Ceiling(RectangleF rectangle) { - return new Rectangle(left.backingVector - right.backingVector); + unchecked + { + return new Rectangle( + (int)MathF.Ceiling(rectangle.X), + (int)MathF.Ceiling(rectangle.Y), + (int)MathF.Ceiling(rectangle.Width), + (int)MathF.Ceiling(rectangle.Height)); + } } /// - /// Compares two objects for equality. + /// Converts a to a by performing a truncate operation on all the coordinates. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(Rectangle left, Rectangle right) + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Truncate(RectangleF rectangle) { - return left.Equals(right); + unchecked + { + return new Rectangle( + (int)rectangle.X, + (int)rectangle.Y, + (int)rectangle.Width, + (int)rectangle.Height); + } } /// - /// Compares two objects for inequality. + /// Converts a to a by performing a round operation on all the coordinates. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(Rectangle left, Rectangle right) + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Round(RectangleF rectangle) { - return !left.Equals(right); + unchecked + { + return new Rectangle( + (int)MathF.Round(rectangle.X), + (int)MathF.Round(rectangle.Y), + (int)MathF.Round(rectangle.Width), + (int)MathF.Round(rectangle.Height)); + } } /// - /// Returns the center point of the given + /// Creates a rectangle that represents the union between and . + /// + /// The first rectangle + /// The second rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Rectangle Union(Rectangle a, Rectangle b) + { + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.Right, b.Right); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Bottom, b.Bottom); + + return new Rectangle(x1, y1, x2 - x1, y2 - y1); + } + + /// + /// Creates a Rectangle that represents the intersection between this Rectangle and the . /// /// The rectangle - /// - public static Point Center(Rectangle rectangle) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(Rectangle rectangle) { - return new Point(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + Rectangle result = Intersect(rectangle, this); + + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; } + /// + /// Inflates this by the specified amount. + /// + /// The width + /// The height + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(int width, int height) + { + unchecked + { + this.X -= width; + this.Y -= height; + + this.Width += 2 * width; + this.Height += 2 * height; + } + } + + /// + /// Inflates this by the specified amount. + /// + /// The size + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(Size size) => this.Inflate(size.Width, size.Height); + /// /// Determines if the specfied point is contained within the rectangular region defined by /// this . @@ -249,33 +376,63 @@ namespace ImageSharp /// The x-coordinate of the given point. /// The y-coordinate of the given point. /// The - public bool Contains(int x, int y) - { - // TODO: SIMD? - return this.X <= x - && x < this.Right - && this.Y <= y - && y < this.Bottom; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(int x, int y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Point point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Rectangle rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); /// /// Determines if the specfied intersects the rectangular region defined by /// this . /// - /// The other Rectange + /// The other Rectange /// The - public bool Intersects(Rectangle rect) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(Rectangle rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(Point point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(int dx, int dy) { - return rect.Left <= this.Right && rect.Right >= this.Left - && - rect.Top <= this.Bottom && rect.Bottom >= this.Top; + unchecked + { + this.X += dx; + this.Y += dy; + } } /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() @@ -285,39 +442,26 @@ namespace ImageSharp return "Rectangle [ Empty ]"; } - return - $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + return $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; } /// - public override bool Equals(object obj) - { - if (obj is Rectangle) - { - return this.Equals((Rectangle)obj); - } - - return false; - } + public override bool Equals(object obj) => obj is Rectangle && this.Equals((Rectangle)obj); /// - public bool Equals(Rectangle other) - { - return this.backingVector.Equals(other.backingVector); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Rectangle other) => this.X == other.X && this.Y == other.Y && this.Width == other.Width && this.Height == other.Height; - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// private int GetHashCode(Rectangle rectangle) { - return rectangle.backingVector.GetHashCode(); + unchecked + { + int hashCode = rectangle.X; + hashCode = (hashCode * 397) ^ rectangle.Y; + hashCode = (hashCode * 397) ^ rectangle.Width; + hashCode = (hashCode * 397) ^ rectangle.Height; + return hashCode; + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Numerics/RectangleF.cs b/src/ImageSharp/Numerics/RectangleF.cs index 2ed57c841..7611c9602 100644 --- a/src/ImageSharp/Numerics/RectangleF.cs +++ b/src/ImageSharp/Numerics/RectangleF.cs @@ -8,9 +8,10 @@ namespace ImageSharp using System; using System.ComponentModel; using System.Numerics; + using System.Runtime.CompilerServices; /// - /// Stores a set of four integers that represent the location and size of a rectangle. + /// Stores a set of four single precision floating points that represent the location and size of a rectangle. /// /// /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, @@ -19,15 +20,10 @@ namespace ImageSharp public struct RectangleF : IEquatable { /// - /// Represents a that has X, Y, Width, and Height values set to zero. + /// Represents a that has X, Y, Width, and Height values set to zero. /// public static readonly RectangleF Empty = default(RectangleF); - /// - /// The backing vector for SIMD support. - /// - private Vector4 backingVector; - /// /// Initializes a new instance of the struct. /// @@ -37,79 +33,80 @@ namespace ImageSharp /// The height of the rectangle. public RectangleF(float x, float y, float width, float height) { - this.backingVector = new Vector4(x, y, width, height); + this.X = x; + this.Y = y; + this.Width = width; + this.Height = height; } /// /// Initializes a new instance of the struct. /// - /// The vector. - public RectangleF(Vector4 vector) + /// + /// The which specifies the rectangles point in a two-dimensional plane. + /// + /// + /// The which specifies the rectangles height and width. + /// + public RectangleF(PointF point, SizeF size) { - this.backingVector = vector; + this.X = point.X; + this.Y = point.Y; + this.Width = size.Width; + this.Height = size.Height; } /// /// Gets or sets the x-coordinate of this . /// - public float X - { - get - { - return this.backingVector.X; - } - - set - { - this.backingVector.X = value; - } - } + public float X { get; set; } /// /// Gets or sets the y-coordinate of this . /// - public float Y - { - get - { - return this.backingVector.Y; - } - - set - { - this.backingVector.Y = value; - } - } + public float Y { get; set; } /// /// Gets or sets the width of this . /// - public float Width + public float Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets or sets the coordinates of the upper-left corner of the rectangular region represented by this . + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public PointF Location { - get - { - return this.backingVector.Z; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new PointF(this.X, this.Y); + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.backingVector.Z = value; + this.X = value.X; + this.Y = value.Y; } } /// - /// Gets or sets the height of this . + /// Gets or sets the size of this . /// - public float Height + [EditorBrowsable(EditorBrowsableState.Never)] + public SizeF Size { - get - { - return this.backingVector.W; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new SizeF(this.Width, this.Height); + [MethodImpl(MethodImplOptions.AggressiveInlining)] set { - this.backingVector.W = value; + this.Width = value.Width; + this.Height = value.Height; } } @@ -117,141 +114,196 @@ namespace ImageSharp /// Gets a value indicating whether this is empty. /// [EditorBrowsable(EditorBrowsableState.Never)] - public bool IsEmpty => this.Equals(Empty); + public bool IsEmpty => (this.Width <= 0) || (this.Height <= 0); /// /// Gets the y-coordinate of the top edge of this . /// - public float Top => this.Y; + public float Top + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.Y; + } + } /// /// Gets the x-coordinate of the right edge of this . /// - public float Right => this.X + this.Width; + public float Right + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.X + this.Width; + } + } /// /// Gets the y-coordinate of the bottom edge of this . /// - public float Bottom => this.Y + this.Height; + public float Bottom + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.Y + this.Height; + } + } /// /// Gets the x-coordinate of the left edge of this . /// - public float Left => this.X; + public float Left + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return this.X; + } + } /// - /// Performs an implicit conversion from to . + /// Creates a with the coordinates of the specified by truncating each coordinate. /// - /// The d. - /// - /// The result of the conversion. - /// - public static implicit operator RectangleF(Rectangle d) - { - return new RectangleF(d.Left, d.Top, d.Width, d.Height); - } + /// The rectangle + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Rectangle(RectangleF rectangle) => Rectangle.Truncate(rectangle); /// - /// Computes the sum of adding two rectangles. + /// Compares two objects for equality. /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. + /// The on the left side of the operand. + /// The on the right side of the operand. /// - /// The + /// True if the current left is equal to the parameter; otherwise, false. /// - public static RectangleF operator +(RectangleF left, RectangleF right) - { - return new RectangleF(left.backingVector + right.backingVector); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(RectangleF left, RectangleF right) => left.Equals(right); /// - /// Computes the difference left by subtracting one rectangle from another. + /// Compares two objects for inequality. /// - /// The rectangle on the left hand of the operand. - /// The rectangle on the right hand of the operand. + /// The on the left side of the operand. + /// The on the right side of the operand. /// - /// The + /// True if the current left is unequal to the parameter; otherwise, false. /// - public static RectangleF operator -(RectangleF left, RectangleF right) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(RectangleF left, RectangleF right) => !left.Equals(right); + + /// + /// Creates a new with the specified location and size. + /// The left coordinate of the rectangle + /// The top coordinate of the rectangle + /// The right coordinate of the rectangle + /// The bottom coordinate of the rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + + // ReSharper disable once InconsistentNaming + public static RectangleF FromLTRB(float left, float top, float right, float bottom) => new RectangleF(left, top, right - left, bottom - top); + + /// + /// Returns the center point of the given + /// + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PointF Center(RectangleF rectangle) => new PointF(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + + /// + /// Creates a rectangle that represents the intersection between and + /// . If there is no intersection, an empty rectangle is returned. + /// + /// The first rectangle + /// The second rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Intersect(RectangleF a, RectangleF b) { - return new RectangleF(left.backingVector - right.backingVector); + float x1 = MathF.Max(a.X, b.X); + float x2 = MathF.Min(a.Right, b.Right); + float y1 = MathF.Max(a.Y, b.Y); + float y2 = MathF.Min(a.Bottom, b.Bottom); + + if (x2 >= x1 && y2 >= y1) + { + return new RectangleF(x1, y1, x2 - x1, y2 - y1); + } + + return Empty; } /// - /// Compares two objects for equality. + /// Creates a that is inflated by the specified amount. /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is equal to the parameter; otherwise, false. - /// - public static bool operator ==(RectangleF left, RectangleF right) + /// The rectangle + /// The amount to inflate the width by + /// The amount to inflate the height by + /// A new + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Inflate(RectangleF rectangle, float x, float y) { - return left.Equals(right); + RectangleF r = rectangle; + r.Inflate(x, y); + return r; } /// - /// Compares two objects for inequality. + /// Creates a rectangle that represents the union between and . /// - /// - /// The on the left side of the operand. - /// - /// - /// The on the right side of the operand. - /// - /// - /// True if the current left is unequal to the parameter; otherwise, false. - /// - public static bool operator !=(RectangleF left, RectangleF right) + /// The first rectangle + /// The second rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RectangleF Union(RectangleF a, RectangleF b) { - return !left.Equals(right); + float x1 = MathF.Min(a.X, b.X); + float x2 = MathF.Max(a.Right, b.Right); + float y1 = MathF.Min(a.Y, b.Y); + float y2 = MathF.Max(a.Bottom, b.Bottom); + + return new RectangleF(x1, y1, x2 - x1, y2 - y1); } /// - /// Returns the center point of the given + /// Creates a RectangleF that represents the intersection between this RectangleF and the . /// /// The rectangle - /// - public static Vector2 Center(RectangleF rectangle) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Intersect(RectangleF rectangle) { - return new Vector2(rectangle.Left + (rectangle.Width / 2), rectangle.Top + (rectangle.Height / 2)); + RectangleF result = Intersect(rectangle, this); + + this.X = result.X; + this.Y = result.Y; + this.Width = result.Width; + this.Height = result.Height; } /// - /// Rounds the points away from the center this into a - /// by rounding the dimensions to the nerent integer ensuring that the new rectangle is - /// never smaller than the source + /// Inflates this by the specified amount. /// - /// The source area to round out - /// - /// The smallest that the will fit inside. - /// - public static Rectangle Ceiling(RectangleF source) + /// The width + /// The height + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(float width, float height) { - int y = (int)Math.Floor(source.Y); - int width = (int)Math.Ceiling(source.Width); - int x = (int)Math.Floor(source.X); - int height = (int)Math.Ceiling(source.Height); - return new Rectangle(x, y, width, height); + this.X -= width; + this.Y -= height; + + this.Width += 2 * width; + this.Height += 2 * height; } /// - /// Outsets the specified region. + /// Inflates this by the specified amount. /// - /// The region. - /// The width. - /// - /// The with all dimensions move away from the center by the offset. - /// - public static RectangleF Outset(RectangleF region, float width) - { - float dblWidth = width * 2; - return new RectangleF(region.X - width, region.Y - width, region.Width + dblWidth, region.Height + dblWidth); - } + /// The size + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Inflate(SizeF size) => this.Inflate(size.Width, size.Height); /// /// Determines if the specfied point is contained within the rectangular region defined by @@ -260,75 +312,89 @@ namespace ImageSharp /// The x-coordinate of the given point. /// The y-coordinate of the given point. /// The - public bool Contains(float x, float y) - { - // TODO: SIMD? - return this.X <= x - && x < this.Right - && this.Y <= y - && y < this.Bottom; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(float x, float y) => this.X <= x && x < this.Right && this.Y <= y && y < this.Bottom; + + /// + /// Determines if the specified point is contained within the rectangular region defined by this . + /// + /// The point + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(PointF point) => this.Contains(point.X, point.Y); + + /// + /// Determines if the rectangular region represented by is entirely contained + /// within the rectangular region represented by this . + /// + /// The rectangle + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(RectangleF rectangle) => + (this.X <= rectangle.X) && (rectangle.Right <= this.Right) && + (this.Y <= rectangle.Y) && (rectangle.Bottom <= this.Bottom); /// - /// Determines if the specfied intersects the rectangular region defined by - /// this . + /// Determines if the specfied intersects the rectangular region defined by + /// this . /// - /// The other Rectange + /// The other Rectange /// The - public bool Intersects(RectangleF rect) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IntersectsWith(RectangleF rectangle) => + (rectangle.X < this.Right) && (this.X < rectangle.Right) && + (rectangle.Y < this.Bottom) && (this.Y < rectangle.Bottom); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(PointF point) => this.Offset(point.X, point.Y); + + /// + /// Adjusts the location of this rectangle by the specified amount. + /// + /// The amount to offset the x-coordinate. + /// The amount to offset the y-coordinate. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Offset(float dx, float dy) { - return rect.Left <= this.Right && rect.Right >= this.Left - && - rect.Top <= this.Bottom && rect.Bottom >= this.Top; + this.X += dx; + this.Y += dy; } /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() { if (this.IsEmpty) { - return "Rectangle [ Empty ]"; + return "RectangleF [ Empty ]"; } - return - $"Rectangle [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; + return $"RectangleF [ X={this.X}, Y={this.Y}, Width={this.Width}, Height={this.Height} ]"; } /// - public override bool Equals(object obj) - { - if (obj is RectangleF) - { - return this.Equals((RectangleF)obj); - } - - return false; - } + public override bool Equals(object obj) => obj is RectangleF && this.Equals((RectangleF)obj); /// - public bool Equals(RectangleF other) - { - return this.backingVector.Equals(other.backingVector); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(RectangleF other) => this.X.Equals(other.X) && this.Y.Equals(other.Y) && this.Width.Equals(other.Width) && this.Height.Equals(other.Height); - /// - /// Returns the hash code for this instance. - /// - /// - /// The instance of to return the hash code for. - /// - /// - /// A 32-bit signed integer that is the hash code for this instance. - /// private int GetHashCode(RectangleF rectangle) { - return rectangle.backingVector.GetHashCode(); + unchecked + { + int hashCode = rectangle.X.GetHashCode(); + hashCode = (hashCode * 397) ^ rectangle.Y.GetHashCode(); + hashCode = (hashCode * 397) ^ rectangle.Width.GetHashCode(); + hashCode = (hashCode * 397) ^ rectangle.Height.GetHashCode(); + return hashCode; + } } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Numerics/Size.cs b/src/ImageSharp/Numerics/Size.cs index bae645ac8..79ee1ddea 100644 --- a/src/ImageSharp/Numerics/Size.cs +++ b/src/ImageSharp/Numerics/Size.cs @@ -23,6 +23,17 @@ namespace ImageSharp /// public static readonly Size Empty = default(Size); + /// + /// Initializes a new instance of the struct. + /// + /// The width and height of the size + public Size(int value) + : this() + { + this.Width = value; + this.Height = value; + } + /// /// Initializes a new instance of the struct. /// @@ -34,6 +45,27 @@ namespace ImageSharp this.Height = height; } + /// + /// Initializes a new instance of the struct. + /// + /// The size + public Size(Size size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The point + public Size(Point point) + { + this.Width = point.X; + this.Height = point.Y; + } + /// /// Gets or sets the width of this . /// @@ -50,6 +82,20 @@ namespace ImageSharp [EditorBrowsable(EditorBrowsableState.Never)] public bool IsEmpty => this.Equals(Empty); + /// + /// Creates a with the dimensions of the specified . + /// + /// The point + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator SizeF(Size size) => new SizeF(size.Width, size.Height); + + /// + /// Converts the given into a . + /// + /// The size + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Point(Size size) => new Point(size.Width, size.Height); + /// /// Computes the sum of adding two sizes. /// @@ -59,10 +105,7 @@ namespace ImageSharp /// The /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator +(Size left, Size right) - { - return new Size(left.Width + right.Width, left.Height + right.Height); - } + public static Size operator +(Size left, Size right) => Add(left, right); /// /// Computes the difference left by subtracting one size from another. @@ -73,10 +116,7 @@ namespace ImageSharp /// The /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Size operator -(Size left, Size right) - { - return new Size(left.Width - right.Width, left.Height - right.Height); - } + public static Size operator -(Size left, Size right) => Subtract(left, right); /// /// Compares two objects for equality. @@ -91,10 +131,7 @@ namespace ImageSharp /// True if the current left is equal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(Size left, Size right) - { - return left.Equals(right); - } + public static bool operator ==(Size left, Size right) => left.Equals(right); /// /// Compares two objects for inequality. @@ -109,16 +146,52 @@ namespace ImageSharp /// True if the current left is unequal to the parameter; otherwise, false. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(Size left, Size right) - { - return !left.Equals(right); - } + public static bool operator !=(Size left, Size right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Add(Size left, Size right) => new Size(unchecked(left.Width + right.Width), unchecked(left.Height + right.Height)); + + /// + /// Contracts a by another + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Subtract(Size left, Size right) => new Size(unchecked(left.Width - right.Width), unchecked(left.Height - right.Height)); + + /// + /// Converts a to a by performing a ceiling operation on all the dimensions. + /// + /// The size + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Ceiling(SizeF size) => new Size(unchecked((int)MathF.Ceiling(size.Width)), unchecked((int)MathF.Ceiling(size.Height))); + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Round(SizeF size) => new Size(unchecked((int)MathF.Round(size.Width)), unchecked((int)MathF.Round(size.Height))); + + /// + /// Converts a to a by performing a round operation on all the dimensions. + /// + /// The size + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Size Truncate(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); /// - public override int GetHashCode() - { - return this.GetHashCode(this); - } + public override int GetHashCode() => this.GetHashCode(this); /// public override string ToString() @@ -132,22 +205,11 @@ namespace ImageSharp } /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public override bool Equals(object obj) - { - if (obj is Size) - { - return this.Equals((Size)obj); - } - - return false; - } + public override bool Equals(object obj) => obj is Size && this.Equals((Size)obj); /// - public bool Equals(Size other) - { - return this.Width == other.Width && this.Height == other.Height; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(Size other) => this.Width == other.Width && this.Height == other.Height; /// /// Returns the hash code for this instance. @@ -158,9 +220,6 @@ namespace ImageSharp /// /// A 32-bit signed integer that is the hash code for this instance. /// - private int GetHashCode(Size size) - { - return size.Width ^ size.Height; - } + private int GetHashCode(Size size) => size.Width ^ size.Height; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Numerics/SizeF.cs b/src/ImageSharp/Numerics/SizeF.cs new file mode 100644 index 000000000..78078fd01 --- /dev/null +++ b/src/ImageSharp/Numerics/SizeF.cs @@ -0,0 +1,179 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.ComponentModel; + using System.Runtime.CompilerServices; + + /// + /// Stores an ordered pair of single precision floating points, which specify a height and width. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public struct SizeF : IEquatable + { + /// + /// Represents a that has Width and Height values set to zero. + /// + public static readonly SizeF Empty = default(SizeF); + + /// + /// Initializes a new instance of the struct. + /// + /// The width of the size. + /// The height of the size. + public SizeF(float width, float height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The size + public SizeF(SizeF size) + : this() + { + this.Width = size.Width; + this.Height = size.Height; + } + + /// + /// Initializes a new instance of the struct from the given . + /// + /// The point + public SizeF(PointF point) + { + this.Width = point.X; + this.Height = point.Y; + } + + /// + /// Gets or sets the width of this . + /// + public float Width { get; set; } + + /// + /// Gets or sets the height of this . + /// + public float Height { get; set; } + + /// + /// Gets a value indicating whether this is empty. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsEmpty => this.Equals(Empty); + + /// + /// Creates a with the dimensions of the specified by truncating each of the dimensions. + /// + /// The size. + /// + /// The . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator Size(SizeF size) => new Size(unchecked((int)size.Width), unchecked((int)size.Height)); + + /// + /// Converts the given into a . + /// + /// The size + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator PointF(SizeF size) => new PointF(size.Width, size.Height); + + /// + /// Computes the sum of adding two sizes. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator +(SizeF left, SizeF right) => Add(left, right); + + /// + /// Computes the difference left by subtracting one size from another. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// The + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF operator -(SizeF left, SizeF right) => Subtract(left, right); + + /// + /// Compares two objects for equality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is equal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator ==(SizeF left, SizeF right) => left.Equals(right); + + /// + /// Compares two objects for inequality. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// + /// True if the current left is unequal to the parameter; otherwise, false. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(SizeF left, SizeF right) => !left.Equals(right); + + /// + /// Performs vector addition of two objects. + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Add(SizeF left, SizeF right) => new SizeF(left.Width + right.Width, left.Height + right.Height); + + /// + /// Contracts a by another + /// + /// The size on the left hand of the operand. + /// The size on the right hand of the operand. + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static SizeF Subtract(SizeF left, SizeF right) => new SizeF(left.Width - right.Width, left.Height - right.Height); + + /// + public override int GetHashCode() + { + return this.GetHashCode(this); + } + + /// + public override string ToString() + { + if (this.IsEmpty) + { + return "SizeF [ Empty ]"; + } + + return $"SizeF [ Width={this.Width}, Height={this.Height} ]"; + } + + /// + public override bool Equals(object obj) => obj is SizeF && this.Equals((SizeF)obj); + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Equals(SizeF other) => this.Width.Equals(other.Width) && this.Height.Equals(other.Height); + + private int GetHashCode(SizeF size) => size.Width.GetHashCode() ^ size.Height.GetHashCode(); + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs b/src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs index 76977455a..de543f2cd 100644 --- a/src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs +++ b/src/ImageSharp/Processing/ColorMatrix/BlackWhite.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs b/src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs index d012d6fe2..c48a86293 100644 --- a/src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs +++ b/src/ImageSharp/Processing/ColorMatrix/ColorBlindness.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Grayscale.cs b/src/ImageSharp/Processing/ColorMatrix/Grayscale.cs index 8700b63e8..daddf106c 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Grayscale.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Grayscale.cs @@ -5,11 +5,9 @@ namespace ImageSharp { - using System; - using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// @@ -17,6 +15,18 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Applies Grayscale toning to the image. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static Image Grayscale(this Image source) + where TPixel : struct, IPixel + { + return Grayscale(source, GrayscaleMode.Bt709); + } + /// /// Applies Grayscale toning to the image. /// @@ -24,23 +34,41 @@ namespace ImageSharp /// The image this method extends. /// The formula to apply to perform the operation. /// The . - public static Image Grayscale(this Image source, GrayscaleMode mode = GrayscaleMode.Bt709) + public static Image Grayscale(this Image source, GrayscaleMode mode) where TPixel : struct, IPixel { - return Grayscale(source, source.Bounds, mode); + return Grayscale(source, mode, source.Bounds); } /// - /// Applies Grayscale toning to the image. + /// Applies Grayscale toning to the image. /// /// The pixel format. /// The image this method extends. /// /// The structure that specifies the portion of the image object to alter. /// + /// The . + public static Image Grayscale(this Image source, Rectangle rectangle) + where TPixel : struct, IPixel + { + IImageProcessor processor = new GrayscaleBt709Processor(); + + source.ApplyProcessor(processor, rectangle); + return source; + } + + /// + /// Applies Grayscale toning to the image. + /// + /// The pixel format. + /// The image this method extends. /// The formula to apply to perform the operation. + /// + /// The structure that specifies the portion of the image object to alter. + /// /// The . - public static Image Grayscale(this Image source, Rectangle rectangle, GrayscaleMode mode = GrayscaleMode.Bt709) + public static Image Grayscale(this Image source, GrayscaleMode mode, Rectangle rectangle) where TPixel : struct, IPixel { IImageProcessor processor = mode == GrayscaleMode.Bt709 diff --git a/src/ImageSharp/Processing/ColorMatrix/Hue.cs b/src/ImageSharp/Processing/ColorMatrix/Hue.cs index 8dbc55530..bcaf68d9a 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Hue.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Hue.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs b/src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs index 13a71a71e..a302bb2e9 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Kodachrome.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 937dca9ba..cbfeb7d05 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index f1a573c05..ac9f7391f 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Saturation.cs b/src/ImageSharp/Processing/ColorMatrix/Saturation.cs index c41f304b4..ffe336c62 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Saturation.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Saturation.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/ColorMatrix/Sepia.cs b/src/ImageSharp/Processing/ColorMatrix/Sepia.cs index 39eca4e8e..8488dcfe5 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Sepia.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Sepia.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Convolution/DetectEdges.cs b/src/ImageSharp/Processing/Convolution/DetectEdges.cs index 3aa1d0b51..368688f62 100644 --- a/src/ImageSharp/Processing/Convolution/DetectEdges.cs +++ b/src/ImageSharp/Processing/Convolution/DetectEdges.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Convolution/GaussianBlur.cs b/src/ImageSharp/Processing/Convolution/GaussianBlur.cs index 72abec6df..0b8c5383c 100644 --- a/src/ImageSharp/Processing/Convolution/GaussianBlur.cs +++ b/src/ImageSharp/Processing/Convolution/GaussianBlur.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Convolution/GaussianSharpen.cs b/src/ImageSharp/Processing/Convolution/GaussianSharpen.cs index 2ed99ea26..1a5d3c2df 100644 --- a/src/ImageSharp/Processing/Convolution/GaussianSharpen.cs +++ b/src/ImageSharp/Processing/Convolution/GaussianSharpen.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Effects/OilPainting.cs b/src/ImageSharp/Processing/Effects/OilPainting.cs index d4528b55b..fd65542e0 100644 --- a/src/ImageSharp/Processing/Effects/OilPainting.cs +++ b/src/ImageSharp/Processing/Effects/OilPainting.cs @@ -16,6 +16,35 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The pixel format. + /// The image this method extends. + /// The . + public static Image OilPaint(this Image source) + where TPixel : struct, IPixel + { + return OilPaint(source, 10, 15); + } + + /// + /// Alters the colors of the image recreating an oil painting effect with levels and brushSize + /// set to 10 and 15 respectively. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The . + public static Image OilPaint(this Image source, Rectangle rectangle) + where TPixel : struct, IPixel + { + return OilPaint(source, 10, 15, rectangle); + } + /// /// Alters the colors of the image recreating an oil painting effect. /// @@ -24,8 +53,8 @@ namespace ImageSharp /// The number of intensity levels. Higher values result in a broader range of color intensities forming part of the result image. /// The number of neighboring pixels used in calculating each individual pixel value. /// The . - public static Image OilPaint(this Image source, int levels = 10, int brushSize = 15) - where TPixel : struct, IPixel + public static Image OilPaint(this Image source, int levels, int brushSize) + where TPixel : struct, IPixel { return OilPaint(source, levels, brushSize, source.Bounds); } diff --git a/src/ImageSharp/Processing/Overlays/Glow.cs b/src/ImageSharp/Processing/Overlays/Glow.cs index 587bbe610..04c85e00c 100644 --- a/src/ImageSharp/Processing/Overlays/Glow.cs +++ b/src/ImageSharp/Processing/Overlays/Glow.cs @@ -5,8 +5,6 @@ namespace ImageSharp { - using System; - using ImageSharp.PixelFormats; using Processing.Processors; @@ -158,7 +156,7 @@ namespace ImageSharp public static Image Glow(this Image source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - GlowProcessor processor = new GlowProcessor(color, options) { Radius = radius, }; + var processor = new GlowProcessor(color, options) { Radius = radius, }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index 2eaf2e1ef..c04e88718 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -5,8 +5,6 @@ namespace ImageSharp { - using System; - using ImageSharp.PixelFormats; using Processing.Processors; @@ -162,7 +160,7 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options) where TPixel : struct, IPixel { - VignetteProcessor processor = new VignetteProcessor(color, options) { RadiusX = radiusX, RadiusY = radiusY }; + var processor = new VignetteProcessor(color, options) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 5cd67f053..a2fa1ddf3 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -83,25 +83,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel color = sourcePixels[offsetX, offsetY]; - - // Any channel will do since it's Grayscale. - sourcePixels[offsetX, offsetY] = color.ToVector4().X >= threshold ? upper : lower; - } - }); - } + ref TPixel color = ref row[x - startX]; + + // Any channel will do since it's Grayscale. + color = color.ToVector4().X >= threshold ? upper : lower; + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index af2d9f760..47811f0ec 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -85,18 +85,17 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) + int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; - this.Diffuser.Dither(sourcePixels, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); - } + int offsetX = x - startX; + TPixel sourceColor = row[offsetX]; + TPixel transformedColor = sourceColor.ToVector4().X >= this.Threshold ? this.UpperColor : this.LowerColor; + this.Diffuser.Dither(source, sourceColor, transformedColor, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index c4d71d9af..898389777 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -93,21 +93,17 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) + byte[] bytes = new byte[4]; + for (int y = minY; y < maxY; y++) { - for (int y = minY; y < maxY; y++) - { - int offsetY = y - startY; - byte[] bytes = ArrayPool.Shared.Rent(4); - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel sourceColor = sourcePixels[offsetX, offsetY]; - this.Dither.Dither(sourcePixels, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); - } + int offsetY = y - startY; + Span row = source.GetRowSpan(offsetY); - ArrayPool.Shared.Return(bytes); + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + TPixel sourceColor = row[offsetX]; + this.Dither.Dither(source, sourceColor, this.UpperColor, this.LowerColor, bytes, this.Index, offsetX, offsetY, maxX, maxY); } } } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs index cfe50150f..49af2667d 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs @@ -7,7 +7,6 @@ namespace ImageSharp.Processing.Processors { using System; using System.Numerics; - using System.Runtime.CompilerServices; using System.Threading.Tasks; using ImageSharp.PixelFormats; @@ -61,39 +60,23 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span row = source.GetRowSpan(y - startY); + for (int x = minX; x < maxX; x++) { - int offsetX = x - startX; - sourcePixels[offsetX, offsetY] = this.ApplyMatrix(sourcePixels[offsetX, offsetY], matrix, compand); - } - }); - } - } + ref TPixel pixel = ref row[x - startX]; + var vector = pixel.ToVector4(); - /// - /// Applies the color matrix against the given color. - /// - /// The source color. - /// The matrix. - /// Whether to compand the color during processing. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private TPixel ApplyMatrix(TPixel color, Matrix4x4 matrix, bool compand) - { - Vector4 vector = color.ToVector4(); + if (compand) + { + vector = vector.Expand(); + } - if (compand) - { - vector = vector.Expand(); + vector = Vector4.Transform(vector, matrix); + pixel.PackFromVector4(compand ? vector.Compress() : vector); + } + }); } - - vector = Vector4.Transform(vector, matrix); - TPixel packed = default(TPixel); - packed.PackFromVector4(compand ? vector.Compress() : vector); - return packed; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index 29086b53f..b6b56adb3 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Processing.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -56,10 +57,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -67,6 +67,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float rX = 0; @@ -83,6 +86,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelXWidth; fx++) { @@ -90,8 +94,7 @@ namespace ImageSharp.Processing.Processors int offsetX = x + fxr; offsetX = offsetX.Clamp(0, maxX); - - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); if (fy < kernelXHeight) { @@ -115,9 +118,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Sqrt((gX * gX) + (gY * gY)); float blue = MathF.Sqrt((bX * bX) + (bY * bY)); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index e39104793..efc00b08f 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor firstPassPixels = new PixelAccessor(width, height)) + using (var firstPassPixels = new PixelAccessor(width, height)) using (PixelAccessor sourcePixels = source.Lock()) { this.ApplyConvolution(firstPassPixels, sourcePixels, source.Bounds, this.KernelX); @@ -84,9 +84,11 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { - Vector4 destination = default(Vector4); + var destination = default(Vector4); // Apply each matrix multiplier to the color components for each pixel. for (int fy = 0; fy < kernelHeight; fy++) @@ -95,6 +97,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span row = sourcePixels.GetRowSpan(offsetY); for (int fx = 0; fx < kernelWidth; fx++) { @@ -103,14 +106,13 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = row[offsetX].ToVector4(); destination += kernel[fy, fx] * currentColor; } } - TPixel packed = default(TPixel); - packed.PackFromVector4(destination); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } }); } diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 17d5e3243..06607c87a 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -46,10 +46,9 @@ namespace ImageSharp.Processing.Processors int maxY = endY - 1; int maxX = endX - 1; - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( startY, @@ -57,6 +56,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { float red = 0; @@ -70,6 +72,7 @@ namespace ImageSharp.Processing.Processors int offsetY = y + fyr; offsetY = offsetY.Clamp(0, maxY); + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx < kernelLength; fx++) { @@ -78,7 +81,7 @@ namespace ImageSharp.Processing.Processors offsetX = offsetX.Clamp(0, maxX); - Vector4 currentColor = sourcePixels[offsetX, offsetY].ToVector4(); + var currentColor = sourceOffsetRow[offsetX].ToVector4(); currentColor *= this.KernelXY[fy, fx]; red += currentColor.X; @@ -87,9 +90,8 @@ namespace ImageSharp.Processing.Processors } } - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } }); diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index 5e7310e32..a3894f8d3 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -61,26 +61,22 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 alphaVector = new Vector4(1, 1, 1, this.Value); + var alphaVector = new Vector4(1, 1, 1, this.Value); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + ref TPixel pixel = ref row[x - startX]; + pixel.PackFromVector4(pixel.ToVector4() * alphaVector); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index cc95f15fc..153719191 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -6,7 +6,6 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Numerics; using System.Threading.Tasks; using ImageSharp.Memory; @@ -64,9 +63,8 @@ namespace ImageSharp.Processing.Processors int width = maxX - minX; - using (Buffer colors = new Buffer(width)) - using (Buffer amount = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var colors = new Buffer(width)) + using (var amount = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -81,11 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - int offsetY = y - startY; + Span destination = source.GetRowSpan(y - startY).Slice(minX - startX, width); - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - - // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + // This switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one blender.Blend(destination, colors, destination, amount); }); } diff --git a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs index f9f1585ea..121d25d1e 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs @@ -63,31 +63,26 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); - // TODO: Check this with other formats. - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); - vector = new Vector4(transformed, vector.W); + for (int x = minX; x < maxX; x++) + { + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); + // TODO: Check this with other formats. + Vector4 vector = pixel.ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); + vector = new Vector4(transformed, vector.W); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index 8308c57e2..1daead6e5 100644 --- a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs @@ -45,8 +45,8 @@ namespace ImageSharp.Processing.Processors int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; - Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); - Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); + var contrastVector = new Vector4(contrast, contrast, contrast, 1); + var shiftVector = new Vector4(.5F, .5F, .5F, 1); // Align start/end positions. int minX = Math.Max(0, startX); @@ -65,29 +65,26 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; + ref TPixel pixel = ref row[x - startX]; - Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); - vector -= shiftVector; - vector *= contrastVector; - vector += shiftVector; - TPixel packed = default(TPixel); - packed.PackFromVector4(vector.Compress()); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + Vector4 vector = pixel.ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + + pixel.PackFromVector4(vector.Compress()); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs index a0348970e..dc0b13442 100644 --- a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs @@ -44,27 +44,24 @@ namespace ImageSharp.Processing.Processors startY = 0; } - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span row = source.GetRowSpan(y - startY); + + for (int x = minX; x < maxX; x++) { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + ref TPixel pixel = ref row[x - startX]; - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(vector, color.W)); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } + var vector = pixel.ToVector4(); + Vector3 vector3 = inverseVector - new Vector3(vector.X, vector.Y, vector.Z); + + pixel.PackFromVector4(new Vector4(vector3, vector.W)); + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 1f06924af..37cc8a9d9 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -69,10 +69,9 @@ namespace ImageSharp.Processing.Processors startX = 0; } - using (PixelAccessor targetPixels = new PixelAccessor(source.Width, source.Height)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var targetPixels = new PixelAccessor(source.Width, source.Height)) { - sourcePixels.CopyTo(targetPixels); + source.CopyTo(targetPixels); Parallel.For( minY, @@ -80,6 +79,9 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + for (int x = startX; x < endX; x++) { int maxIntensity = 0; @@ -95,50 +97,33 @@ namespace ImageSharp.Processing.Processors int fyr = fy - radius; int offsetY = y + fyr; - // Skip the current row - if (offsetY < minY) - { - continue; - } + offsetY = offsetY.Clamp(0, maxY); - // Outwith the current bounds so break. - if (offsetY >= maxY) - { - break; - } + Span sourceOffsetRow = source.GetRowSpan(offsetY); for (int fx = 0; fx <= radius; fx++) { int fxr = fx - radius; int offsetX = x + fxr; + offsetX = offsetX.Clamp(0, maxX); - // Skip the column - if (offsetX < 0) - { - continue; - } + var vector = sourceOffsetRow[offsetX].ToVector4(); - if (offsetX < maxX) - { - // ReSharper disable once AccessToDisposedClosure - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + float sourceRed = vector.X; + float sourceBlue = vector.Z; + float sourceGreen = vector.Y; - float sourceRed = color.X; - float sourceBlue = color.Z; - float sourceGreen = color.Y; + int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * (levels - 1)); - int currentIntensity = (int)Math.Round((sourceBlue + sourceGreen + sourceRed) / 3.0 * (levels - 1)); + intensityBin[currentIntensity] += 1; + blueBin[currentIntensity] += sourceBlue; + greenBin[currentIntensity] += sourceGreen; + redBin[currentIntensity] += sourceRed; - intensityBin[currentIntensity] += 1; - blueBin[currentIntensity] += sourceBlue; - greenBin[currentIntensity] += sourceGreen; - redBin[currentIntensity] += sourceRed; - - if (intensityBin[currentIntensity] > maxIntensity) - { - maxIntensity = intensityBin[currentIntensity]; - maxIndex = currentIntensity; - } + if (intensityBin[currentIntensity] > maxIntensity) + { + maxIntensity = intensityBin[currentIntensity]; + maxIndex = currentIntensity; } } @@ -146,9 +131,8 @@ namespace ImageSharp.Processing.Processors float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); - TPixel packed = default(TPixel); - packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); - targetPixels[x, y] = packed; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(new Vector4(red, green, blue, sourceRow[x].ToVector4().W)); } } }); diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index 0287eaab8..ba6bb5c9d 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -66,47 +66,45 @@ namespace ImageSharp.Processing.Processors // Get the range on the y-plane to choose from. IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.ForEach( - range, - this.ParallelOptions, - y => + Parallel.ForEach( + range, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + int offsetPy = offset; + + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) { - int offsetY = y - startY; - int offsetPy = offset; + offsetPy--; + } - for (int x = minX; x < maxX; x += size) - { - int offsetX = x - startX; - int offsetPx = offset; + Span row = source.GetRowSpan(offsetY + offsetPy); - // Make sure that the offset is within the boundary of the image. - while (offsetY + offsetPy >= maxY) - { - offsetPy--; - } + for (int x = minX; x < maxX; x += size) + { + int offsetX = x - startX; + int offsetPx = offset; - while (x + offsetPx >= maxX) - { - offsetPx--; - } + while (x + offsetPx >= maxX) + { + offsetPx--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - TPixel pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; + // Get the pixel color in the centre of the soon to be pixelated area. + TPixel pixel = row[offsetX + offsetPx]; - // For each pixel in the pixelate size, set it to the centre color. - for (int l = offsetY; l < offsetY + size && l < maxY; l++) + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) { - for (int k = offsetX; k < offsetX + size && k < maxX; k++) - { - sourcePixels[k, l] = pixel; - } + source[k, l] = pixel; } } - }); - } + } + }); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 9de91c47b..4e4a36a38 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + Vector2 centre = Rectangle.Center(sourceRectangle); float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // Align start/end positions. @@ -73,8 +73,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -82,26 +81,26 @@ namespace ImageSharp.Processing.Processors } Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - using (Buffer amounts = new Buffer(width)) - { - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); - } + minY, + maxY, + this.ParallelOptions, + y => + { + using (var amounts = new Buffer(width)) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); - this.blender.Blend(destination, destination, rowColors, amounts); - } - }); + this.blender.Blend(destination, destination, rowColors, amounts); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index be431b07d..23ba5afe0 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -58,7 +58,7 @@ namespace ImageSharp.Processing.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel vignetteColor = this.VignetteColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + Vector2 centre = Rectangle.Center(sourceRectangle); float rX = this.RadiusX > 0 ? MathF.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; float rY = this.RadiusY > 0 ? MathF.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; float maxDistance = MathF.Sqrt((rX * rX) + (rY * rY)); @@ -81,8 +81,7 @@ namespace ImageSharp.Processing.Processors } int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) + using (var rowColors = new Buffer(width)) { for (int i = 0; i < width; i++) { @@ -95,7 +94,7 @@ namespace ImageSharp.Processing.Processors this.ParallelOptions, y => { - using (Buffer amounts = new Buffer(width)) + using (var amounts = new Buffer(width)) { int offsetY = y - startY; int offsetX = minX - startX; @@ -105,7 +104,7 @@ namespace ImageSharp.Processing.Processors amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); } - Span destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + Span destination = source.GetRowSpan(offsetY).Slice(offsetX, width); this.blender.Blend(destination, destination, rowColors, amounts); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index b67ef5bf1..ade5fa830 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Processing.Processors { using System; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -44,22 +44,18 @@ namespace ImageSharp.Processing.Processors int minX = Math.Max(this.CropRectangle.X, sourceRectangle.X); int maxX = Math.Min(this.CropRectangle.Right, sourceRectangle.Right); - using (PixelAccessor targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) + using (var targetPixels = new PixelAccessor(this.CropRectangle.Width, this.CropRectangle.Height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - targetPixels[x - minX, y - minY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(minX, y); + Span targetRow = targetPixels.GetRowSpan(y - minY); + SpanHelper.Copy(sourceRow, targetRow, maxX - minX); + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index 2faf77905..d4303c455 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Threading.Tasks; + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -57,24 +58,23 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfHeight = (int)Math.Ceiling(source.Height * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - halfHeight, - this.ParallelOptions, - y => - { - for (int x = 0; x < width; x++) - { - int newY = height - y - 1; - targetPixels[x, y] = sourcePixels[x, newY]; - targetPixels[x, newY] = sourcePixels[x, y]; - } - }); - } + Parallel.For( + 0, + halfHeight, + this.ParallelOptions, + y => + { + int newY = height - y - 1; + Span sourceRow = source.GetRowSpan(y); + Span altSourceRow = source.GetRowSpan(newY); + Span targetRow = targetPixels.GetRowSpan(y); + Span altTargetRow = targetPixels.GetRowSpan(newY); + + sourceRow.CopyTo(altTargetRow); + altSourceRow.CopyTo(targetRow); + }); source.SwapPixelsBuffers(targetPixels); } @@ -91,24 +91,24 @@ namespace ImageSharp.Processing.Processors int height = source.Height; int halfWidth = (int)Math.Ceiling(width * .5F); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < halfWidth; x++) { - for (int x = 0; x < halfWidth; x++) - { - int newX = width - x - 1; - targetPixels[x, y] = sourcePixels[newX, y]; - targetPixels[newX, y] = sourcePixels[x, y]; - } - }); - } + int newX = width - x - 1; + targetRow[x] = sourceRow[newX]; + targetRow[newX] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs index 3135551f8..4cc03d864 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Processing.Processors { - using System; using System.Numerics; using ImageSharp.PixelFormats; @@ -45,8 +44,8 @@ namespace ImageSharp.Processing.Processors /// protected Matrix3x2 GetCenteredMatrix(ImageBase source, Matrix3x2 matrix) { - Matrix3x2 translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); - Matrix3x2 translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); + var translationToTargetCenter = Matrix3x2.CreateTranslation(-this.CanvasRectangle.Width * .5F, -this.CanvasRectangle.Height * .5F); + var translateToSourceCenter = Matrix3x2.CreateTranslation(source.Width * .5F, source.Height * .5F); return (translationToTargetCenter * matrix) * translateToSourceCenter; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs index d49f37803..8aef87ec8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -1,3 +1,8 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp.Processing.Processors { using System; @@ -22,31 +27,52 @@ namespace ImageSharp.Processing.Processors public int Left; /// - /// The span of weights pointing to . + /// The length of the weights window + /// + public int Length; + + /// + /// The index in the destination buffer /// - public Span Span; + private readonly int flatStartIndex; + + /// + /// The buffer containing the weights values. + /// + private readonly Buffer buffer; /// /// Initializes a new instance of the struct. /// + /// The destination index in the buffer /// The local left index - /// The span + /// The span + /// The length of the window [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal WeightsWindow(int left, Span span) + internal WeightsWindow(int index, int left, Buffer2D buffer, int length) { + this.flatStartIndex = (index * buffer.Width) + left; this.Left = left; - this.Span = span; + this.buffer = buffer; + this.Length = length; } /// - /// Gets an unsafe float* pointer to the beginning of . + /// Gets a reference to the first item of the window. /// - public ref float Ptr => ref this.Span.DangerousGetPinnableReference(); + /// The reference to the first item of the window + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref float GetStartReference() + { + return ref this.buffer[this.flatStartIndex]; + } /// - /// Gets the lenghth of the weights window + /// Gets the span representing the portion of the that this window covers /// - public int Length => this.Span.Length; + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span GetWindowSpan() => this.buffer.Slice(this.flatStartIndex, this.Length); /// /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. @@ -57,7 +83,7 @@ namespace ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ComputeWeightedRowSum(Span rowSpan, int sourceX) { - ref float horizontalValues = ref this.Ptr; + ref float horizontalValues = ref this.GetStartReference(); int left = this.Left; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); @@ -84,7 +110,7 @@ namespace ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ComputeExpandedWeightedRowSum(Span rowSpan, int sourceX) { - ref float horizontalValues = ref this.Ptr; + ref float horizontalValues = ref this.GetStartReference(); int left = this.Left; ref Vector4 vecPtr = ref Unsafe.Add(ref rowSpan.DangerousGetPinnableReference(), left + sourceX); @@ -112,7 +138,7 @@ namespace ImageSharp.Processing.Processors [MethodImpl(MethodImplOptions.AggressiveInlining)] public Vector4 ComputeWeightedColumnSum(Buffer2D firstPassPixels, int x, int sourceY) { - ref float verticalValues = ref this.Ptr; + ref float verticalValues = ref this.GetStartReference(); int left = this.Left; // Destination color components @@ -134,7 +160,7 @@ namespace ImageSharp.Processing.Processors /// internal class WeightsBuffer : IDisposable { - private Buffer2D dataBuffer; + private readonly Buffer2D dataBuffer; /// /// Initializes a new instance of the class. @@ -169,8 +195,7 @@ namespace ImageSharp.Processing.Processors /// The weights public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) { - Span span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1); - return new WeightsWindow(leftIdx, span); + return new WeightsWindow(destIdx, leftIdx, this.dataBuffer, rightIdx - leftIdx + 1); } } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index e2f77d812..7245b961f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,9 +6,7 @@ namespace ImageSharp.Processing.Processors { using System; - using System.Buffers; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; using ImageSharp.PixelFormats; @@ -90,7 +88,7 @@ namespace ImageSharp.Processing.Processors IResampler sampler = this.Sampler; float radius = MathF.Ceiling(scale * sampler.Radius); - WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); + var result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { @@ -114,7 +112,7 @@ namespace ImageSharp.Processing.Processors WeightsWindow ws = result.GetWeightsWindow(i, left, right); result.Weights[i] = ws; - ref float weights = ref ws.Ptr; + ref float weightsBaseRef = ref ws.GetStartReference(); for (int j = left; j <= right; j++) { @@ -122,7 +120,7 @@ namespace ImageSharp.Processing.Processors sum += weight; // weights[j - left] = weight: - Unsafe.Add(ref weights, j - left) = weight; + Unsafe.Add(ref weightsBaseRef, j - left) = weight; } // Normalise, best to do it here rather than in the pixel loop later on. @@ -131,7 +129,7 @@ namespace ImageSharp.Processing.Processors for (int w = 0; w < ws.Length; w++) { // weights[w] = weights[w] / sum: - ref float wRef = ref Unsafe.Add(ref weights, w); + ref float wRef = ref Unsafe.Add(ref weightsBaseRef, w); wRef = wRef / sum; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 61a64f60f..68ff1397d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -73,26 +73,24 @@ namespace ImageSharp.Processing.Processors float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Y coordinates of source points - int originY = (int)(((y - startY) * heightFactor) + sourceY); + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + // Y coordinates of source points + Span sourceRow = source.GetRowSpan((int)(((y - startY) * heightFactor) + sourceY)); + Span targetRow = targetPixels.GetRowSpan(y); - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; - } - }); - } + for (int x = minX; x < maxX; x++) + { + // X coordinates of source points + targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; + } + }); // Break out now. source.SwapPixelsBuffers(targetPixels); @@ -106,10 +104,9 @@ namespace ImageSharp.Processing.Processors // are the upper and lower bounds of the source rectangle. // TODO: Using a transposed variant of 'firstPassPixels' could eliminate the need for the WeightsWindow.ComputeWeightedColumnSum() method, and improve speed! - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - using (Buffer2D firstPassPixels = new Buffer2D(width, source.Height)) + using (var firstPassPixels = new Buffer2D(width, source.Height)) { firstPassPixels.Clear(); @@ -120,21 +117,18 @@ namespace ImageSharp.Processing.Processors y => { // TODO: Without Parallel.For() this buffer object could be reused: - using (Buffer tempRowBuffer = new Buffer(sourcePixels.Width)) + using (var tempRowBuffer = new Buffer(source.Width)) { - Span sourceRow = sourcePixels.GetRowSpan(y); - - PixelOperations.Instance.ToVector4( - sourceRow, - tempRowBuffer, - sourceRow.Length); + Span firstPassRow = firstPassPixels.GetRowSpan(y); + Span sourceRow = source.GetRowSpan(y); + PixelOperations.Instance.ToVector4(sourceRow, tempRowBuffer, sourceRow.Length); if (this.Compand) { for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeExpandedWeightedRowSum(tempRowBuffer, sourceX); } } else @@ -142,7 +136,7 @@ namespace ImageSharp.Processing.Processors for (int x = minX; x < maxX; x++) { WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; - firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); + firstPassRow[x] = window.ComputeWeightedRowSum(tempRowBuffer, sourceX); } } } @@ -157,6 +151,7 @@ namespace ImageSharp.Processing.Processors { // Ensure offsets are normalised for cropping and padding. WeightsWindow window = this.VerticalWeights.Weights[y - startY]; + Span targetRow = targetPixels.GetRowSpan(y); if (this.Compand) { @@ -165,9 +160,9 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); destination = destination.Compress(); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } else @@ -177,9 +172,8 @@ namespace ImageSharp.Processing.Processors // Destination color components Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x, sourceY); - TPixel d = default(TPixel); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + ref TPixel pixel = ref targetRow[x]; + pixel.PackFromVector4(destination); } } }); diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index fc5d29b06..15d7ca641 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Rotate(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Rotate(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -78,7 +78,7 @@ namespace ImageSharp.Processing.Processors return; } - this.processMatrix = Point.CreateRotation(new Point(0, 0), -this.Angle); + this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0)); if (this.Expand) { this.CreateNewCanvas(sourceRectangle, this.processMatrix); @@ -128,7 +128,7 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { using (PixelAccessor sourcePixels = source.Lock()) { @@ -161,24 +161,22 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(height - y - 1); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = width - x - 1; - int newY = height - y - 1; - targetPixels[newX, newY] = sourcePixels[x, y]; - } - }); - } + targetRow[width - x - 1] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -193,23 +191,21 @@ namespace ImageSharp.Processing.Processors int width = source.Width; int height = source.Height; - using (PixelAccessor targetPixels = new PixelAccessor(height, width)) + using (var targetPixels = new PixelAccessor(height, width)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + int newX = height - y - 1; + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) - { - int newX = height - y - 1; - targetPixels[newX, x] = sourcePixels[x, y]; - } - }); - } + targetPixels[newX, x] = sourceRow[x]; + } + }); source.SwapPixelsBuffers(targetPixels); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index 40ea6a94e..264cf4a53 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Processing.Processors using System; using System.Numerics; using System.Threading.Tasks; - + using ImageSharp.Memory; using ImageSharp.PixelFormats; /// @@ -45,26 +45,26 @@ namespace ImageSharp.Processing.Processors int width = this.CanvasRectangle.Width; Matrix3x2 matrix = this.GetCenteredMatrix(source, this.processMatrix); - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) + using (var targetPixels = new PixelAccessor(width, height)) { - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - 0, - height, - this.ParallelOptions, - y => + Parallel.For( + 0, + height, + this.ParallelOptions, + y => + { + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < width; x++) { - for (int x = 0; x < width; x++) + var transformedPoint = Point.Skew(new Point(x, y), matrix); + + if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) { - Point transformedPoint = Point.Skew(new Point(x, y), matrix); - if (source.Bounds.Contains(transformedPoint.X, transformedPoint.Y)) - { - targetPixels[x, y] = sourcePixels[transformedPoint.X, transformedPoint.Y]; - } + targetRow[x] = source[transformedPoint.X, transformedPoint.Y]; } - }); - } + } + }); source.SwapPixelsBuffers(targetPixels); } @@ -73,7 +73,7 @@ namespace ImageSharp.Processing.Processors /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) { - this.processMatrix = Point.CreateSkew(new Point(0, 0), -this.AngleX, -this.AngleY); + this.processMatrix = Matrix3x2Extensions.CreateSkew(-this.AngleX, -this.AngleY, new Point(0, 0)); if (this.Expand) { this.CreateNewCanvas(sourceRectangle, this.processMatrix); diff --git a/src/ImageSharp/Processing/Transforms/AutoOrient.cs b/src/ImageSharp/Processing/Transforms/AutoOrient.cs index f9d3a60aa..07e5d5bc9 100644 --- a/src/ImageSharp/Processing/Transforms/AutoOrient.cs +++ b/src/ImageSharp/Processing/Transforms/AutoOrient.cs @@ -5,12 +5,9 @@ namespace ImageSharp { - using System; - using ImageSharp.PixelFormats; - using Processing; - using Processing.Processors; + using ImageSharp.Processing; /// /// Extension methods for the type. @@ -80,7 +77,7 @@ namespace ImageSharp return Orientation.Unknown; } - Orientation orientation = (Orientation)value.Value; + var orientation = (Orientation)value.Value; source.MetaData.ExifProfile.SetValue(ExifTag.Orientation, (ushort)Orientation.TopLeft); diff --git a/src/ImageSharp/Processing/Transforms/Flip.cs b/src/ImageSharp/Processing/Transforms/Flip.cs index 342b4dd46..1c8baebf1 100644 --- a/src/ImageSharp/Processing/Transforms/Flip.cs +++ b/src/ImageSharp/Processing/Transforms/Flip.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs index c87688266..712efec12 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs @@ -5,7 +5,6 @@ namespace ImageSharp.Processing { - using System; using System.Linq; using ImageSharp.PixelFormats; @@ -86,17 +85,17 @@ namespace ImageSharp.Processing if (options.CenterCoordinates.Any()) { - float center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); - destinationY = (int)center + (height / 2); + float center = -(ratio * sourceHeight) * options.CenterCoordinates.ToArray()[1]; + destinationY = (int)MathF.Round(center + (height / 2F)); if (destinationY > 0) { destinationY = 0; } - if (destinationY < (int)(height - (sourceHeight * ratio))) + if (destinationY < (int)MathF.Round(height - (sourceHeight * ratio))) { - destinationY = (int)(height - (sourceHeight * ratio)); + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); } } else @@ -111,10 +110,10 @@ namespace ImageSharp.Processing case AnchorPosition.Bottom: case AnchorPosition.BottomLeft: case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); break; default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); + destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); break; } } @@ -127,17 +126,17 @@ namespace ImageSharp.Processing if (options.CenterCoordinates.Any()) { - float center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; - destinationX = (int)center + (width / 2); + float center = -(ratio * sourceWidth) * options.CenterCoordinates.First(); + destinationX = (int)MathF.Round(center + (width / 2F)); if (destinationX > 0) { destinationX = 0; } - if (destinationX < (int)(width - (sourceWidth * ratio))) + if (destinationX < (int)MathF.Round(width - (sourceWidth * ratio))) { - destinationX = (int)(width - (sourceWidth * ratio)); + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); } } else @@ -152,10 +151,10 @@ namespace ImageSharp.Processing case AnchorPosition.Right: case AnchorPosition.TopRight: case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); break; default: - destinationX = (int)((width - (sourceWidth * ratio)) / 2); + destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); break; } } @@ -202,7 +201,7 @@ namespace ImageSharp.Processing if (percentHeight < percentWidth) { ratio = percentHeight; - destinationWidth = Convert.ToInt32(sourceWidth * percentHeight); + destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); switch (options.Position) { @@ -214,17 +213,17 @@ namespace ImageSharp.Processing case AnchorPosition.Right: case AnchorPosition.TopRight: case AnchorPosition.BottomRight: - destinationX = (int)(width - (sourceWidth * ratio)); + destinationX = (int)MathF.Round(width - (sourceWidth * ratio)); break; default: - destinationX = Convert.ToInt32((width - (sourceWidth * ratio)) / 2); + destinationX = (int)MathF.Round((width - (sourceWidth * ratio)) / 2F); break; } } else { ratio = percentWidth; - destinationHeight = Convert.ToInt32(sourceHeight * percentWidth); + destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); switch (options.Position) { @@ -236,10 +235,10 @@ namespace ImageSharp.Processing case AnchorPosition.Bottom: case AnchorPosition.BottomLeft: case AnchorPosition.BottomRight: - destinationY = (int)(height - (sourceHeight * ratio)); + destinationY = (int)MathF.Round(height - (sourceHeight * ratio)); break; default: - destinationY = (int)((height - (sourceHeight * ratio)) / 2); + destinationY = (int)MathF.Round((height - (sourceHeight * ratio)) / 2F); break; } } @@ -274,8 +273,8 @@ namespace ImageSharp.Processing float percentHeight = MathF.Abs(height / (float)sourceHeight); float percentWidth = MathF.Abs(width / (float)sourceWidth); - int boxPadHeight = height > 0 ? height : Convert.ToInt32(sourceHeight * percentWidth); - int boxPadWidth = width > 0 ? width : Convert.ToInt32(sourceWidth * percentHeight); + int boxPadHeight = height > 0 ? height : (int)MathF.Round(sourceHeight * percentWidth); + int boxPadWidth = width > 0 ? width : (int)MathF.Round(sourceWidth * percentHeight); // Only calculate if upscaling. if (sourceWidth < boxPadWidth && sourceHeight < boxPadHeight) @@ -356,17 +355,17 @@ namespace ImageSharp.Processing float percentWidth = MathF.Abs(width / (float)source.Width); // Integers must be cast to floats to get needed precision - float ratio = (float)options.Size.Height / options.Size.Width; - float sourceRatio = (float)source.Height / source.Width; + float ratio = options.Size.Height / (float)options.Size.Width; + float sourceRatio = source.Height / (float)source.Width; if (sourceRatio < ratio) { - destinationHeight = Convert.ToInt32(source.Height * percentWidth); + destinationHeight = (int)MathF.Round(source.Height * percentWidth); height = destinationHeight; } else { - destinationWidth = Convert.ToInt32(source.Width * percentHeight); + destinationWidth = (int)MathF.Round(source.Width * percentHeight); width = destinationWidth; } @@ -389,38 +388,54 @@ namespace ImageSharp.Processing { int width = options.Size.Width; int height = options.Size.Height; + int sourceWidth = source.Width; + int sourceHeight = source.Height; int destinationWidth; int destinationHeight; // Don't upscale - if (width > source.Width || height > source.Height) + if (width > sourceWidth || height > sourceHeight) { - options.Size = new Size(source.Width, source.Height); - return new Rectangle(0, 0, source.Width, source.Height); + options.Size = new Size(sourceWidth, sourceHeight); + return new Rectangle(0, 0, sourceWidth, sourceHeight); } - float sourceRatio = (float)source.Height / source.Width; + // Fractional variants for preserving aspect ratio. + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); + + float sourceRatio = (float)sourceHeight / sourceWidth; // Find the shortest distance to go. - int widthDiff = source.Width - width; - int heightDiff = source.Height - height; + int widthDiff = sourceWidth - width; + int heightDiff = sourceHeight - height; if (widthDiff < heightDiff) { - destinationHeight = Convert.ToInt32(width * sourceRatio); + destinationHeight = (int)MathF.Round(width * sourceRatio); height = destinationHeight; destinationWidth = width; } else if (widthDiff > heightDiff) { - destinationWidth = Convert.ToInt32(height / sourceRatio); + destinationWidth = (int)MathF.Round(height / sourceRatio); destinationHeight = height; width = destinationWidth; } else { - destinationWidth = width; - destinationHeight = height; + if (height > width) + { + destinationWidth = width; + destinationHeight = (int)MathF.Round(sourceHeight * percentWidth); + height = destinationHeight; + } + else + { + destinationHeight = height; + destinationWidth = (int)MathF.Round(sourceWidth * percentHeight); + width = destinationWidth; + } } // Replace the size to match the rectangle. @@ -428,4 +443,4 @@ namespace ImageSharp.Processing return new Rectangle(0, 0, destinationWidth, destinationHeight); } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Transforms/Pad.cs b/src/ImageSharp/Processing/Transforms/Pad.cs index 42851e205..4bded0772 100644 --- a/src/ImageSharp/Processing/Transforms/Pad.cs +++ b/src/ImageSharp/Processing/Transforms/Pad.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index 543b98297..3399ff0e3 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -5,11 +5,9 @@ namespace ImageSharp { - using System; - using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// @@ -31,12 +29,12 @@ namespace ImageSharp // Ensure size is populated across both dimensions. if (options.Size.Width == 0 && options.Size.Height > 0) { - options.Size = new Size(source.Width * options.Size.Height / source.Height, options.Size.Height); + options.Size = new Size((int)MathF.Round(source.Width * options.Size.Height / (float)source.Height), options.Size.Height); } if (options.Size.Height == 0 && options.Size.Width > 0) { - options.Size = new Size(options.Size.Width, source.Height * options.Size.Width / source.Width); + options.Size = new Size(options.Size.Width, (int)MathF.Round(source.Height * options.Size.Width / (float)source.Width)); } Rectangle targetRectangle = ResizeHelper.CalculateTargetLocationAndBounds(source, options); @@ -58,6 +56,21 @@ namespace ImageSharp return Resize(source, size.Width, size.Height, new BicubicResampler(), false); } + /// + /// Resizes an image to the given . + /// + /// The pixel format. + /// The image to resize. + /// The target image size. + /// Whether to compress and expand the image color-space to gamma correct the image during processing. + /// The + /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image + public static Image Resize(this Image source, Size size, bool compand) + where TPixel : struct, IPixel + { + return Resize(source, size.Width, size.Height, new BicubicResampler(), compand); + } + /// /// Resizes an image to the given width and height. /// @@ -140,26 +153,25 @@ namespace ImageSharp /// Whether to compress and expand the image color-space to gamma correct the image during processing. /// The /// Passing zero for one of height or width will automatically preserve the aspect ratio of the original image - public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand = false) + public static Image Resize(this Image source, int width, int height, IResampler sampler, Rectangle sourceRectangle, Rectangle targetRectangle, bool compand) where TPixel : struct, IPixel { if (width == 0 && height > 0) { - width = source.Width * height / source.Height; + width = (int)MathF.Round(source.Width * height / (float)source.Height); targetRectangle.Width = width; } if (height == 0 && width > 0) { - height = source.Height * width / source.Width; + height = (int)MathF.Round(source.Height * width / (float)source.Width); targetRectangle.Height = height; } Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - ResizeProcessor processor = - new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }; + var processor = new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }; source.ApplyProcessor(processor, sourceRectangle); return source; diff --git a/src/ImageSharp/Processing/Transforms/Rotate.cs b/src/ImageSharp/Processing/Transforms/Rotate.cs index de9dbdc3b..af7c06a27 100644 --- a/src/ImageSharp/Processing/Transforms/Rotate.cs +++ b/src/ImageSharp/Processing/Transforms/Rotate.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; using Processing.Processors; /// diff --git a/src/ImageSharp/Processing/Transforms/RotateFlip.cs b/src/ImageSharp/Processing/Transforms/RotateFlip.cs index 460d004cb..805deb8d1 100644 --- a/src/ImageSharp/Processing/Transforms/RotateFlip.cs +++ b/src/ImageSharp/Processing/Transforms/RotateFlip.cs @@ -9,7 +9,7 @@ namespace ImageSharp using ImageSharp.PixelFormats; - using Processing; + using ImageSharp.Processing; /// /// Extension methods for the type. diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 40bce74c3..e19df4cfa 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Quantizers /// /// Maximum allowed color depth /// - private int colors; + private byte colors; /// /// The reduced image palette @@ -58,7 +58,7 @@ namespace ImageSharp.Quantizers /// public override QuantizedImage Quantize(ImageBase image, int maxColors) { - this.colors = maxColors.Clamp(1, 255); + this.colors = (byte)maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.palette = null; @@ -66,7 +66,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -78,11 +78,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. @@ -121,7 +123,7 @@ namespace ImageSharp.Quantizers /// protected override TPixel[] GetPalette() { - return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, 1))); + return this.palette ?? (this.palette = this.octree.Palletize(Math.Max(this.colors, (byte)1))); } /// @@ -141,6 +143,12 @@ namespace ImageSharp.Quantizers return this.GetClosestPixel(pixel, this.palette, this.colorMap); } + pixel.ToXyzwBytes(this.pixelBuffer, 0); + if (this.pixelBuffer[3] == 0) + { + return this.colors; + } + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); } @@ -490,7 +498,7 @@ namespace ImageSharp.Quantizers byte b = (this.blue / this.pixelCount).ToByte(); // And set the color of the palette entry - TPixel pixel = default(TPixel); + var pixel = default(TPixel); pixel.PackFromBytes(r, g, b, 255); palette[index] = pixel; diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index 7e07da6c3..cf3ff94ee 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -51,7 +51,7 @@ namespace ImageSharp.Quantizers for (int i = 0; i < constants.Length; i++) { constants[i].ToXyzwBytes(this.pixelBuffer, 0); - TPixel packed = default(TPixel); + var packed = default(TPixel); packed.PackFromBytes(this.pixelBuffer[0], this.pixelBuffer[1], this.pixelBuffer[2], this.pixelBuffer[3]); safe[i] = packed; } @@ -72,7 +72,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -84,11 +84,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs index 48f33f98b..2e3ea7a54 100644 --- a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Quantizers { + using System; using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; @@ -54,35 +55,30 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; - TPixel[] colorPalette; - using (PixelAccessor pixels = image.Lock()) + // Call the FirstPass function if not a single pass algorithm. + // For something like an Octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) { - // Call the FirstPass function if not a single pass algorithm. - // For something like an Octree quantizer, this will run through - // all image pixels, build a data structure, and create a palette. - if (!this.singlePass) - { - this.FirstPass(pixels, width, height); - } + this.FirstPass(image, width, height); + } - // Collect the palette. Required before the second pass runs. - colorPalette = this.GetPalette(); + // Collect the palette. Required before the second pass runs. + TPixel[] colorPalette = this.GetPalette(); - if (this.Dither) - { - // We clone the image as we don't want to alter the original. - using (Image clone = new Image(image)) - using (PixelAccessor clonedPixels = clone.Lock()) - { - this.SecondPass(clonedPixels, quantizedPixels, width, height); - } - } - else + if (this.Dither) + { + // We clone the image as we don't want to alter the original. + using (var clone = new Image(image)) { - this.SecondPass(pixels, quantizedPixels, width, height); + this.SecondPass(clone, quantizedPixels, width, height); } } + else + { + this.SecondPass(image, quantizedPixels, width, height); + } return new QuantizedImage(width, height, colorPalette, quantizedPixels); } @@ -93,16 +89,18 @@ namespace ImageSharp.Quantizers /// The source data /// The width in pixels of the image. /// The height in pixels of the image. - protected virtual void FirstPass(PixelAccessor source, int width, int height) + protected virtual void FirstPass(ImageBase source, int width, int height) { // Loop through each row for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Now I have the pixel, call the FirstPassQuantize function... - this.InitialQuantizePixel(source[x, y]); + this.InitialQuantizePixel(row[x]); } } } @@ -114,7 +112,7 @@ namespace ImageSharp.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected abstract void SecondPass(PixelAccessor source, byte[] output, int width, int height); + protected abstract void SecondPass(ImageBase source, byte[] output, int width, int height); /// /// Override this to process the pixel in the first pass of the algorithm @@ -155,7 +153,7 @@ namespace ImageSharp.Quantizers // Not found - loop through the palette and find the nearest match. byte colorIndex = 0; float leastDistance = int.MaxValue; - Vector4 vector = pixel.ToVector4(); + var vector = pixel.ToVector4(); for (int index = 0; index < colorPalette.Length; index++) { diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index fb63c9dcd..aecca771c 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -183,7 +183,7 @@ namespace ImageSharp.Quantizers float b = Volume(this.colorCube[k], this.vmb) / weight; float a = Volume(this.colorCube[k], this.vma) / weight; - TPixel color = default(TPixel); + var color = default(TPixel); color.PackFromVector4(new Vector4(r, g, b, a) / 255F); this.palette[k] = color; } @@ -221,7 +221,7 @@ namespace ImageSharp.Quantizers } /// - protected override void FirstPass(PixelAccessor source, int width, int height) + protected override void FirstPass(ImageBase source, int width, int height) { // Build up the 3-D color histogram // Loop through each row @@ -240,7 +240,7 @@ namespace ImageSharp.Quantizers } /// - protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) + protected override void SecondPass(ImageBase source, byte[] output, int width, int height) { // Load up the values for the first pixel. We can use these to speed up the second // pass of the algorithm by avoiding transforming rows of identical color. @@ -252,11 +252,13 @@ namespace ImageSharp.Quantizers for (int y = 0; y < height; y++) { + Span row = source.GetRowSpan(y); + // And loop through each column for (int x = 0; x < width; x++) { // Get the pixel. - sourcePixel = source[x, y]; + sourcePixel = row[x]; // Check if this is the same as the last pixel. If so use that value // rather than calculating it again. This is an inexpensive optimization. diff --git a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs index 5b5d14750..1d4ed1193 100644 --- a/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs +++ b/tests/ImageSharp.Benchmarks/Image/CopyPixels.cs @@ -5,19 +5,20 @@ namespace ImageSharp.Benchmarks.Image { + using System; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; - using ImageSharp.PixelFormats; + using ImageSharp.Memory; public class CopyPixels : BenchmarkBase { - [Benchmark(Description = "Copy by Pixel")] - public Rgba32 CopyByPixel() + [Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")] + public Rgba32 CopyByPixelAccesor() { - using (Image source = new Image(1024, 768)) - using (Image target = new Image(1024, 768)) + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) { using (PixelAccessor sourcePixels = source.Lock()) using (PixelAccessor targetPixels = target.Lock()) @@ -38,5 +39,81 @@ namespace ImageSharp.Benchmarks.Image } } } + + [Benchmark(Description = "PixelAccessor Copy by Span")] + public Rgba32 CopyByPixelAccesorSpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + using (PixelAccessor sourcePixels = source.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = sourcePixels.GetRowSpan(y); + Span targetRow = targetPixels.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return targetPixels[0, 0]; + } + } + } + + [Benchmark(Description = "Copy by indexer")] + public Rgba32 Copy() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + for (int x = 0; x < source.Width; x++) + { + target[x, y] = source[x, y]; + } + }); + + return target[0, 0]; + } + } + + [Benchmark(Description = "Copy by Span")] + public Rgba32 CopySpan() + { + using (var source = new Image(1024, 768)) + using (var target = new Image(1024, 768)) + { + Parallel.For( + 0, + source.Height, + Configuration.Default.ParallelOptions, + y => + { + Span sourceRow = source.GetRowSpan(y); + Span targetRow = target.GetRowSpan(y); + + for (int x = 0; x < source.Width; x++) + { + targetRow[x] = sourceRow[x]; + } + }); + + return target[0, 0]; + } + } } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index d4920ff08..e77716ec8 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -9,7 +9,7 @@ namespace ImageSharp.Benchmarks using BenchmarkDotNet.Attributes; - using Processing; + using ImageSharp.Processing; using CoreImage = ImageSharp.Image; diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 7608d3065..be4929d9a 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -79,7 +79,7 @@ namespace ImageSharp.Benchmarks int startX = sourceRectangle.X; int endX = sourceRectangle.Right; TPixel glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + Vector2 centre = Rectangle.Center(sourceRectangle); float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // Align start/end positions. diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 7ea459aae..b882de0cc 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -10,6 +10,7 @@ namespace ImageSharp.Sandbox46 using ImageSharp.Tests; using ImageSharp.Tests.Colors; + using ImageSharp.Tests.Processing.Transforms; using Xunit.Abstractions; diff --git a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs index 1291160b2..21e86d434 100644 --- a/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs +++ b/tests/ImageSharp.Tests/Common/PixelDataPoolTests.cs @@ -44,7 +44,7 @@ namespace ImageSharp.Tests int max = isRawData ? PixelDataPool.CalculateMaxArrayLength() : PixelDataPool.CalculateMaxArrayLength(); - Assert.Equal(max < int.MaxValue, !isRawData); + Assert.Equal(max > 1024 * 1024, !isRawData); } [Fact] diff --git a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs index ff6892199..3d49a70b8 100644 --- a/tests/ImageSharp.Tests/Drawing/BeziersTests.cs +++ b/tests/ImageSharp.Tests/Drawing/BeziersTests.cs @@ -43,8 +43,8 @@ namespace ImageSharp.Tests.Drawing Assert.Equal(Rgba32.HotPink, sourcePixels[138, 115]); //start points - Assert.Equal(Rgba32.HotPink, sourcePixels[10, 400]); - Assert.Equal(Rgba32.HotPink, sourcePixels[300, 400]); + Assert.Equal(Rgba32.HotPink, sourcePixels[10, 395]); + Assert.Equal(Rgba32.HotPink, sourcePixels[300, 395]); //curve points should not be never be set Assert.Equal(Rgba32.Blue, sourcePixels[30, 10]); @@ -85,14 +85,14 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - //top of curve + // top of curve Assert.Equal(mergedColor, sourcePixels[138, 115]); - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); + // start points + Assert.Equal(mergedColor, sourcePixels[10, 395]); + Assert.Equal(mergedColor, sourcePixels[300, 395]); - //curve points should not be never be set + // curve points should not be never be set Assert.Equal(Rgba32.Blue, sourcePixels[30, 10]); Assert.Equal(Rgba32.Blue, sourcePixels[240, 30]); diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs index 6c742c2e0..23f0569e7 100644 --- a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs +++ b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Tests.Drawing .Select(x=> new object[] { x }); [Theory] - [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] public void DrawBlendedValues(TestImageProvider provider, PixelBlenderMode mode) where TPixel : struct, IPixel { @@ -34,7 +34,7 @@ namespace ImageSharp.Tests.Drawing } [Theory] - [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)] public void DrawBlendedValues_transparent(TestImageProvider provider, PixelBlenderMode mode) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 030034a8f..42353a2aa 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests public class DrawImageTest : FileTestBase { - private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass; + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; public static readonly string[] TestFiles = { TestImages.Jpeg.Baseline.Calliphora, diff --git a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs index 7d54879c3..4ba4a3a83 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawPathTests.cs @@ -13,6 +13,7 @@ namespace ImageSharp.Tests.Drawing using ImageSharp.PixelFormats; using Xunit; + using ImageSharp.Drawing.Pens; public class DrawPathTests : FileTestBase { @@ -43,7 +44,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); @@ -89,7 +90,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + Assert.Equal(mergedColor, sourcePixels[11, 11]); Assert.Equal(mergedColor, sourcePixels[199, 149]); @@ -98,5 +99,32 @@ namespace ImageSharp.Tests.Drawing } } + + [Fact] + public void PathExtendingOffEdgeOfImageShouldNotBeCropped() + { + + string path = this.CreateOutputDirectory("Drawing", "Path"); + using (var image = new Image(256, 256)) + { + image.Fill(Rgba32.Black); + var pen = Pens.Solid(Rgba32.White, 5f); + + for (int i = 0; i < 300; i += 20) + { + image.DrawLines(pen, new Vector2[] { new Vector2(100, 2), new Vector2(-10, i) }); + } + + using (FileStream output = File.OpenWrite($"{path}/ClippedLines.png")) + { + image + .Save(output); + } + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Rgba32.White, sourcePixels[0, 90]); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs index c7b789da0..58e55c124 100644 --- a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -8,6 +8,9 @@ namespace ImageSharp.Tests.Drawing using Moq; using ImageSharp.PixelFormats; + using System; + using ImageSharp.Drawing.Pens; + using System.Numerics; public class FillRegionProcessorTests { @@ -26,14 +29,61 @@ namespace ImageSharp.Tests.Drawing Mock region = new Mock(); region.Setup(x => x.Bounds).Returns(bounds); - GraphicsOptions options = new GraphicsOptions(antialias) { + GraphicsOptions options = new GraphicsOptions(antialias) + { AntialiasSubpixelDepth = 1 }; FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); Image img = new Image(1, 1); processor.Apply(img, bounds); - region.Verify(x => x.Scan(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(4)); + region.Verify(x => x.Scan(It.IsAny(), It.IsAny>()), Times.Exactly(4)); + } + + [Fact] + public void FillOffCanvas() + { + + ImageSharp.Rectangle bounds = new ImageSharp.Rectangle(-100, -10, 10, 10); + + Mock> brush = new Mock>(); + Mock region = new Mock(); + region.Setup(x => x.Bounds).Returns(bounds); + + region.Setup(x => x.MaxIntersections).Returns(10); + region.Setup(x => x.Scan(It.IsAny(), It.IsAny>())) + .Returns>((y, span) => + { + if (y < 5) + { + span[0] = -10f; + span[1] = 100f; + return 2; + } + return 0; + }); + + GraphicsOptions options = new GraphicsOptions(true) + { + }; + FillRegionProcessor processor = new FillRegionProcessor(brush.Object, region.Object, options); + Image img = new Image(10, 10); + processor.Apply(img, bounds); + } + + + + [Fact] + public void DrawOffCanvas() + { + + using (var img = new Image(10, 10)) + { + img.DrawLines(new Pen(Rgba32.Black, 10), new Vector2[] { + new Vector2(-10, 5), + new Vector2(20, 5), + }); + } } } } diff --git a/tests/ImageSharp.Tests/Drawing/LineTests.cs b/tests/ImageSharp.Tests/Drawing/LineTests.cs index 3396d89c5..e751557b6 100644 --- a/tests/ImageSharp.Tests/Drawing/LineTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineTests.cs @@ -38,7 +38,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); @@ -69,7 +69,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]); @@ -187,7 +187,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[9, 9]); + Assert.Equal(mergedColor, sourcePixels[11, 11]); Assert.Equal(mergedColor, sourcePixels[199, 149]); @@ -217,7 +217,7 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Rgba32.HotPink, sourcePixels[8, 8]); + Assert.Equal(Rgba32.HotPink, sourcePixels[11, 11]); Assert.Equal(Rgba32.HotPink, sourcePixels[198, 10]); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs deleted file mode 100644 index a9b2284e8..000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawBeziersTests.cs +++ /dev/null @@ -1,163 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - - using ImageSharp.Drawing.Brushes; - - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawBeziersTests : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Firebrick, 99.9f); - Vector2[] points = new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - }; - private ProcessorWatchingImage img; - - public DrawBeziersTests() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndPoints() - { - img.DrawBeziers(brush, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - Assert.NotNull(path.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessPointsAndOptions() - { - img.DrawBeziers(brush, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndPoints() - { - img.DrawBeziers(color, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessPointsAndOptions() - { - img.DrawBeziers(color, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndPoints() - { - img.DrawBeziers(pen, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenPointsAndOptions() - { - img.DrawBeziers(pen, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - BezierLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs deleted file mode 100644 index 3b7ba303d..000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawLinesTests.cs +++ /dev/null @@ -1,160 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - - using ImageSharp.Drawing.Brushes; - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawLinesTests : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Gray, 99.9f); - Vector2[] points = new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - }; - private ProcessorWatchingImage img; - - public DrawLinesTests() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndPoints() - { - img.DrawLines(brush, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessPointsAndOptions() - { - img.DrawLines(brush, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndPoints() - { - img.DrawLines(color, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessPointsAndOptions() - { - img.DrawLines(color, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndPoints() - { - img.DrawLines(pen, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenPointsAndOptions() - { - img.DrawLines(pen, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - SixLabors.Shapes.Path vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs deleted file mode 100644 index 0bdcbdc08..000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPath.cs +++ /dev/null @@ -1,149 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - - using ImageSharp.Drawing.Brushes; - - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawPath : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Gray, 99.9f); - IPath path = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - })); - private ProcessorWatchingImage img; - - public DrawPath() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndPath() - { - img.Draw(brush, thickness, path); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessPathAndOptions() - { - img.Draw(brush, thickness, path, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndPath() - { - img.Draw(color, thickness, path); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessPathAndOptions() - { - img.Draw(color, thickness, path, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndPath() - { - img.Draw(pen, path); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenPathAndOptions() - { - img.Draw(pen, path, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - Assert.Equal(path, shapepath.Path); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs deleted file mode 100644 index 3474e6f62..000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawPolygon.cs +++ /dev/null @@ -1,161 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - - using ImageSharp.Drawing.Brushes; - - using Xunit; - using ImageSharp.Drawing; - using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawPolygon : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Gray, 99.9f); - Vector2[] points = new Vector2[] { - new Vector2(10,10), - new Vector2(20,10), - new Vector2(20,10), - new Vector2(30,10), - }; - private ProcessorWatchingImage img; - - public DrawPolygon() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndPoints() - { - img.DrawPolygon(brush, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessPointsAndOptions() - { - img.DrawPolygon(brush, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndPoints() - { - img.DrawPolygon(color, thickness, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessPointsAndOptions() - { - img.DrawPolygon(color, thickness, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndPoints() - { - img.DrawPolygon(pen, points); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenPointsAndOptions() - { - img.DrawPolygon(pen, points, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath path = Assert.IsType(processor.Path); - - Polygon vector = Assert.IsType(path.Path); - LinearLineSegment segment = Assert.IsType(vector.LineSegments[0]); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs b/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs deleted file mode 100644 index e08e702c1..000000000 --- a/tests/ImageSharp.Tests/Drawing/Paths/DrawRectangle.cs +++ /dev/null @@ -1,177 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Paths -{ - using System; - using ImageSharp; - using ImageSharp.Drawing.Brushes; - using Xunit; - using ImageSharp.Drawing; - using ImageSharp.Drawing.Processors; - using ImageSharp.Drawing.Pens; - using ImageSharp.PixelFormats; - - public class DrawRectangle : IDisposable - { - float thickness = 7.2f; - GraphicsOptions noneDefault = new GraphicsOptions(); - Rgba32 color = Rgba32.HotPink; - SolidBrush brush = Brushes.Solid(Rgba32.HotPink); - Pen pen = new Pen(Rgba32.Gray, 99.9f); - ImageSharp.Rectangle rectangle = new ImageSharp.Rectangle(10, 10, 98, 324); - - private ProcessorWatchingImage img; - - public DrawRectangle() - { - this.img = new Paths.ProcessorWatchingImage(10, 10); - } - - public void Dispose() - { - img.Dispose(); - } - - [Fact] - public void CorrectlySetsBrushThicknessAndRectangle() - { - img.Draw(brush, thickness, rectangle); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsBrushThicknessRectangleAndOptions() - { - img.Draw(brush, thickness, rectangle, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(brush, pen.Brush); - Assert.Equal(thickness, pen.Width); - } - - [Fact] - public void CorrectlySetsColorThicknessAndRectangle() - { - img.Draw(color, thickness, rectangle); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsColorThicknessRectangleAndOptions() - { - img.Draw(color, thickness, rectangle, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Pen pen = Assert.IsType>(processor.Pen); - Assert.Equal(thickness, pen.Width); - - SolidBrush brush = Assert.IsType>(pen.Brush); - Assert.Equal(color, brush.Color); - } - - [Fact] - public void CorrectlySetsPenAndRectangle() - { - img.Draw(pen, rectangle); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(GraphicsOptions.Default, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Assert.Equal(pen, processor.Pen); - } - - [Fact] - public void CorrectlySetsPenRectangleAndOptions() - { - img.Draw(pen, rectangle, noneDefault); - - Assert.NotEmpty(img.ProcessorApplications); - DrawPathProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); - - Assert.Equal(noneDefault, processor.Options); - - ShapePath shapepath = Assert.IsType(processor.Path); - - SixLabors.Shapes.Rectangle rect = Assert.IsType(shapepath.Path); - - Assert.Equal(rect.Location.X, rectangle.X); - Assert.Equal(rect.Location.Y, rectangle.Y); - Assert.Equal(rect.Size.Width, rectangle.Width); - Assert.Equal(rect.Size.Height, rectangle.Height); - - Assert.Equal(pen, processor.Pen); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs index a18dd90bc..24f2a6bd8 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/Extensions.cs @@ -5,7 +5,7 @@ namespace ImageSharp.Tests.Drawing.Paths using System.IO; using ImageSharp; using ImageSharp.Drawing.Brushes; - using Processing; + using ImageSharp.Processing; using System.Collections.Generic; using Xunit; using ImageSharp.Drawing; diff --git a/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs new file mode 100644 index 000000000..e60ac0c13 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Paths/FillPathCollection.cs @@ -0,0 +1,131 @@ + +namespace ImageSharp.Tests.Drawing.Paths +{ + using System; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.PixelFormats; + + public class FillPathCollection : IDisposable + { + GraphicsOptions noneDefault = new GraphicsOptions(); + Rgba32 color = Rgba32.HotPink; + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + IPath path1 = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + IPath path2 = new SixLabors.Shapes.Path(new LinearLineSegment(new Vector2[] { + new Vector2(10,10), + new Vector2(20,10), + new Vector2(20,10), + new Vector2(30,10), + })); + + IPathCollection pathCollection; + + private ProcessorWatchingImage img; + + public FillPathCollection() + { + this.pathCollection = new PathCollection(path1, path2); + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + img.Dispose(); + } + + [Fact] + public void CorrectlySetsBrushAndPath() + { + img.Fill(brush, pathCollection); + + Assert.Equal(2, img.ProcessorApplications.Count); + for (var i = 0; i < 2; i++) + { + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + + // path is converted to a polygon before filling + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsBrushPathOptions() + { + img.Fill(brush, pathCollection, noneDefault); + + Assert.Equal(2, img.ProcessorApplications.Count); + for (var i = 0; i < 2; i++) + { + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + Assert.Equal(brush, processor.Brush); + } + } + + [Fact] + public void CorrectlySetsColorAndPath() + { + img.Fill(color, pathCollection); + + Assert.Equal(2, img.ProcessorApplications.Count); + for (var i = 0; i < 2; i++) + { + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); + + Assert.Equal(GraphicsOptions.Default, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } + + [Fact] + public void CorrectlySetsColorPathAndOptions() + { + img.Fill(color, pathCollection, noneDefault); + + Assert.Equal(2, img.ProcessorApplications.Count); + for (var i = 0; i < 2; i++) + { + FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[i].processor); + + Assert.Equal(noneDefault, processor.Options); + + ShapeRegion region = Assert.IsType(processor.Region); + Polygon polygon = Assert.IsType(region.Shape); + LinearLineSegment segments = Assert.IsType(polygon.LineSegments[0]); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(color, brush.Color); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index c1d34a112..039216078 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -4,7 +4,7 @@ namespace ImageSharp.Tests.Drawing.Paths using System; using System.IO; using ImageSharp; - using Processing; + using ImageSharp.Processing; using System.Collections.Generic; using ImageSharp.PixelFormats; diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs index 494e2a672..e791a2bfb 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapePathTests.cs @@ -5,7 +5,7 @@ namespace ImageSharp.Tests.Drawing.Paths using System.IO; using ImageSharp; using ImageSharp.Drawing.Brushes; - using Processing; + using ImageSharp.Processing; using System.Collections.Generic; using Xunit; using ImageSharp.Drawing; @@ -18,109 +18,6 @@ namespace ImageSharp.Tests.Drawing.Paths public class ShapePathTests { - private readonly Mock pathMock1; - private readonly Mock pathMock2; - private readonly SixLabors.Shapes.Rectangle bounds1; - - public ShapePathTests() - { - this.pathMock2 = new Mock(); - this.pathMock1 = new Mock(); - - this.bounds1 = new SixLabors.Shapes.Rectangle(10.5f, 10, 10, 10); - pathMock1.Setup(x => x.Bounds).Returns(this.bounds1); - pathMock2.Setup(x => x.Bounds).Returns(this.bounds1); - // wire up the 2 mocks to reference eachother - pathMock1.Setup(x => x.AsClosedPath()).Returns(() => pathMock2.Object); - } - - [Fact] - public void ShapePathFromPathConvertsBoundsDoesNotProxyToShape() - { - ShapePath region = new ShapePath(pathMock1.Object); - - Assert.Equal(Math.Floor(bounds1.Left), region.Bounds.Left); - Assert.Equal(Math.Ceiling(bounds1.Right), region.Bounds.Right); - - pathMock1.Verify(x => x.Bounds); - } - - [Fact] - public void ShapePathFromPathMaxIntersectionsProxyToShape() - { - ShapePath region = new ShapePath(pathMock1.Object); - - int i = region.MaxIntersections; - pathMock1.Verify(x => x.MaxIntersections); - } - - [Fact] - public void ShapePathFromPathScanXProxyToShape() - { - int xToScan = 10; - ShapePath region = new ShapePath(pathMock1.Object); - - pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(xToScan, s.X); - Assert.Equal(xToScan, e.X); - Assert.True(s.Y < bounds1.Top); - Assert.True(e.Y > bounds1.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); - - pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public void ShapePathFromPathScanYProxyToShape() - { - int yToScan = 10; - ShapePath region = new ShapePath(pathMock1.Object); - - pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(yToScan, s.Y); - Assert.Equal(yToScan, e.Y); - Assert.True(s.X < bounds1.Left); - Assert.True(e.X > bounds1.Right); - }).Returns(0); - - int i = region.ScanY(yToScan, new float[0], 0, 0); - - pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - - [Fact] - public void ShapePathFromShapeScanXProxyToShape() - { - int xToScan = 10; - ShapePath region = new ShapePath(pathMock1.Object); - - pathMock1.Setup(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Callback((s, e, b, c, o) => { - Assert.Equal(xToScan, s.X); - Assert.Equal(xToScan, e.X); - Assert.True(s.Y < bounds1.Top); - Assert.True(e.Y > bounds1.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); - - pathMock1.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - [Fact] - public void GetPointInfoCallSinglePathForPath() - { - ShapePath region = new ShapePath(pathMock1.Object); - - ImageSharp.Drawing.PointInfo info = region.GetPointInfo(10, 1); - - pathMock1.Verify(x => x.Distance(new Vector2(10, 1)), Times.Once); - pathMock2.Verify(x => x.Distance(new Vector2(10, 1)), Times.Never); - } + // TODO readd these back in } } diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index 78c349233..7765436f7 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -5,7 +5,7 @@ namespace ImageSharp.Tests.Drawing.Paths using System.IO; using ImageSharp; using ImageSharp.Drawing.Brushes; - using Processing; + using ImageSharp.Processing; using System.Collections.Generic; using Xunit; using ImageSharp.Drawing; @@ -81,7 +81,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.Scan(yToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0]); pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -100,7 +100,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.Scan(yToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0]); pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs new file mode 100644 index 000000000..60fe44acd --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.Path.cs @@ -0,0 +1,251 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Drawing.Text +{ + using System; + using System.Numerics; + + using ImageSharp.Drawing; + using ImageSharp.Drawing.Brushes; + using ImageSharp.Drawing.Pens; + using ImageSharp.Drawing.Processors; + using ImageSharp.PixelFormats; + using ImageSharp.Tests.Drawing.Paths; + + using SixLabors.Fonts; + using SixLabors.Shapes; + + using Xunit; + + public class DrawText_Path : IDisposable + { + Rgba32 color = Rgba32.HotPink; + + SolidBrush brush = Brushes.Solid(Rgba32.HotPink); + + IPath path = new SixLabors.Shapes.Path( + new LinearLineSegment( + new Vector2[] { new Vector2(10, 10), new Vector2(20, 10), new Vector2(20, 10), new Vector2(30, 10), })); + + private ProcessorWatchingImage img; + + private readonly FontCollection FontCollection; + + private readonly Font Font; + + public DrawText_Path() + { + this.FontCollection = new FontCollection(); + this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + this.img = new ProcessorWatchingImage(10, 10); + } + + public void Dispose() + { + this.img.Dispose(); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPen() + { + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + null, + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetAndNotPenDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), null, path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSet() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenBrushSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void FillsForEachACharachterWhenColorSet() + { + this.img.DrawText("123", this.Font, Rgba32.Red, path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); + FillRegionProcessor processor = + Assert.IsType>(this.img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void FillsForEachACharachterWhenColorSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Rgba32.Red, path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + FillRegionProcessor processor = + Assert.IsType>(this.img.ProcessorApplications[0].processor); + + SolidBrush brush = Assert.IsType>(processor.Brush); + Assert.Equal(Rgba32.Red, brush.Color); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrush() + { + this.img.DrawText( + "123", + this.Font, + null, + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndNotBrushDefaultOptions() + { + this.img.DrawText("123", this.Font, null, Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSet() + { + this.img.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), path, new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied + Assert.IsType>(this.img.ProcessorApplications[0].processor); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSet() + { + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Rgba32.Red), + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); + } + + [Fact] + public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() + { + this.img.DrawText("123", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); + } + + [Fact] + public void BrushAppliesBeforPen() + { + this.img.DrawText( + "1", + this.Font, + Brushes.Solid(Rgba32.Red), + Pens.Dash(Rgba32.Red, 1), + path, + new TextGraphicsOptions(true)); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); + } + + [Fact] + public void BrushAppliesBeforPenDefaultOptions() + { + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), Pens.Dash(Rgba32.Red, 1), path); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); + } + + [Fact] + public void GlyphHeightChangesBasedOnuseImageResolutionFlag() + { + this.img.MetaData.VerticalResolution = 1; + this.img.MetaData.HorizontalResolution = 1; + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true) { + UseImageResolution = false + }); + + this.img.DrawText("1", this.Font, Brushes.Solid(Rgba32.Red), path, new TextGraphicsOptions(true) + { + UseImageResolution = true + }); + + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(2, this.img.ProcessorApplications.Count); + FillRegionProcessor ownResolution = Assert.IsType>(this.img.ProcessorApplications[0].processor); + FillRegionProcessor imgResolution = Assert.IsType>(this.img.ProcessorApplications[1].processor); + + ShapeRegion ownRegion = Assert.IsType(ownResolution.Region); + ShapeRegion imgRegion = Assert.IsType(imgResolution.Region); + + // magic numbers based on the font used at well known resolutions + Assert.Equal(7.44, ownRegion.Shape.Bounds.Height, 2); + Assert.Equal(0.1, imgRegion.Shape.Bounds.Height, 2); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs index 1516b33d4..4718ed419 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -136,7 +136,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[0].processor); } [Fact] @@ -146,7 +146,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[0].processor); } [Fact] @@ -156,7 +156,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[0].processor); } [Fact] @@ -166,7 +166,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(3, this.img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(this.img.ProcessorApplications[0].processor); + Assert.IsType>(this.img.ProcessorApplications[0].processor); } [Fact] @@ -207,7 +207,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(2, this.img.ProcessorApplications.Count); Assert.IsType>(this.img.ProcessorApplications[0].processor); - Assert.IsType>(this.img.ProcessorApplications[1].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); } [Fact] @@ -218,7 +218,7 @@ namespace ImageSharp.Tests.Drawing.Text Assert.NotEmpty(this.img.ProcessorApplications); Assert.Equal(2, this.img.ProcessorApplications.Count); Assert.IsType>(this.img.ProcessorApplications[0].processor); - Assert.IsType>(this.img.ProcessorApplications[1].processor); + Assert.IsType>(this.img.ProcessorApplications[1].processor); } [Fact] diff --git a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs deleted file mode 100644 index 7f16f30bb..000000000 --- a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs +++ /dev/null @@ -1,68 +0,0 @@ - -namespace ImageSharp.Tests.Drawing.Text -{ - using ImageSharp.Drawing; - using SixLabors.Fonts; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Numerics; - using System.Threading.Tasks; - using Xunit; - - public class GlyphBuilderTests - { - [Fact] - public void OriginUsed() - { - // Y axis is inverted as it expects to be drawing for bottom left - GlyphBuilder fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); - IGlyphRenderer builder = fullBuilder; - - builder.BeginGlyph(Vector2.Zero); - builder.BeginFigure(); - builder.MoveTo(new Vector2(0, 0)); - builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 - - builder.CubicBezierTo( - new Vector2(15, 15), // control point - will not be in the final point collection - new Vector2(15, 10), // control point - will not be in the final point collection - new Vector2(10, 10));// becomes 10, -10 - - builder.QuadraticBezierTo( - new Vector2(10, 5), // control point - will not be in the final point collection - new Vector2(10, 0)); - - builder.EndFigure(); - builder.EndGlyph(); - - System.Collections.Immutable.ImmutableArray points = fullBuilder.Paths.Single().Flatten().Single().Points; - - Assert.Contains(new Vector2(10, 99), points); - Assert.Contains(new Vector2(10, 109), points); - Assert.Contains(new Vector2(20, 99), points); - Assert.Contains(new Vector2(20, 109), points); - } - - [Fact] - public void EachGlypeCausesNewPath() - { - // Y axis is inverted as it expects to be drawing for bottom left - GlyphBuilder fullBuilder = new GlyphBuilder(); - IGlyphRenderer builder = fullBuilder; - for (int i = 0; i < 10; i++) - { - builder.BeginGlyph(Vector2.Zero); - builder.BeginFigure(); - builder.MoveTo(new Vector2(0, 0)); - builder.LineTo(new Vector2(0, 10)); // becomes 0, -10 - builder.LineTo(new Vector2(10, 10));// becomes 10, -10 - builder.LineTo(new Vector2(10, 0)); - builder.EndFigure(); - builder.EndGlyph(); - } - - Assert.Equal(10, fullBuilder.Paths.Count()); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs index bb9cd264e..21646b03f 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs @@ -5,7 +5,7 @@ namespace ImageSharp.Tests.Drawing.Text using System.IO; using ImageSharp; using ImageSharp.Drawing.Brushes; - using Processing; + using ImageSharp.Processing; using System.Collections.Generic; using Xunit; using ImageSharp.Drawing; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 12c7d5154..51a1562f5 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -12,6 +12,54 @@ namespace ImageSharp.Tests /// public abstract class FileTestBase : TestBase { + /// + /// A collection made up of one file for each image format + /// + public static IEnumerable DefaultFiles = + new[] + { + TestImages.Bmp.Car, + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Png.Splash, + TestImages.Gif.Trans + }; + + /// + /// A collection of all the bmp test images + /// + public static IEnumerable AllBmpFiles = TestImages.Bmp.All; + + /// + /// A collection of all the jpeg test images + /// + public static IEnumerable AllJpegFiles = TestImages.Jpeg.All; + + /// + /// A collection of all the png test images + /// + public static IEnumerable AllPngFiles = TestImages.Png.All; + + /// + /// A collection of all the gif test images + /// + public static IEnumerable AllGifFiles = TestImages.Gif.All; + + /// + /// The standard pixel format enumeration + /// + public const PixelTypes DefaultPixelType = PixelTypes.Rgba32; + + public static class Extensions + { + public const string Bmp = "bmp"; + + public const string Jpeg = "jpg"; + + public const string Png = "png"; + + public const string Gif = "gif"; + } + /// /// The collection of image files to test against. /// diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index b47df8395..ec1a8c465 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -13,22 +13,16 @@ namespace ImageSharp.Tests public class GeneralFormatTests : FileTestBase { - [Fact] - public void ResolutionShouldChange() + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ResolutionShouldChange(TestImageProvider provider) + where TPixel : struct, IPixel { - string path = this.CreateOutputDirectory("Resolution"); - - foreach (TestFile file in Files) + using (Image image = provider.GetImage()) { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.MetaData.VerticalResolution = 150; - image.MetaData.HorizontalResolution = 150; - image.Save(output); - } - } + image.MetaData.VerticalResolution = 150; + image.MetaData.HorizontalResolution = 150; + image.DebugSave(provider, null, Extensions.Bmp); } } diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index a5fc92901..89df2d086 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Tests public class GifDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; public static readonly string[] TestFiles = { TestImages.Gif.Giphy, TestImages.Gif.Rings, TestImages.Gif.Trans }; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index b0ffaaf85..70cc8e2ba 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Tests public class GifEncoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; [Theory] [WithTestPatternImages(100, 100, PixelTypes)] diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index a6d6d31f3..446c5e96a 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -26,7 +26,7 @@ namespace ImageSharp.Tests public static string[] ProgressiveTestJpegs = TestImages.Jpeg.Progressive.All; [Theory] - [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32 | PixelTypes.StandardImageClass | PixelTypes.Argb32)] + [WithFileCollection(nameof(BaselineTestJpegs), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32)] public void OpenBaselineJpeg_SaveBmp(TestImageProvider provider) where TPixel : struct, IPixel { @@ -37,7 +37,7 @@ namespace ImageSharp.Tests } [Theory] - [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32 | PixelTypes.StandardImageClass | PixelTypes.Argb32)] + [WithFileCollection(nameof(ProgressiveTestJpegs), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32)] public void OpenProgressiveJpeg_SaveBmp(TestImageProvider provider) where TPixel : struct, IPixel { @@ -48,11 +48,11 @@ namespace ImageSharp.Tests } [Theory] - [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 75)] - [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio420, 100)] - [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 75)] - [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] - [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.StandardImageClass, JpegSubsample.Ratio444, 100)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio420, 100)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 75)] + [WithSolidFilledImages(16, 16, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)] + [WithSolidFilledImages(8, 8, 255, 0, 0, PixelTypes.Rgba32, JpegSubsample.Ratio444, 100)] public void DecodeGenerated_SaveBmp( TestImageProvider provider, JpegSubsample subsample, @@ -79,7 +79,7 @@ namespace ImageSharp.Tests } [Theory] - [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.StandardImageClass)] + [WithSolidFilledImages(42, 88, 255, 0, 0, PixelTypes.Rgba32)] public void DecodeGenerated_MetadataOnly( TestImageProvider provider) where TPixel : struct, IPixel diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 1b4f3ea78..ab81c69b4 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -28,10 +28,10 @@ namespace ImageSharp.Tests } [Theory] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio420)] - [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] - [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.StandardImageClass, 75, JpegSubsample.Ratio444)] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio420)] + [WithFile(TestImages.Jpeg.Baseline.Snake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] + [WithFile(TestImages.Jpeg.Baseline.Lake, PixelTypes.Rgba32, 75, JpegSubsample.Ratio444)] public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) where TPixel : struct, IPixel { @@ -49,8 +49,8 @@ namespace ImageSharp.Tests } [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.StandardImageClass | PixelTypes.Argb32, JpegSubsample.Ratio420, 75)] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.StandardImageClass | PixelTypes.Argb32, JpegSubsample.Ratio444, 75)] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio420, 75)] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32 | PixelTypes.Rgba32 | PixelTypes.Argb32, JpegSubsample.Ratio444, 75)] public void OpenBmp_SaveJpeg(TestImageProvider provider, JpegSubsample subSample, int quality) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index b41826e2f..aac3f4611 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -72,7 +72,7 @@ namespace ImageSharp.Tests Image[] testImages = testFiles.Select( - tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.StandardImageClass).GetImage()) + tf => TestImageProvider.File(tf, pixelTypeOverride: PixelTypes.Rgba32).GetImage()) .ToArray(); using (MemoryStream ms = new MemoryStream()) diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs index f242faf12..f681e1d8f 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegUtilsTests.cs @@ -40,7 +40,7 @@ namespace ImageSharp.Tests } [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.StandardImageClass | PixelTypes.Argb32)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] public void CopyStretchedRGBTo_FromOrigo(TestImageProvider provider) where TPixel : struct, IPixel { @@ -62,7 +62,7 @@ namespace ImageSharp.Tests } [Theory] - [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.StandardImageClass | PixelTypes.Argb32)] + [WithMemberFactory(nameof(CreateTestImage), PixelTypes.Rgba32| PixelTypes.Rgba32 | PixelTypes.Argb32)] public void CopyStretchedRGBTo_WithOffset(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index cf5c1cfa9..195a8dcb3 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -13,12 +13,12 @@ namespace ImageSharp.Tests public class PngDecoderTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; public static readonly string[] TestFiles = { TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar, - TestImages.Png.ChunkLength1, TestImages.Png.ChunkLength2 + TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2 }; [Theory] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 195eaba10..02edf7688 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Tests public class PngEncoderTests : FileTestBase { - private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; + private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; [Theory] [WithTestPatternImages(100, 100, PixelTypes, PngColorType.RgbWithAlpha)] diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index 22bb0b244..90f994f36 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -20,7 +20,7 @@ namespace ImageSharp.Tests.Formats.Png public class PngSmokeTests { [Theory] - [WithTestPatternImages(300, 300, PixelTypes.StandardImageClass)] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void GeneralTest(TestImageProvider provider) where TPixel : struct, IPixel { @@ -41,7 +41,7 @@ namespace ImageSharp.Tests.Formats.Png } [Theory] - [WithTestPatternImages(100, 100, PixelTypes.StandardImageClass)] + [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] public void CanSaveIndexedPng(TestImageProvider provider) where TPixel : struct, IPixel { @@ -105,7 +105,7 @@ namespace ImageSharp.Tests.Formats.Png //} [Theory] - [WithTestPatternImages(300, 300, PixelTypes.StandardImageClass)] + [WithTestPatternImages(300, 300, PixelTypes.Rgba32)] public void Resize(TestImageProvider provider) where TPixel : struct, IPixel { diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index 4cdf529e6..7a5b8ffc5 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -526,6 +526,48 @@ namespace ImageSharp.Tests this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); } + [Fact] + public void LoadFromPixelData_Pixels() + { + var img = Image.LoadPixelData(new Rgba32[] { + Rgba32.Black, Rgba32.White, + Rgba32.White, Rgba32.Black, + }, 2, 2); + + Assert.NotNull(img); + using (var px = img.Lock()) + { + Assert.Equal(Rgba32.Black, px[0, 0]); + Assert.Equal(Rgba32.White, px[0, 1]); + + Assert.Equal(Rgba32.White, px[1, 0]); + Assert.Equal(Rgba32.Black, px[1, 1]); + + } + } + + [Fact] + public void LoadFromPixelData_Bytes() + { + var img = Image.LoadPixelData(new byte[] { + 0,0,0,255, // 0,0 + 255,255,255,255, // 0,1 + 255,255,255,255, // 1,0 + 0,0,0,255, // 1,1 + }, 2, 2); + + Assert.NotNull(img); + using (var px = img.Lock()) + { + Assert.Equal(Rgba32.Black, px[0, 0]); + Assert.Equal(Rgba32.White, px[0, 1]); + + Assert.Equal(Rgba32.White, px[1, 0]); + Assert.Equal(Rgba32.Black, px[1, 1]); + + } + } + public void Dispose() { // clean up the global object; diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs index d339dc83d..9a30cc363 100644 --- a/tests/ImageSharp.Tests/ImageComparer.cs +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -1,4 +1,9 @@ -namespace ImageSharp.Tests +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests { using System; using ImageSharp; @@ -12,9 +17,41 @@ /// public static class ImageComparer { - const int DefaultScalingFactor = 32; // this is means the images get scaled into a 32x32 image to sample pixels - const int DefaultSegmentThreshold = 3; // the greyscale difference between 2 segements my be > 3 before it influances the overall difference - const float DefaultImageThreshold = 0.000f; // after segment threasholds the images must have no differences + const int DefaultScalingFactor = 32; // This is means the images get scaled into a 32x32 image to sample pixels + const int DefaultSegmentThreshold = 3; // The greyscale difference between 2 segements my be > 3 before it influences the overall difference + const float DefaultImageThreshold = 0.000F; // After segment thresholds the images must have no differences + + /// + /// Fills the bounded area with a solid color and does a visual comparison between 2 images asserting the difference outwith + /// that area is less then a configurable threshold. + /// + /// The color of the expected image + /// The color type fo the the actual image + /// The expected image + /// The actual image + /// The bounds within the image has been altered + /// + /// The threshold for the percentage difference where the images are asumed to be the same. + /// The default/undefined value is + /// + /// + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is + /// + /// + /// This is a sampling factor we sample a grid of average pixels width by high + /// The default undefined value is + /// + public static void EnsureProcessorChangesAreConstrained(Image expected, Image actual, Rectangle bounds, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) + where TPixelA : struct, IPixel + where TPixelB : struct, IPixel + { + // Draw identical shapes over the bounded and compare to ensure changes are constrained. + expected.Fill(NamedColors.HotPink, bounds); + actual.Fill(NamedColors.HotPink, bounds); + + CheckSimilarity(expected, actual, imageTheshold, segmentThreshold, scalingFactor); + } /// /// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold @@ -25,15 +62,15 @@ /// The actual image /// /// The threshold for the percentage difference where the images are asumed to be the same. - /// The default/undefined value is + /// The default/undefined value is /// /// - /// The threashold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is /// /// /// This is a sampling factor we sample a grid of average pixels width by high - /// The default undefined value is + /// The default undefined value is /// public static void CheckSimilarity(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) where TPixelA : struct, IPixel @@ -52,14 +89,14 @@ /// The source image /// The target image /// - /// The threashold of the individual segments before it acumulates towards the overall difference. - /// The default undefined value is + /// The threshold of the individual segments before it acumulates towards the overall difference. + /// The default undefined value is /// /// /// This is a sampling factor we sample a grid of average pixels width by high /// The default undefined value is /// - /// Returns a number from 0 - 1 which represents the diference focter between the images. + /// Returns a number from 0 - 1 which represents the difference focter between the images. public static float PercentageDifference(this Image source, Image target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) where TPixelA : struct, IPixel where TPixelB : struct, IPixel @@ -74,14 +111,14 @@ if (b > segmentThreshold) { diffPixels++; } } - return (float)diffPixels / (float)(scalingFactor * scalingFactor); + return diffPixels / (float)(scalingFactor * scalingFactor); } private static Fast2DArray GetDifferences(Image source, Image target, int scalingFactor) where TPixelA : struct, IPixel where TPixelB : struct, IPixel { - Fast2DArray differences = new Fast2DArray(scalingFactor, scalingFactor); + var differences = new Fast2DArray(scalingFactor, scalingFactor); Fast2DArray firstGray = source.GetGrayScaleValues(scalingFactor); Fast2DArray secondGray = target.GetGrayScaleValues(scalingFactor); @@ -89,7 +126,7 @@ { for (int x = 0; x < scalingFactor; x++) { - var diff = firstGray[x, y] - secondGray[x, y]; + int diff = firstGray[x, y] - secondGray[x, y]; differences[x, y] = (byte)Math.Abs(diff); } } @@ -100,18 +137,18 @@ private static Fast2DArray GetGrayScaleValues(this Image source, int scalingFactor) where TPixelA : struct, IPixel { - byte[] buffer = new byte[4]; + byte[] buffer = new byte[3]; using (Image img = new Image(source).Resize(scalingFactor, scalingFactor).Grayscale()) { using (PixelAccessor pixels = img.Lock()) { - Fast2DArray grayScale = new Fast2DArray(scalingFactor, scalingFactor); + var grayScale = new Fast2DArray(scalingFactor, scalingFactor); for (int y = 0; y < scalingFactor; y++) { for (int x = 0; x < scalingFactor; x++) { pixels[x, y].ToXyzBytes(buffer, 0); - grayScale[x, y] = buffer[1]; + grayScale[x, y] = buffer[0]; } } diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 781e73f9e..afde531b4 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -13,7 +13,7 @@ - + diff --git a/tests/ImageSharp.Tests/Numerics/PointFTests.cs b/tests/ImageSharp.Tests/Numerics/PointFTests.cs new file mode 100644 index 000000000..3b5a72a14 --- /dev/null +++ b/tests/ImageSharp.Tests/Numerics/PointFTests.cs @@ -0,0 +1,192 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Numerics +{ + using System; + using System.Globalization; + using System.Numerics; + using System.Reflection; + using Xunit; + + public class PointFTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(PointF.Empty, new PointF()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0.0, 0.0)] + public void NonDefaultConstructorTest(float x, float y) + { + var p1 = new PointF(x, y); + + Assert.Equal(x, p1.X); + Assert.Equal(y, p1.Y); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(PointF.Empty.IsEmpty); + Assert.True(new PointF().IsEmpty); + Assert.True(new PointF(0, 0).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float x, float y) + { + Assert.False(new PointF(x, y).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(float x, float y) + { + var p = new PointF(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + + p.X = 10; + Assert.Equal(10, p.X); + + p.Y = -10.123f; + Assert.Equal(-10.123, p.Y, 3); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, int.MaxValue, int.MinValue)] + [InlineData(float.MinValue, float.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void ArithmeticTestWithSize(float x, float y, int x1, int y1) + { + var p = new PointF(x, y); + var s = new Size(x1, y1); + + var addExpected = new PointF(x + x1, y + y1); + var subExpected = new PointF(x - x1, y - y1); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTestWithSizeF(float x, float y) + { + var p = new PointF(x, y); + var s = new SizeF(y, x); + + var addExpected = new PointF(x + y, y + x); + var subExpected = new PointF(x - y, y - x); + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, PointF.Add(p, s)); + Assert.Equal(subExpected, PointF.Subtract(p, s)); + } + + [Fact] + public void RotateTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotation(45, PointF.Empty); + + var pout = PointF.Rotate(p, matrix); + + Assert.Equal(new PointF(-2.82842732F, 21.2132034F), pout); + } + + [Fact] + public void SkewTest() + { + var p = new PointF(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkew(45, 45, PointF.Empty); + + var pout = PointF.Skew(p, matrix); + Assert.Equal(new PointF(30, 30), pout); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float x, float y) + { + var pLeft = new PointF(x, y); + var pRight = new PointF(y, x); + + if (x == y) + { + Assert.True(pLeft == pRight); + Assert.False(pLeft != pRight); + Assert.True(pLeft.Equals(pRight)); + Assert.True(pLeft.Equals((object)pRight)); + Assert.Equal(pLeft.GetHashCode(), pRight.GetHashCode()); + return; + } + + Assert.True(pLeft != pRight); + Assert.False(pLeft == pRight); + Assert.False(pLeft.Equals(pRight)); + Assert.False(pLeft.Equals((object)pRight)); + } + + [Fact] + public static void EqualityTest_NotPointF() + { + var point = new PointF(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + + // If PointF implements IEquatable (e.g. in .NET Core), then structs that are implicitly + // convertible to var can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToPointF = typeof(IEquatable).IsAssignableFrom(point.GetType()); + Assert.Equal(expectsImplicitCastToPointF, point.Equals(new Point(0, 0))); + + Assert.False(point.Equals((object)new Point(0, 0))); // No implicit cast + } + + [Fact] + public static void GetHashCodeTest() + { + var point = new PointF(10, 10); + Assert.Equal(point.GetHashCode(), new PointF(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new PointF(10, 20).GetHashCode()); + } + + [Fact] + public void ToStringTest() + { + var p = new PointF(5.1F, -5.123F); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "PointF [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); + } + + [Fact] + public void ToStringEmptyTest() + { + var p = new PointF(0, 0); + Assert.Equal("PointF [ Empty ]", p.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/PointTests.cs b/tests/ImageSharp.Tests/Numerics/PointTests.cs index 82b26b946..9f7abe067 100644 --- a/tests/ImageSharp.Tests/Numerics/PointTests.cs +++ b/tests/ImageSharp.Tests/Numerics/PointTests.cs @@ -5,46 +5,248 @@ namespace ImageSharp.Tests { + using System.Globalization; + using System.Numerics; + using Xunit; - /// - /// Tests the struct. - /// public class PointTests { - /// - /// Tests the equality operators for equality. - /// [Fact] - public void AreEqual() + public void DefaultConstructorTest() + { + Assert.Equal(Point.Empty, new Point()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(new Size(x, y)); + + Assert.Equal(p1, p2); + } + + [Theory] + [InlineData(int.MaxValue)] + [InlineData(int.MinValue)] + [InlineData(0)] + public void SingleIntConstructorTest(int x) + { + var p1 = new Point(x); + var p2 = new Point(unchecked((short)(x & 0xFFFF)), unchecked((short)((x >> 16) & 0xFFFF))); + + Assert.Equal(p1, p2); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(Point.Empty.IsEmpty); + Assert.True(new Point().IsEmpty); + Assert.True(new Point(0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int x, int y) + { + Assert.False(new Point(x, y).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void CoordinatesTest(int x, int y) + { + var p = new Point(x, y); + Assert.Equal(x, p.X); + Assert.Equal(y, p.Y); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int x, int y) + { + PointF p = new Point(x, y); + Assert.Equal(new PointF(x, y), p); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int x, int y) + { + var sz = (Size)new Point(x, y); + Assert.Equal(new Size(x, y), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int x, int y) + { + Point addExpected, subExpected, p = new Point(x, y); + var s = new Size(y, x); + + unchecked + { + addExpected = new Point(x + y, y + x); + subExpected = new Point(x - y, y - x); + } + + Assert.Equal(addExpected, p + s); + Assert.Equal(subExpected, p - s); + Assert.Equal(addExpected, Point.Add(p, s)); + Assert.Equal(subExpected, Point.Subtract(p, s)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float x, float y) + { + var pf = new PointF(x, y); + Point pCeiling, pTruncate, pRound; + + unchecked + { + pCeiling = new Point((int)MathF.Ceiling(x), (int)MathF.Ceiling(y)); + pTruncate = new Point((int)x, (int)y); + pRound = new Point((int)MathF.Round(x), (int)MathF.Round(y)); + } + + Assert.Equal(pCeiling, Point.Ceiling(pf)); + Assert.Equal(pRound, Point.Round(pf)); + Assert.Equal(pTruncate, (Point)pf); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void OffsetTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(y, x); + + p1.Offset(p2); + + Assert.Equal(unchecked(p2.X + p2.Y), p1.X); + Assert.Equal(p1.X, p1.Y); + + p2.Offset(x, y); + Assert.Equal(p1, p2); + } + + [Fact] + public void RotateTest() + { + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateRotation(45, Point.Empty); + + var pout = Point.Rotate(p, matrix); + + Assert.Equal(new Point(-3, 21), pout); + } + + [Fact] + public void SkewTest() { - Point first = new Point(100, 100); - Point second = new Point(100, 100); + var p = new Point(13, 17); + Matrix3x2 matrix = Matrix3x2Extensions.CreateSkew(45, 45, Point.Empty); - Assert.Equal(first, second); + var pout = Point.Skew(p, matrix); + Assert.Equal(new Point(30, 30), pout); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int x, int y) + { + var p1 = new Point(x, y); + var p2 = new Point(x / 2 - 1, y / 2 - 1); + var p3 = new Point(x, y); + + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); + + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); + + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); + + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); } - /// - /// Tests the equality operators for inequality. - /// [Fact] - public void AreNotEqual() + public static void EqualityTest_NotPoint() { - Point first = new Point(0, 100); - Point second = new Point(100, 100); + var point = new Point(0, 0); + Assert.False(point.Equals(null)); + Assert.False(point.Equals(0)); + Assert.False(point.Equals(new PointF(0, 0))); + } - Assert.NotEqual(first, second); + [Fact] + public static void GetHashCodeTest() + { + var point = new Point(10, 10); + Assert.Equal(point.GetHashCode(), new Point(10, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(20, 10).GetHashCode()); + Assert.NotEqual(point.GetHashCode(), new Point(10, 20).GetHashCode()); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(1, -2, 3, -4)] + public void ConversionTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + RectangleF rectF = rect; + Assert.Equal(x, rectF.X); + Assert.Equal(y, rectF.Y); + Assert.Equal(width, rectF.Width); + Assert.Equal(height, rectF.Height); + } + + [Fact] + public void ToStringTest() + { + var p = new Point(5, -5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Point [ X={0}, Y={1} ]", p.X, p.Y), p.ToString()); } - /// - /// Tests whether the point constructor correctly assign properties. - /// [Fact] - public void ConstructorAssignsProperties() + public void ToStringEmptyTest() { - Point first = new Point(4, 5); - Assert.Equal(4, first.X); - Assert.Equal(5, first.Y); + var p = new Point(0, 0); + Assert.Equal("Point [ Empty ]", p.ToString()); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/RectangleFTests.cs b/tests/ImageSharp.Tests/Numerics/RectangleFTests.cs new file mode 100644 index 000000000..b20b660dd --- /dev/null +++ b/tests/ImageSharp.Tests/Numerics/RectangleFTests.cs @@ -0,0 +1,267 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Globalization; + using System.Reflection; + + using Xunit; + + /// + /// Tests the struct. + /// + public class RectangleFTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(RectangleF.Empty, new RectangleF()); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void NonDefaultConstructorTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + var rect2 = new RectangleF(p, s); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void FromLTRBTest(float left, float top, float right, float bottom) + { + var expected = new RectangleF(left, top, right - left, bottom - top); + var actual = RectangleF.FromLTRB(left, top, right, bottom); + + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void DimensionsTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + var p = new PointF(x, y); + var s = new SizeF(width, height); + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(x + width, rect.Right); + Assert.Equal(y + height, rect.Bottom); + } + + [Fact] + public void IsEmptyTest() + { + Assert.True(RectangleF.Empty.IsEmpty); + Assert.True(new RectangleF().IsEmpty); + Assert.True(new RectangleF(1, -2, -10, 10).IsEmpty); + Assert.True(new RectangleF(1, -2, 10, -10).IsEmpty); + Assert.True(new RectangleF(1, -2, 0, 0).IsEmpty); + + Assert.False(new RectangleF(0, 0, 10, 10).IsEmpty); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public static void LocationSetTest(float x, float y) + { + var point = new PointF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(float.MaxValue, float.MinValue)] + public static void SizeSetTest(float x, float y) + { + var size = new SizeF(x, y); + var rect = new RectangleF(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void EqualityTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(width, height, x, y); + + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); + } + + [Fact] + public static void EqualityTestNotRectangleF() + { + var rectangle = new RectangleF(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + + // If RectangleF implements IEquatable (e.g. in .NET Core), then classes that are implicitly + // convertible to RectangleF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToRectangleF = typeof(IEquatable).IsAssignableFrom(rectangle.GetType()); + Assert.Equal(expectsImplicitCastToRectangleF, rectangle.Equals(new Rectangle(0, 0, 0, 0))); + + Assert.False(rectangle.Equals((object)new Rectangle(0, 0, 0, 0))); // No implicit cast + } + + [Fact] + public static void GetHashCodeTest() + { + var rect1 = new RectangleF(10, 10, 10, 10); + var rect2 = new RectangleF(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new RectangleF(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void ContainsTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + float X = (x + width) / 2; + float Y = (y + height) / 2; + var p = new PointF(X, Y); + var r = new RectangleF(X, Y, width / 2, height / 2); + + Assert.False(rect.Contains(X, Y)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue / 2, float.MinValue / 2, float.MinValue / 2, float.MaxValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void InflateTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + var inflatedRect = new RectangleF(x - width, y - height, width + 2 * width, height + 2 * height); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new SizeF(x, y); + inflatedRect = RectangleF.Inflate(rect, x, y); + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue / 2, float.MinValue / 2)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void IntersectTest(float x, float y, float width, float height) + { + var rect1 = new RectangleF(x, y, width, height); + var rect2 = new RectangleF(y, x, width, height); + var expectedRect = RectangleF.Intersect(rect1, rect2); + rect1.Intersect(rect2); + Assert.Equal(expectedRect, rect1); + Assert.False(rect1.IntersectsWith(expectedRect)); + } + + [Fact] + public static void IntersectIntersectingRectsTest() + { + var rect1 = new RectangleF(0, 0, 5, 5); + var rect2 = new RectangleF(1, 1, 3, 3); + var expected = new RectangleF(1, 1, 3, 3); + + Assert.Equal(expected, RectangleF.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void UnionTest(float x, float y, float width, float height) + { + var a = new RectangleF(x, y, width, height); + var b = new RectangleF(width, height, x, y); + + float x1 = Math.Min(a.X, b.X); + float x2 = Math.Max(a.X + a.Width, b.X + b.Width); + float y1 = Math.Min(a.Y, b.Y); + float y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + var expectedRectangle = new RectangleF(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedRectangle, RectangleF.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(float.MaxValue, float.MinValue, float.MinValue, float.MaxValue)] + [InlineData(float.MaxValue, 0, 0, float.MaxValue)] + [InlineData(0, float.MinValue, float.MaxValue, 0)] + public void OffsetTest(float x, float y, float width, float height) + { + var r1 = new RectangleF(x, y, width, height); + var expectedRect = new RectangleF(x + width, y + height, width, height); + var p = new PointF(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new RectangleF(5, 5.1F, 1.3F, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "RectangleF [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + + [InlineData(0, 0, 0, 0)] + [InlineData(5, -5, 0.2, -1.3)] + public void ToStringTestEmpty(float x, float y, float width, float height) + { + var r = new RectangleF(x, y, width, height); + Assert.Equal("RectangleF [ Empty ]", r.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/RectangleTests.cs b/tests/ImageSharp.Tests/Numerics/RectangleTests.cs index 2f9ad3d37..1ef03f0c6 100644 --- a/tests/ImageSharp.Tests/Numerics/RectangleTests.cs +++ b/tests/ImageSharp.Tests/Numerics/RectangleTests.cs @@ -5,6 +5,9 @@ namespace ImageSharp.Tests { + using System; + using System.Globalization; + using Xunit; /// @@ -12,55 +15,294 @@ namespace ImageSharp.Tests /// public class RectangleTests { - /// - /// Tests the equality operators for equality. - /// [Fact] - public void AreEqual() + public void DefaultConstructorTest() + { + Assert.Equal(Rectangle.Empty, new Rectangle()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonDefaultConstructorTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(new Point(x, y), new Size(width, height)); + + Assert.Equal(rect1, rect2); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void FromLTRBTest(int left, int top, int right, int bottom) { - Rectangle first = new Rectangle(1, 1, 100, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); + var rect1 = new Rectangle(left, top, unchecked(right - left), unchecked(bottom - top)); + var rect2 = Rectangle.FromLTRB(left, top, right, bottom); - Assert.Equal(first, second); + Assert.Equal(rect1, rect2); } - /// - /// Tests the equality operators for inequality. - /// [Fact] - public void AreNotEqual() + public void EmptyTest() + { + Assert.True(Rectangle.Empty.IsEmpty); + Assert.True(new Rectangle(0, 0, 0, 0).IsEmpty); + Assert.True(new Rectangle().IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + public void NonEmptyTest(int x, int y, int width, int height) + { + Assert.False(new Rectangle(x, y, width, height).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, 0, 0, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void DimensionsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + Assert.Equal(new Point(x, y), rect.Location); + Assert.Equal(new Size(width, height), rect.Size); + + Assert.Equal(x, rect.X); + Assert.Equal(y, rect.Y); + Assert.Equal(width, rect.Width); + Assert.Equal(height, rect.Height); + Assert.Equal(x, rect.Left); + Assert.Equal(y, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + + var p = new Point(width, height); + var s = new Size(x, y); + rect.Location = p; + rect.Size = s; + + Assert.Equal(p, rect.Location); + Assert.Equal(s, rect.Size); + + Assert.Equal(width, rect.X); + Assert.Equal(height, rect.Y); + Assert.Equal(x, rect.Width); + Assert.Equal(y, rect.Height); + Assert.Equal(width, rect.Left); + Assert.Equal(height, rect.Top); + Assert.Equal(unchecked(x + width), rect.Right); + Assert.Equal(unchecked(y + height), rect.Bottom); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public static void LocationSetTest(int x, int y) + { + var point = new Point(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Location = point }; + Assert.Equal(point, rect.Location); + Assert.Equal(point.X, rect.X); + Assert.Equal(point.Y, rect.Y); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(int.MaxValue, int.MinValue)] + public static void SizeSetTest(int x, int y) { - Rectangle first = new Rectangle(1, 1, 0, 100); - Rectangle second = new Rectangle(1, 1, 100, 100); + var size = new Size(x, y); + var rect = new Rectangle(10, 10, 10, 10) { Size = size }; + Assert.Equal(size, rect.Size); + Assert.Equal(size.Width, rect.Width); + Assert.Equal(size.Height, rect.Height); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MaxValue, int.MinValue)] + [InlineData(int.MaxValue, 0, int.MinValue, 0)] + [InlineData(0, int.MinValue, 0, int.MaxValue)] + [InlineData(int.MinValue, int.MaxValue, int.MinValue, int.MaxValue)] + public void EqualityTest(int x, int y, int width, int height) + { + var rect1 = new Rectangle(x, y, width, height); + var rect2 = new Rectangle(width / 2, height / 2, x, y); - Assert.NotEqual(first, second); + Assert.True(rect1 != rect2); + Assert.False(rect1 == rect2); + Assert.False(rect1.Equals(rect2)); + Assert.False(rect1.Equals((object)rect2)); } - /// - /// Tests whether the rectangle constructors correctly assign properties. - /// [Fact] - public void ConstructorAssignsProperties() - { - Rectangle first = new Rectangle(1, 1, 50, 100); - Assert.Equal(1, first.X); - Assert.Equal(1, first.Y); - Assert.Equal(50, first.Width); - Assert.Equal(100, first.Height); - Assert.Equal(1, first.Top); - Assert.Equal(51, first.Right); - Assert.Equal(101, first.Bottom); - Assert.Equal(1, first.Left); - - Rectangle second = new Rectangle(new Point(1, 1), new Size(50, 100)); - Assert.Equal(1, second.X); - Assert.Equal(1, second.Y); - Assert.Equal(50, second.Width); - Assert.Equal(100, second.Height); - Assert.Equal(1, second.Top); - Assert.Equal(51, second.Right); - Assert.Equal(101, second.Bottom); - Assert.Equal(1, second.Left); + public static void EqualityTestNotRectangle() + { + var rectangle = new Rectangle(0, 0, 0, 0); + Assert.False(rectangle.Equals(null)); + Assert.False(rectangle.Equals(0)); + Assert.False(rectangle.Equals(new RectangleF(0, 0, 0, 0))); + } + + [Fact] + public static void GetHashCodeTest() + { + var rect1 = new Rectangle(10, 10, 10, 10); + var rect2 = new Rectangle(10, 10, 10, 10); + Assert.Equal(rect1.GetHashCode(), rect2.GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(20, 10, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 20, 10, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 20, 10).GetHashCode()); + Assert.NotEqual(rect1.GetHashCode(), new Rectangle(10, 10, 10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue, float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)] + [InlineData(0, 0, 0, 0)] + public void RectangleFConversionTest(float x, float y, float width, float height) + { + var rect = new RectangleF(x, y, width, height); + Rectangle rCeiling, rTruncate, rRound; + + unchecked + { + rCeiling = new Rectangle((int)Math.Ceiling(x), (int)Math.Ceiling(y), + (int)Math.Ceiling(width), (int)Math.Ceiling(height)); + rTruncate = new Rectangle((int)x, (int)y, (int)width, (int)height); + rRound = new Rectangle((int)Math.Round(x), (int)Math.Round(y), + (int)Math.Round(width), (int)Math.Round(height)); + } + + Assert.Equal(rCeiling, Rectangle.Ceiling(rect)); + Assert.Equal(rTruncate, Rectangle.Truncate(rect)); + Assert.Equal(rRound, Rectangle.Round(rect)); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void ContainsTest(int x, int y, int width, int height) + { + var rect = new Rectangle(unchecked(2 * x - width), unchecked(2 * y - height), width, height); + var p = new Point(x, y); + var r = new Rectangle(x, y, width / 2, height / 2); + + Assert.False(rect.Contains(x, y)); + Assert.False(rect.Contains(p)); + Assert.False(rect.Contains(r)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void InflateTest(int x, int y, int width, int height) + { + Rectangle inflatedRect, rect = new Rectangle(x, y, width, height); + unchecked + { + inflatedRect = new Rectangle(x - width, y - height, width + 2 * width, height + 2 * height); + } + + Assert.Equal(inflatedRect, Rectangle.Inflate(rect, width, height)); + + rect.Inflate(width, height); + Assert.Equal(inflatedRect, rect); + + var s = new Size(x, y); + unchecked + { + inflatedRect = new Rectangle(rect.X - x, rect.Y - y, rect.Width + 2 * x, rect.Height + 2 * y); + } + + rect.Inflate(s); + Assert.Equal(inflatedRect, rect); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void IntersectTest(int x, int y, int width, int height) + { + var rect = new Rectangle(x, y, width, height); + var expectedRect = Rectangle.Intersect(rect, rect); + rect.Intersect(rect); + Assert.Equal(expectedRect, rect); + Assert.False(rect.IntersectsWith(expectedRect)); + } + + [Fact] + public static void IntersectIntersectingRectsTest() + { + var rect1 = new Rectangle(0, 0, 5, 5); + var rect2 = new Rectangle(1, 1, 3, 3); + var expected = new Rectangle(1, 1, 3, 3); + + Assert.Equal(expected, Rectangle.Intersect(rect1, rect2)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void UnionTest(int x, int y, int width, int height) + { + var a = new Rectangle(x, y, width, height); + var b = new Rectangle(width, height, x, y); + + int x1 = Math.Min(a.X, b.X); + int x2 = Math.Max(a.X + a.Width, b.X + b.Width); + int y1 = Math.Min(a.Y, b.Y); + int y2 = Math.Max(a.Y + a.Height, b.Y + b.Height); + + var expectedRectangle = new Rectangle(x1, y1, x2 - x1, y2 - y1); + + Assert.Equal(expectedRectangle, Rectangle.Union(a, b)); + } + + [Theory] + [InlineData(0, 0, 0, 0)] + [InlineData(int.MaxValue, int.MinValue, int.MinValue, int.MaxValue)] + [InlineData(int.MaxValue, 0, 0, int.MaxValue)] + [InlineData(0, int.MinValue, int.MaxValue, 0)] + public void OffsetTest(int x, int y, int width, int height) + { + var r1 = new Rectangle(x, y, width, height); + var expectedRect = new Rectangle(x + width, y + height, width, height); + var p = new Point(width, height); + + r1.Offset(p); + Assert.Equal(expectedRect, r1); + + expectedRect.Offset(p); + r1.Offset(width, height); + Assert.Equal(expectedRect, r1); + } + + [Fact] + public void ToStringTest() + { + var r = new Rectangle(5, -5, 0, 1); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Rectangle [ X={0}, Y={1}, Width={2}, Height={3} ]", r.X, r.Y, r.Width, r.Height), r.ToString()); + } + + [Fact] + public void ToStringTestEmpty() + { + var r = new Rectangle(0, 0, 0, 0); + Assert.Equal("Rectangle [ Empty ]", r.ToString()); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/SizeFTests.cs b/tests/ImageSharp.Tests/Numerics/SizeFTests.cs new file mode 100644 index 000000000..dfeee762f --- /dev/null +++ b/tests/ImageSharp.Tests/Numerics/SizeFTests.cs @@ -0,0 +1,163 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Globalization; + using System.Reflection; + using Xunit; + + public class SizeFTests + { + [Fact] + public void DefaultConstructorTest() + { + Assert.Equal(SizeF.Empty, new SizeF()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorAndDimensionsTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = new PointF(width, height); + var s2 = new SizeF(s1); + + Assert.Equal(s1, s2); + Assert.Equal(s1, new SizeF(p1)); + Assert.Equal(s2, new SizeF(p1)); + + Assert.Equal(width, s1.Width); + Assert.Equal(height, s1.Height); + + s1.Width = 10; + Assert.Equal(10, s1.Width); + + s1.Height = -10.123f; + Assert.Equal(-10.123, s1.Height, 3); + } + + [Fact] + public void IsEmptyDefaultsTest() + { + Assert.True(SizeF.Empty.IsEmpty); + Assert.True(new SizeF().IsEmpty); + Assert.True(new SizeF(0, 0).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + public void IsEmptyRandomTest(float width, float height) + { + Assert.False(new SizeF(width, height).IsEmpty); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(float width, float height) + { + var s1 = new SizeF(width, height); + var s2 = new SizeF(height, width); + var addExpected = new SizeF(width + height, width + height); + var subExpected = new SizeF(width - height, height - width); + + Assert.Equal(addExpected, s1 + s2); + Assert.Equal(addExpected, SizeF.Add(s1, s2)); + + Assert.Equal(subExpected, s1 - s2); + Assert.Equal(subExpected, SizeF.Subtract(s1, s2)); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(float width, float height) + { + var sLeft = new SizeF(width, height); + var sRight = new SizeF(height, width); + + if (width == height) + { + Assert.True(sLeft == sRight); + Assert.False(sLeft != sRight); + Assert.True(sLeft.Equals(sRight)); + Assert.True(sLeft.Equals((object)sRight)); + Assert.Equal(sLeft.GetHashCode(), sRight.GetHashCode()); + return; + } + + Assert.True(sLeft != sRight); + Assert.False(sLeft == sRight); + Assert.False(sLeft.Equals(sRight)); + Assert.False(sLeft.Equals((object)sRight)); + } + + [Fact] + public static void EqualityTest_NotSizeF() + { + var size = new SizeF(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + + // If SizeF implements IEquatable (e.g in .NET Core), then classes that are implicitly + // convertible to SizeF can potentially be equal. + // See https://github.com/dotnet/corefx/issues/5255. + bool expectsImplicitCastToSizeF = typeof(IEquatable).IsAssignableFrom(size.GetType()); + Assert.Equal(expectsImplicitCastToSizeF, size.Equals(new Size(0, 0))); + + Assert.False(size.Equals((object)new Size(0, 0))); // No implicit cast + } + + [Fact] + public static void GetHashCodeTest() + { + var size = new SizeF(10, 10); + Assert.Equal(size.GetHashCode(), new SizeF(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new SizeF(10, 20).GetHashCode()); + } + + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void ConversionTest(float width, float height) + { + var s1 = new SizeF(width, height); + var p1 = (PointF)s1; + var s2 = new Size(unchecked((int)width), unchecked((int)height)); + + Assert.Equal(new PointF(width, height), p1); + Assert.Equal(p1, (PointF)s1); + Assert.Equal(s2, (Size)s1); + } + + [Fact] + public void ToStringTest() + { + var sz = new SizeF(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "SizeF [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); + } + + [Fact] + public void ToStringTestEmpty() + { + var sz = new SizeF(0, 0); + Assert.Equal("SizeF [ Empty ]", sz.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Numerics/SizeTests.cs b/tests/ImageSharp.Tests/Numerics/SizeTests.cs index 29eb768d9..010cdad3c 100644 --- a/tests/ImageSharp.Tests/Numerics/SizeTests.cs +++ b/tests/ImageSharp.Tests/Numerics/SizeTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests { + using System.Globalization; using Xunit; /// @@ -12,39 +13,183 @@ namespace ImageSharp.Tests /// public class SizeTests { - /// - /// Tests the equality operators for equality. - /// [Fact] - public void AreEqual() + public void DefaultConstructorTest() { - Size first = new Size(100, 100); - Size second = new Size(100, 100); + Assert.Equal(Size.Empty, new Size()); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void NonDefaultConstructorTest(int width, int height) + { + var s1 = new Size(width, height); + var s2 = new Size(new Point(width, height)); - Assert.Equal(first, second); + Assert.Equal(s1, s2); + + s1.Width = 10; + Assert.Equal(10, s1.Width); + + s1.Height = -10; + Assert.Equal(-10, s1.Height); } - /// - /// Tests the equality operators for inequality. - /// [Fact] - public void AreNotEqual() + public void IsEmptyDefaultsTest() + { + Assert.True(Size.Empty.IsEmpty); + Assert.True(new Size().IsEmpty); + Assert.True(new Size(0, 0).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + public void IsEmptyRandomTest(int width, int height) + { + Assert.False(new Size(width, height).IsEmpty); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void DimensionsTest(int width, int height) { - Size first = new Size(0, 100); - Size second = new Size(100, 100); + var p = new Size(width, height); + Assert.Equal(width, p.Width); + Assert.Equal(height, p.Height); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void PointFConversionTest(int width, int height) + { + SizeF sz = new Size(width, height); + Assert.Equal(new SizeF(width, height), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void SizeConversionTest(int width, int height) + { + var sz = (Point)new Size(width, height); + Assert.Equal(new Point(width, height), sz); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void ArithmeticTest(int width, int height) + { + var sz1 = new Size(width, height); + var sz2 = new Size(height, width); + Size addExpected, subExpected; + + unchecked + { + addExpected = new Size(width + height, height + width); + subExpected = new Size(width - height, height - width); + } + + Assert.Equal(addExpected, sz1 + sz2); + Assert.Equal(subExpected, sz1 - sz2); + Assert.Equal(addExpected, Size.Add(sz1, sz2)); + Assert.Equal(subExpected, Size.Subtract(sz1, sz2)); + } - Assert.NotEqual(first, second); + [Theory] + [InlineData(float.MaxValue, float.MinValue)] + [InlineData(float.MinValue, float.MinValue)] + [InlineData(float.MaxValue, float.MaxValue)] + [InlineData(0, 0)] + public void PointFMathematicalTest(float width, float height) + { + var szF = new SizeF(width, height); + Size pCeiling, pTruncate, pRound; + + unchecked + { + pCeiling = new Size((int)MathF.Ceiling(width), (int)MathF.Ceiling(height)); + pTruncate = new Size((int)width, (int)height); + pRound = new Size((int)MathF.Round(width), (int)MathF.Round(height)); + } + + Assert.Equal(pCeiling, Size.Ceiling(szF)); + Assert.Equal(pRound, Size.Round(szF)); + Assert.Equal(pTruncate, (Size)szF); + } + + [Theory] + [InlineData(int.MaxValue, int.MinValue)] + [InlineData(int.MinValue, int.MinValue)] + [InlineData(int.MaxValue, int.MaxValue)] + [InlineData(0, 0)] + public void EqualityTest(int width, int height) + { + var p1 = new Size(width, height); + var p2 = new Size(unchecked(width - 1), unchecked(height - 1)); + var p3 = new Size(width, height); + + Assert.True(p1 == p3); + Assert.True(p1 != p2); + Assert.True(p2 != p3); + + Assert.True(p1.Equals(p3)); + Assert.False(p1.Equals(p2)); + Assert.False(p2.Equals(p3)); + + Assert.True(p1.Equals((object)p3)); + Assert.False(p1.Equals((object)p2)); + Assert.False(p2.Equals((object)p3)); + + Assert.Equal(p1.GetHashCode(), p3.GetHashCode()); + } + + [Fact] + public static void EqualityTest_NotSize() + { + var size = new Size(0, 0); + Assert.False(size.Equals(null)); + Assert.False(size.Equals(0)); + Assert.False(size.Equals(new SizeF(0, 0))); + } + + [Fact] + public static void GetHashCodeTest() + { + var size = new Size(10, 10); + Assert.Equal(size.GetHashCode(), new Size(10, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(20, 10).GetHashCode()); + Assert.NotEqual(size.GetHashCode(), new Size(10, 20).GetHashCode()); + } + + [Fact] + public void ToStringTest() + { + var sz = new Size(10, 5); + Assert.Equal(string.Format(CultureInfo.CurrentCulture, "Size [ Width={0}, Height={1} ]", sz.Width, sz.Height), sz.ToString()); } - /// - /// Tests whether the size constructor correctly assign properties. - /// [Fact] - public void ConstructorAssignsProperties() + public void ToStringTestEmpty() { - Size first = new Size(4, 5); - Assert.Equal(4, first.Width); - Assert.Equal(5, first.Height); + var sz = new Size(0, 0); + Assert.Equal("Size [ Empty ]", sz.ToString()); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs new file mode 100644 index 000000000..e792de22f --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/BinaryThresholdTest.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Binarization +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class BinaryThresholdTest : FileTestBase + { + public static readonly TheoryData BinaryThresholdValues + = new TheoryData + { + .25F, + .75F + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + public void ImageShouldApplyBinaryThresholdFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.BinaryThreshold(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BinaryThresholdValues), DefaultPixelType)] + public void ImageShouldApplyBinaryThresholdInBox(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.BinaryThreshold(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Binarization/DitherTest.cs b/tests/ImageSharp.Tests/Processing/Binarization/DitherTest.cs new file mode 100644 index 000000000..2948c1046 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Binarization/DitherTest.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Binarization +{ + using ImageSharp.Dithering; + using ImageSharp.Dithering.Ordered; + using ImageSharp.PixelFormats; + + using Xunit; + + public class DitherTest : FileTestBase + { + public static readonly TheoryData Ditherers = new TheoryData + { + { "Ordered", new Ordered() }, + { "Bayer", new Bayer() } + }; + + public static readonly TheoryData ErrorDiffusers = new TheoryData + { + { "Atkinson", new Atkinson() }, + { "Burks", new Burks() }, + { "FloydSteinberg", new FloydSteinberg() }, + { "JarvisJudiceNinke", new JarvisJudiceNinke() }, + { "Sierra2", new Sierra2() }, + { "Sierra3", new Sierra3() }, + { "SierraLite", new SierraLite() }, + { "Stucki", new Stucki() }, + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(Ditherers), DefaultPixelType)] + public void ImageShouldApplyDitherFilter(TestImageProvider provider, string name, IOrderedDither ditherer) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Dither(ditherer) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(Ditherers), DefaultPixelType)] + public void ImageShouldApplyDitherFilterInBox(TestImageProvider provider, string name, IOrderedDither ditherer) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Dither(ditherer, bounds) + .DebugSave(provider, name, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ErrorDiffusers), DefaultPixelType)] + public void ImageShouldApplyDiffusionFilter(TestImageProvider provider, string name, IErrorDiffuser diffuser) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Dither(diffuser, .5F) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ErrorDiffusers), DefaultPixelType)] + public void ImageShouldApplyDiffusionFilterInBox(TestImageProvider provider, string name, IErrorDiffuser diffuser) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Dither(diffuser,.5F, bounds) + .DebugSave(provider, name, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/BlackWhiteTest.cs new file mode 100644 index 000000000..d825b4993 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/BlackWhiteTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class BlackWhiteTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyBlackWhiteFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.BlackWhite() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyBlackWhiteFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.BlackWhite(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/ColorBlindnessTest.cs new file mode 100644 index 000000000..b56ec3471 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/ColorBlindnessTest.cs @@ -0,0 +1,57 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class ColorBlindnessTest : FileTestBase + { + public static readonly TheoryData ColorBlindnessFilters + = new TheoryData + { + ColorBlindness.Achromatomaly, + ColorBlindness.Achromatopsia, + ColorBlindness.Deuteranomaly, + ColorBlindness.Deuteranopia, + ColorBlindness.Protanomaly, + ColorBlindness.Protanopia, + ColorBlindness.Tritanomaly, + ColorBlindness.Tritanopia + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ColorBlindnessFilters), DefaultPixelType)] + public void ImageShouldApplyColorBlindnessFilter(TestImageProvider provider, ColorBlindness colorBlindness) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.ColorBlindness(colorBlindness) + .DebugSave(provider, colorBlindness.ToString(), Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ColorBlindnessFilters), DefaultPixelType)] + public void ImageShouldApplyColorBlindnessFilterInBox(TestImageProvider provider, ColorBlindness colorBlindness) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.ColorBlindness(colorBlindness, bounds) + .DebugSave(provider, colorBlindness.ToString(), Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/GrayscaleTest.cs similarity index 57% rename from tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs rename to tests/ImageSharp.Tests/Processing/ColorMatrix/GrayscaleTest.cs index 9a7d87854..89a388e4d 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/GrayscaleTest.cs @@ -3,21 +3,27 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Tests +namespace ImageSharp.Tests.Processing.ColorMatrix { - using Xunit; + using ImageSharp.PixelFormats; using ImageSharp.Processing; - using ImageSharp.PixelFormats; + using Xunit; public class GrayscaleTest : FileTestBase { + public static readonly TheoryData GrayscaleModeTypes + = new TheoryData + { + GrayscaleMode.Bt601, + GrayscaleMode.Bt709 + }; + /// /// Use test patterns over loaded images to save decode time. /// [Theory] - [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt709)] - [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt601)] + [WithTestPatternImages(nameof(GrayscaleModeTypes), 50, 50, DefaultPixelType)] public void ImageShouldApplyGrayscaleFilterAll(TestImageProvider provider, GrayscaleMode value) where TPixel : struct, IPixel { @@ -25,9 +31,9 @@ namespace ImageSharp.Tests { image.Grayscale(value); byte[] data = new byte[3]; - foreach (TPixel p in image.Pixels) + for (int i = 0; i < image.Pixels.Length; i++) { - p.ToXyzBytes(data, 0); + image.Pixels[i].ToXyzBytes(data, 0); Assert.Equal(data[0], data[1]); Assert.Equal(data[1], data[2]); } @@ -37,22 +43,18 @@ namespace ImageSharp.Tests } [Theory] - [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt709)] - [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt601)] + [WithTestPatternImages(nameof(GrayscaleModeTypes), 50, 50, DefaultPixelType)] public void ImageShouldApplyGrayscaleFilterInBox(TestImageProvider provider, GrayscaleMode value) where TPixel : struct, IPixel { using (Image source = provider.GetImage()) - using (Image image = new Image(source)) + using (var image = new Image(source)) { - Rectangle rect = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - image.Grayscale(rect, value) + var bounds = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + image.Grayscale(value, bounds) .DebugSave(provider, value.ToString()); - // Let's draw identical shapes over the greyed areas and ensure that it didn't change the outer area - image.Fill(NamedColors.HotPink, rect); - source.Fill(NamedColors.HotPink, rect); - ImageComparer.CheckSimilarity(image, source); + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/HueTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/HueTest.cs new file mode 100644 index 000000000..b71905374 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/HueTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class HueTest : FileTestBase + { + public static readonly TheoryData HueValues + = new TheoryData + { + 180, + -180 + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(HueValues), DefaultPixelType)] + public void ImageShouldApplyHueFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Hue(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(HueValues), DefaultPixelType)] + public void ImageShouldApplyHueFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Hue(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/KodachromeTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/KodachromeTest.cs new file mode 100644 index 000000000..3a5d4f0d3 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/KodachromeTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class KodachromeTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyKodachromeFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Kodachrome() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyKodachromeFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Kodachrome(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/LomographTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/LomographTest.cs new file mode 100644 index 000000000..bfa37eef4 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/LomographTest.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + + using ImageSharp.PixelFormats; + + using Xunit; + + public class LomographTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyLomographFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Lomograph() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyLomographFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Lomograph(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/PolaroidTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/PolaroidTest.cs new file mode 100644 index 000000000..2bf1c3698 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/PolaroidTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class PolaroidTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyPolaroidFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Polaroid() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyPolaroidFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Polaroid(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/SaturationTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/SaturationTest.cs new file mode 100644 index 000000000..9212c09e8 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/SaturationTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class SaturationTest : FileTestBase + { + public static readonly TheoryData SaturationValues + = new TheoryData + { + 50 , + -50 , + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(SaturationValues), DefaultPixelType)] + public void ImageShouldApplySaturationFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Saturation(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(SaturationValues), DefaultPixelType)] + public void ImageShouldApplySaturationFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Saturation(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/ColorMatrix/SepiaTest.cs b/tests/ImageSharp.Tests/Processing/ColorMatrix/SepiaTest.cs new file mode 100644 index 000000000..de536050b --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/ColorMatrix/SepiaTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.ColorMatrix +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class SepiaTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplySepiaFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Sepia() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplySepiaFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Sepia(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs new file mode 100644 index 000000000..b5ee876de --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Convolution/BoxBlurTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Convolution +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class BoxBlurTest : FileTestBase + { + public static readonly TheoryData BoxBlurValues + = new TheoryData + { + 3, + 5 + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BoxBlurValues), DefaultPixelType)] + public void ImageShouldApplyBoxBlurFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.BoxBlur(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BoxBlurValues), DefaultPixelType)] + public void ImageShouldApplyBoxBlurFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.BoxBlur(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs new file mode 100644 index 000000000..220c0f466 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Convolution/DetectEdgesTest.cs @@ -0,0 +1,59 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Convolution +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class DetectEdgesTest : FileTestBase + { + public static readonly TheoryData DetectEdgesFilters + = new TheoryData + { + EdgeDetection.Kayyali, + EdgeDetection.Kirsch, + EdgeDetection.Lapacian3X3, + EdgeDetection.Lapacian5X5, + EdgeDetection.LaplacianOfGaussian, + EdgeDetection.Prewitt, + EdgeDetection.RobertsCross, + EdgeDetection.Robinson, + EdgeDetection.Scharr, + EdgeDetection.Sobel + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(DetectEdgesFilters), DefaultPixelType)] + public void ImageShouldApplyDetectEdgesFilter(TestImageProvider provider, EdgeDetection detector) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.DetectEdges(detector) + .DebugSave(provider, detector.ToString(), Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(DetectEdgesFilters), DefaultPixelType)] + public void ImageShouldApplyDetectEdgesFilterInBox(TestImageProvider provider, EdgeDetection detector) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.DetectEdges(detector, bounds) + .DebugSave(provider, detector.ToString(), Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs similarity index 50% rename from tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs rename to tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs index 4b2ac8b7c..dbaed9290 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianBlurTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianBlurTest.cs @@ -3,49 +3,47 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Tests +namespace ImageSharp.Tests.Processing.Convolution { - using System.IO; using ImageSharp.PixelFormats; + using Xunit; - public class GaussianBlurTest + public class GaussianBlurTest : FileTestBase { public static readonly TheoryData GaussianBlurValues = new TheoryData { - 3 , - 5 , + 3, + 5 }; [Theory] - [WithTestPatternImages(nameof(GaussianBlurValues), 320, 240, PixelTypes.StandardImageClass)] + [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] public void ImageShouldApplyGaussianBlurFilter(TestImageProvider provider, int value) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { image.GaussianBlur(value) - .DebugSave(provider, value.ToString()); + .DebugSave(provider, value, Extensions.Bmp); } } [Theory] - [WithTestPatternImages(nameof(GaussianBlurValues), 320, 240, PixelTypes.StandardImageClass)] + [WithFileCollection(nameof(DefaultFiles), nameof(GaussianBlurValues), DefaultPixelType)] public void ImageShouldApplyGaussianBlurFilterInBox(TestImageProvider provider, int value) where TPixel : struct, IPixel { using (Image source = provider.GetImage()) - using (Image image = new Image(source)) + using (var image = new Image(source)) { - Rectangle rect = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - image.GaussianBlur(value, rect) - .DebugSave(provider, value.ToString()); + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.GaussianBlur(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); - // lets draw identical shapes over the blured areas and ensure that it didn't change the outer area - image.Fill(NamedColors.HotPink, rect); - source.Fill(NamedColors.HotPink, rect); - ImageComparer.CheckSimilarity(image, source); + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs similarity index 50% rename from tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs rename to tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs index 1fa1ae15c..44296d975 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GaussianSharpenTest.cs +++ b/tests/ImageSharp.Tests/Processing/Convolution/GaussianSharpenTest.cs @@ -3,10 +3,10 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Tests +namespace ImageSharp.Tests.Processing.Convolution { - using System.IO; using ImageSharp.PixelFormats; + using Xunit; public class GaussianSharpenTest : FileTestBase @@ -14,38 +14,36 @@ namespace ImageSharp.Tests public static readonly TheoryData GaussianSharpenValues = new TheoryData { - 3 , - 5 , + 3, + 5 }; [Theory] - [WithTestPatternImages(nameof(GaussianSharpenValues), 320, 240, PixelTypes.StandardImageClass)] + [WithFileCollection(nameof(DefaultFiles), nameof(GaussianSharpenValues), DefaultPixelType)] public void ImageShouldApplyGaussianSharpenFilter(TestImageProvider provider, int value) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { image.GaussianSharpen(value) - .DebugSave(provider, value.ToString()); + .DebugSave(provider, value, Extensions.Bmp); } } [Theory] - [WithTestPatternImages(nameof(GaussianSharpenValues), 320, 240, PixelTypes.StandardImageClass)] + [WithFileCollection(nameof(DefaultFiles), nameof(GaussianSharpenValues), DefaultPixelType)] public void ImageShouldApplyGaussianSharpenFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel + where TPixel : struct, IPixel { using (Image source = provider.GetImage()) - using (Image image = new Image(source)) + using (var image = new Image(source)) { - Rectangle rect = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - image.GaussianSharpen(value, rect) - .DebugSave(provider, value.ToString()); + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.GaussianSharpen(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); - // lets draw identical shapes over the Sharpened areas and ensure that it didn't change the outer area - image.Fill(NamedColors.HotPink, rect); - source.Fill(NamedColors.HotPink, rect); - ImageComparer.CheckSimilarity(image, source); + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); } } } diff --git a/tests/ImageSharp.Tests/Processing/Effects/AlphaTest.cs b/tests/ImageSharp.Tests/Processing/Effects/AlphaTest.cs new file mode 100644 index 000000000..63366dc1d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/AlphaTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class AlphaTest : FileTestBase + { + public static readonly TheoryData AlphaValues + = new TheoryData + { + 20/100F, + 80/100F + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(AlphaValues), DefaultPixelType)] + public void ImageShouldApplyAlphaFilter(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Alpha(value) + .DebugSave(provider, value, Extensions.Png); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(AlphaValues), DefaultPixelType)] + public void ImageShouldApplyAlphaFilterInBox(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Alpha(value, bounds) + .DebugSave(provider, value, Extensions.Png); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs new file mode 100644 index 000000000..c3fda4cd4 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/BackgroundColorTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class BackgroundColorTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyBackgroundColorFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.BackgroundColor(NamedColors.HotPink) + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyBackgroundColorFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.BackgroundColor(NamedColors.HotPink, bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/BrightnessTest.cs b/tests/ImageSharp.Tests/Processing/Effects/BrightnessTest.cs new file mode 100644 index 000000000..4c0217fed --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/BrightnessTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class BrightnessTest : FileTestBase + { + public static readonly TheoryData BrightnessValues + = new TheoryData + { + 50, + -50 + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BrightnessValues), DefaultPixelType)] + public void ImageShouldApplyBrightnessFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Brightness(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(BrightnessValues), DefaultPixelType)] + public void ImageShouldApplyBrightnessFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Brightness(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); ; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/ContrastTest.cs b/tests/ImageSharp.Tests/Processing/Effects/ContrastTest.cs new file mode 100644 index 000000000..ac217e9f6 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/ContrastTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class ContrastTest : FileTestBase + { + public static readonly TheoryData ContrastValues + = new TheoryData + { + 50, + -50 + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ContrastValues), DefaultPixelType)] + public void ImageShouldApplyContrastFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Contrast(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(ContrastValues), DefaultPixelType)] + public void ImageShouldApplyContrastFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Contrast(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/InvertTest.cs b/tests/ImageSharp.Tests/Processing/Effects/InvertTest.cs new file mode 100644 index 000000000..80e345092 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/InvertTest.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class InvertTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyInvertFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Invert() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyInvertFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Invert(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs new file mode 100644 index 000000000..7d02ace3d --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/OilPaintTest.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class OilPaintTest : FileTestBase + { + public static readonly TheoryData OilPaintValues + = new TheoryData + { + { 15, 10 }, + { 6, 5 } + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] + public void ImageShouldApplyOilPaintFilter(TestImageProvider provider, int levels, int brushSize) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.OilPaint(levels, brushSize) + .DebugSave(provider, string.Join("-", levels, brushSize), Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(OilPaintValues), DefaultPixelType)] + public void ImageShouldApplyOilPaintFilterInBox(TestImageProvider provider, int levels, int brushSize) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.OilPaint(levels, brushSize, bounds) + .DebugSave(provider, string.Join("-", levels, brushSize), Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds, 0.001F); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs new file mode 100644 index 000000000..740b78c55 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Effects/PixelateTest.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Effects +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class PixelateTest : FileTestBase + { + public static readonly TheoryData PixelateValues + = new TheoryData + { + 4 , + 8 + }; + + [Theory] + [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] + public void ImageShouldApplyPixelateFilter(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Pixelate(value) + .DebugSave(provider, value, Extensions.Bmp); + + // Test the neigbouring pixels + for (int y = 0; y < image.Height; y += value) + { + for (int x = 0; x < image.Width; x += value) + { + TPixel source = image[x, y]; + for (int pixY = y; pixY < y + value && pixY < image.Height; pixY++) + { + for (int pixX = x; pixX < x + value && pixX < image.Width; pixX++) + { + Assert.Equal(source, image[pixX, pixY]); + } + } + } + } + } + } + + [Theory] + [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.Rgba32)] + public void ImageShouldApplyPixelateFilterInBox(TestImageProvider provider, int value) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + + image.Pixelate(value, bounds) + .DebugSave(provider, value, Extensions.Bmp); + + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + int tx = x; + int ty = y; + TPixel sourceColor = source[tx, ty]; + if (bounds.Contains(tx, ty)) + { + int sourceX = tx - ((tx - bounds.Left) % value) + (value / 2); + int sourceY = ty - ((ty - bounds.Top) % value) + (value / 2); + + sourceColor = image[sourceX, sourceY]; + } + Assert.Equal(sourceColor, image[tx, ty]); + } + } + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs new file mode 100644 index 000000000..a233e204c --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Overlays/GlowTest.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Overlays +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class GlowTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyGlowFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Glow() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyGlowFilterColor(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Glow(NamedColors.Orange) + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyGlowFilterRadius(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Glow(image.Width / 4F) + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyGlowFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Glow(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs new file mode 100644 index 000000000..85ccfca6f --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Overlays/VignetteTest.cs @@ -0,0 +1,67 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Overlays +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class VignetteTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyVignetteFilter(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Vignette() + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyVignetteFilterColor(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Vignette(NamedColors.Orange) + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyVignetteFilterRadius(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Vignette(image.Width / 4F, image.Height / 4F) + .DebugSave(provider, null, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldApplyVignetteFilterInBox(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image source = provider.GetImage()) + using (var image = new Image(source)) + { + var bounds = new Rectangle(10, 10, image.Width / 2, image.Height / 2); + + image.Vignette(bounds) + .DebugSave(provider, null, Extensions.Bmp); + + ImageComparer.EnsureProcessorChangesAreConstrained(source, image, bounds); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs similarity index 56% rename from tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs rename to tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs index 470e7150b..7bc0c8bb5 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AutoOrientTests.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/AutoOrientTests.cs @@ -1,19 +1,19 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Tests +namespace ImageSharp.Tests.Processing.Transforms { - using System.IO; - using ImageSharp.PixelFormats; + using ImageSharp.Processing; - using Processing; using Xunit; public class AutoOrientTests : FileTestBase { + public static readonly string[] FlipFiles = { TestImages.Bmp.F }; + public static readonly TheoryData OrientationValues = new TheoryData { @@ -29,23 +29,19 @@ namespace ImageSharp.Tests }; [Theory] - [MemberData(nameof(OrientationValues))] - public void ImageShouldFlip(RotateType rotateType, FlipType flipType, ushort orientation) + [WithFileCollection(nameof(FlipFiles), nameof(OrientationValues), DefaultPixelType)] + public void ImageShouldAutoRotate(TestImageProvider provider, RotateType rotateType, FlipType flipType, ushort orientation) + where TPixel : struct, IPixel { - string path = this.CreateOutputDirectory("AutoOrient"); - - TestFile file = TestFile.Create(TestImages.Bmp.F); - - using (Image image = file.CreateImage()) + using (Image image = provider.GetImage()) { image.MetaData.ExifProfile = new ExifProfile(); image.MetaData.ExifProfile.SetValue(ExifTag.Orientation, orientation); - using (FileStream before = File.OpenWrite($"{path}/before-{file.FileName}")) - using (FileStream after = File.OpenWrite($"{path}/after-{file.FileName}")) - { - image.RotateFlip(rotateType, flipType).Save(before).AutoOrient().Save(after); - } + image.RotateFlip(rotateType, flipType) + .DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "1_before"), Extensions.Bmp) + .AutoOrient() + .DebugSave(provider, string.Join("_", rotateType, flipType, orientation, "2_after"), Extensions.Bmp); } } } diff --git a/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs new file mode 100644 index 000000000..ca20abf79 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/CropTest.cs @@ -0,0 +1,26 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class CropTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldCrop(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Crop(image.Width / 2, image.Height / 2) + .DebugSave(provider, null, Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs new file mode 100644 index 000000000..24febd5b2 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/EntropyCropTest.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class EntropyCropTest : FileTestBase + { + public static readonly TheoryData EntropyCropValues + = new TheoryData + { + .25F, + .75F + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(EntropyCropValues), DefaultPixelType)] + public void ImageShouldEntropyCrop(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.EntropyCrop(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs new file mode 100644 index 000000000..45ab1e5f8 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/FlipTests.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class FlipTests : FileTestBase + { + public static readonly string[] FlipFiles = { TestImages.Bmp.F }; + + public static readonly TheoryData FlipValues + = new TheoryData + { + { FlipType.None }, + { FlipType.Vertical }, + { FlipType.Horizontal }, + }; + + [Theory] + [WithFileCollection(nameof(FlipFiles), nameof(FlipValues), DefaultPixelType)] + public void ImageShouldFlip(TestImageProvider provider, FlipType flipType) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Flip(flipType) + .DebugSave(provider, flipType, Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs new file mode 100644 index 000000000..666f3df78 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/PadTest.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class PadTest : FileTestBase + { + [Theory] + [WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] + public void ImageShouldPad(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Pad(image.Width + 50, image.Height + 50) + .DebugSave(provider, null, Extensions.Bmp); + + // Check pixels are empty + for (int y = 0; y < 25; y++) + { + for (int x = 0; x < 25; x++) + { + Assert.Equal(image[x, y], default(TPixel)); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeProfilingBenchmarks.cs similarity index 69% rename from tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs rename to tests/ImageSharp.Tests/Processing/Transforms/ResizeProfilingBenchmarks.cs index a743665d4..8b57586f4 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeProfilingBenchmarks.cs @@ -1,9 +1,14 @@ -namespace ImageSharp.Tests +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms { + using System; using System.IO; using System.Text; - using ImageSharp.PixelFormats; using ImageSharp.Processing; using ImageSharp.Processing.Processors; @@ -27,7 +32,7 @@ namespace ImageSharp.Tests this.Measure(this.ExecutionCount, () => { - using (Image image = new Image(width, height)) + using (var image = new Image(width, height)) { image.Resize(width / 4, height / 4); } @@ -37,26 +42,28 @@ namespace ImageSharp.Tests // [Fact] public void PrintWeightsData() { - ResizeProcessor proc = new ResizeProcessor(new BicubicResampler(), 200, 200); + var proc = new ResizeProcessor(new BicubicResampler(), 200, 200); ResamplingWeightedProcessor.WeightsBuffer weights = proc.PrecomputeWeights(200, 500); - StringBuilder bld = new StringBuilder(); + var bld = new StringBuilder(); foreach (ResamplingWeightedProcessor.WeightsWindow window in weights.Weights) { + Span span = window.GetWindowSpan(); for (int i = 0; i < window.Length; i++) { - float value = window.Span[i]; + float value = span[i]; bld.Append(value); bld.Append("| "); } + bld.AppendLine(); } File.WriteAllText("BicubicWeights.MD", bld.ToString()); - //this.Output.WriteLine(bld.ToString()); + // this.Output.WriteLine(bld.ToString()); } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs new file mode 100644 index 000000000..008d041e7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/ResizeTests.cs @@ -0,0 +1,273 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class ResizeTests : FileTestBase + { + public static readonly string[] ResizeFiles = { TestImages.Jpeg.Baseline.Calliphora }; + + public static readonly TheoryData ReSamplers = + new TheoryData + { + { "Bicubic", new BicubicResampler() }, + { "Triangle", new TriangleResampler() }, + { "NearestNeighbor", new NearestNeighborResampler() }, + { "Box", new BoxResampler() }, + { "Lanczos3", new Lanczos3Resampler() }, + { "Lanczos5", new Lanczos5Resampler() }, + { "MitchellNetravali", new MitchellNetravaliResampler() }, + { "Lanczos8", new Lanczos8Resampler() }, + { "Hermite", new HermiteResampler() }, + { "Spline", new SplineResampler() }, + { "Robidoux", new RobidouxResampler() }, + { "RobidouxSharp", new RobidouxSharpResampler() }, + { "Welch", new WelchResampler() } + }; + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResize(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Resize(image.Width / 2, image.Height / 2, sampler, true) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeFromSourceRectangle(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4); + var destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); + + image.Resize(image.Width, image.Height, sampler, sourceRectangle, destRectangle, false) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWidthAndKeepAspect(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Resize(image.Width / 3, 0, sampler, false) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeHeightAndKeepAspect(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Resize(0, image.Height / 3, sampler, false) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithCropWidthMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(image.Width / 2, image.Height) + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithCropHeightMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(image.Width, image.Height / 2) + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithPadMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(image.Width + 200, image.Height), + Mode = ResizeMode.Pad + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithBoxPadMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(image.Width + 200, image.Height + 200), + Mode = ResizeMode.BoxPad + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithMaxMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(300, 300), + Mode = ResizeMode.Max + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithMinMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size((int)MathF.Round(image.Width * .75F), (int)MathF.Round(image.Height * .95F)), + Mode = ResizeMode.Min + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(ResizeFiles), nameof(ReSamplers), DefaultPixelType)] + public void ImageShouldResizeWithStretchMode(TestImageProvider provider, string name, IResampler sampler) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var options = new ResizeOptions + { + Sampler = sampler, + Size = new Size(image.Width / 2, image.Height), + Mode = ResizeMode.Stretch + }; + + image.Resize(options) + .DebugSave(provider, name, Extensions.Bmp); + } + } + + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void BicubicWindowOscillatesCorrectly(float x, float expected) + { + var sampler = new BicubicResampler(); + float result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void TriangleWindowOscillatesCorrectly(float x, float expected) + { + var sampler = new TriangleResampler(); + float result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + + [Theory] + [InlineData(-2, 0)] + [InlineData(-1, 0)] + [InlineData(0, 1)] + [InlineData(1, 0)] + [InlineData(2, 0)] + public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) + { + var sampler = new Lanczos3Resampler(); + float result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + + [Theory] + [InlineData(-4, 0)] + [InlineData(-2, 0)] + [InlineData(0, 1)] + [InlineData(2, 0)] + [InlineData(4, 0)] + public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) + { + var sampler = new Lanczos5Resampler(); + float result = sampler.GetValue(x); + + Assert.Equal(result, expected); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs new file mode 100644 index 000000000..f85ef6f13 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateFlipTests.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class RotateFlipTests : FileTestBase + { + public static readonly string[] FlipFiles = { TestImages.Bmp.F }; + + public static readonly TheoryData RotateFlipValues + = new TheoryData + { + { RotateType.None, FlipType.Vertical }, + { RotateType.None, FlipType.Horizontal }, + { RotateType.Rotate90, FlipType.None }, + { RotateType.Rotate180, FlipType.None }, + { RotateType.Rotate270, FlipType.None }, + }; + + [Theory] + [WithFileCollection(nameof(FlipFiles), nameof(RotateFlipValues), DefaultPixelType)] + public void ImageShouldRotateFlip(TestImageProvider provider, RotateType rotateType, FlipType flipType) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.RotateFlip(rotateType, flipType) + .DebugSave(provider, string.Join("_", rotateType, flipType), Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs new file mode 100644 index 000000000..1f1856429 --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/RotateTests.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + using Xunit; + + public class RotateTests : FileTestBase + { + public static readonly TheoryData RotateFloatValues + = new TheoryData + { + 170, + -170 + }; + + public static readonly TheoryData RotateEnumValues + = new TheoryData + { + RotateType.None, + RotateType.Rotate90, + RotateType.Rotate180, + RotateType.Rotate270 + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(RotateFloatValues), DefaultPixelType)] + public void ImageShouldRotate(TestImageProvider provider, float value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Rotate(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(RotateEnumValues), DefaultPixelType)] + public void ImageShouldRotateEnum(TestImageProvider provider, RotateType value) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Rotate(value) + .DebugSave(provider, value, Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs new file mode 100644 index 000000000..f2c2d7cbd --- /dev/null +++ b/tests/ImageSharp.Tests/Processing/Transforms/SkewTest.cs @@ -0,0 +1,33 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Processing.Transforms +{ + using ImageSharp.PixelFormats; + + using Xunit; + + public class SkewTest : FileTestBase + { + public static readonly TheoryData SkewValues + = new TheoryData + { + { 20, 10 }, + { -20, -10 } + }; + + [Theory] + [WithFileCollection(nameof(DefaultFiles), nameof(SkewValues), DefaultPixelType)] + public void ImageShouldSkew(TestImageProvider provider, float x, float y) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + image.Skew(x, y) + .DebugSave(provider, string.Join("_", x, y), Extensions.Bmp); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs deleted file mode 100644 index e40e3a205..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class AlphaTest : FileTestBase - { - public static readonly TheoryData AlphaValues - = new TheoryData - { - 20/100f , - 80/100f - }; - - [Theory] - [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilter(int value) - { - string path = this.CreateOutputDirectory("Alpha"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Alpha(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(AlphaValues))] - public void ImageShouldApplyAlphaFilterInBox(int value) - { - string path = this.CreateOutputDirectory("Alpha"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs deleted file mode 100644 index d7ca78d1b..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/BackgroundColorTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class BackgroundColorTest : FileTestBase - { - [Fact] - public void ImageShouldApplyBackgroundColorFilter() - { - string path = this.CreateOutputDirectory("BackgroundColor"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.BackgroundColor(Rgba32.HotPink).Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyBackgroundColorFilterInBox() - { - string path = this.CreateOutputDirectory("BackgroundColor"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BackgroundColor(Rgba32.HotPink, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/BinaryThresholdTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BinaryThresholdTest.cs deleted file mode 100644 index 4e5e8a82e..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/BinaryThresholdTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class BinaryThresholdTest : FileTestBase - { - public static readonly TheoryData BinaryThresholdValues - = new TheoryData - { - .25f , - .75f , - }; - - [Theory] - [MemberData(nameof(BinaryThresholdValues))] - public void ImageShouldApplyBinaryThresholdFilter(float value) - { - string path = this.CreateOutputDirectory("BinaryThreshold"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BinaryThreshold(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(BinaryThresholdValues))] - public void ImageShouldApplyBinaryThresholdInBox(float value) - { - string path = this.CreateOutputDirectory("BinaryThreshold"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BinaryThreshold(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs deleted file mode 100644 index d10698a99..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/BlackWhiteTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class BlackWhiteTest : FileTestBase - { - [Fact] - public void ImageShouldApplyBlackWhiteFilter() - { - string path = this.CreateOutputDirectory("BlackWhite"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.BlackWhite().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyBlackWhiteFilterInBox() - { - string path = this.CreateOutputDirectory("BlackWhite"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BlackWhite(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs deleted file mode 100644 index 03226a961..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/BoxBlurTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class BoxBlurTest : FileTestBase - { - public static readonly TheoryData BoxBlurValues - = new TheoryData - { - 3 , - 5 , - }; - - [Theory] - [MemberData(nameof(BoxBlurValues))] - public void ImageShouldApplyBoxBlurFilter(int value) - { - string path = this.CreateOutputDirectory("BoxBlur"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BoxBlur(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(BoxBlurValues))] - public void ImageShouldApplyBoxBlurFilterInBox(int value) - { - string path = this.CreateOutputDirectory("BoxBlur"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.BoxBlur(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs deleted file mode 100644 index ce434d734..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/BrightnessTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class BrightnessTest : FileTestBase - { - public static readonly TheoryData BrightnessValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData(nameof(BrightnessValues))] - public void ImageShouldApplyBrightnessFilter(int value) - { - string path = this.CreateOutputDirectory("Brightness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Brightness(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(BrightnessValues))] - public void ImageShouldApplyBrightnessFilterInBox(int value) - { - string path = this.CreateOutputDirectory("Brightness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Brightness(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs deleted file mode 100644 index c28732253..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/ColorBlindnessTest.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Processing; - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class ColorBlindnessTest : FileTestBase - { - public static readonly TheoryData ColorBlindnessFilters - = new TheoryData - { - ColorBlindness.Achromatomaly, - ColorBlindness.Achromatopsia, - ColorBlindness.Deuteranomaly, - ColorBlindness.Deuteranopia, - ColorBlindness.Protanomaly, - ColorBlindness.Protanopia, - ColorBlindness.Tritanomaly, - ColorBlindness.Tritanopia - }; - - [Theory] - [MemberData(nameof(ColorBlindnessFilters))] - public void ImageShouldApplyColorBlindnessFilter(ColorBlindness colorBlindness) - { - string path = this.CreateOutputDirectory("ColorBlindness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(colorBlindness); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.ColorBlindness(colorBlindness).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ColorBlindnessFilters))] - public void ImageShouldApplyBrightnessFilterInBox(ColorBlindness colorBlindness) - { - string path = this.CreateOutputDirectory("ColorBlindness"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(colorBlindness + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.ColorBlindness(colorBlindness, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs b/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs deleted file mode 100644 index 4626fbb6e..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/ContrastTest.cs +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class ContrastTest : FileTestBase - { - public static readonly TheoryData ContrastValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData(nameof(ContrastValues))] - public void ImageShouldApplyContrastFilter(int value) - { - string path = this.CreateOutputDirectory("Contrast"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Contrast(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ContrastValues))] - public void ImageShouldApplyContrastFilterInBox(int value) - { - string path = this.CreateOutputDirectory("Contrast"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Contrast(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs deleted file mode 100644 index 6713d0d38..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/CropTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class CropTest : FileTestBase - { - [Fact] - public void ImageShouldApplyCropSampler() - { - string path = this.CreateOutputDirectory("Crop"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Crop(image.Width / 2, image.Height / 2).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs deleted file mode 100644 index 00440e8a5..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/DetectEdgesTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using Processing; - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class DetectEdgesTest : FileTestBase - { - public static readonly TheoryData DetectEdgesFilters - = new TheoryData - { - EdgeDetection.Kayyali, - EdgeDetection.Kirsch, - EdgeDetection.Lapacian3X3, - EdgeDetection.Lapacian5X5, - EdgeDetection.LaplacianOfGaussian, - EdgeDetection.Prewitt, - EdgeDetection.RobertsCross, - EdgeDetection.Robinson, - EdgeDetection.Scharr, - EdgeDetection.Sobel - }; - - [Theory] - [MemberData(nameof(DetectEdgesFilters))] - public void ImageShouldApplyDetectEdgesFilter(EdgeDetection detector) - { - string path = this.CreateOutputDirectory("DetectEdges"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(detector); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.DetectEdges(detector).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(DetectEdgesFilters))] - public void ImageShouldApplyDetectEdgesFilterInBox(EdgeDetection detector) - { - string path = this.CreateOutputDirectory("DetectEdges"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(detector + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.DetectEdges(detector, new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs b/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs deleted file mode 100644 index 066e2d134..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/DitherTest.cs +++ /dev/null @@ -1,104 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.Dithering; - using ImageSharp.Dithering.Ordered; - using ImageSharp.PixelFormats; - - using Xunit; - - public class DitherTest : FileTestBase - { - public static readonly TheoryData Ditherers = new TheoryData - { - { "Ordered", new Ordered() }, - { "Bayer", new Bayer() } - }; - - public static readonly TheoryData ErrorDiffusers = new TheoryData - { - { "Atkinson", new Atkinson() }, - { "Burks", new Burks() }, - { "FloydSteinberg", new FloydSteinberg() }, - { "JarvisJudiceNinke", new JarvisJudiceNinke() }, - { "Sierra2", new Sierra2() }, - { "Sierra3", new Sierra3() }, - { "SierraLite", new SierraLite() }, - { "Stucki", new Stucki() }, - }; - - [Theory] - [MemberData(nameof(Ditherers))] - public void ImageShouldApplyDitherFilter(string name, IOrderedDither ditherer) - { - string path = this.CreateOutputDirectory("Dither", "Dither"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Dither(ditherer).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(Ditherers))] - public void ImageShouldApplyDitherFilterInBox(string name, IOrderedDither ditherer) - { - string path = this.CreateOutputDirectory("Dither", "Dither"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName($"{name}-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Dither(ditherer, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ErrorDiffusers))] - public void ImageShouldApplyDiffusionFilter(string name, IErrorDiffuser diffuser) - { - string path = this.CreateOutputDirectory("Dither", "Diffusion"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Dither(diffuser, .5F).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ErrorDiffusers))] - public void ImageShouldApplyDiffusionFilterInBox(string name, IErrorDiffuser diffuser) - { - string path = this.CreateOutputDirectory("Dither", "Diffusion"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName($"{name}-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Dither(diffuser, .5F, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs b/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs deleted file mode 100644 index 710e23e37..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/EntropyCropTest.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class EntropyCropTest : FileTestBase - { - public static readonly TheoryData EntropyCropValues - = new TheoryData - { - .25f , - .75f , - }; - - [Theory] - [MemberData(nameof(EntropyCropValues))] - public void ImageShouldApplyEntropyCropSampler(float value) - { - string path = this.CreateOutputDirectory("EntropyCrop"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.EntropyCrop(value).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs b/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs deleted file mode 100644 index 87f5e1025..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/FlipTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Processing; - using Xunit; - - public class FlipTests : FileTestBase - { - public static readonly TheoryData FlipValues - = new TheoryData - { - { FlipType.None }, - { FlipType.Vertical }, - { FlipType.Horizontal }, - }; - - [Theory] - [MemberData(nameof(FlipValues))] - public void ImageShouldFlip(FlipType flipType) - { - string path = this.CreateOutputDirectory("Flip"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(flipType); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Flip(flipType).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs deleted file mode 100644 index 43e45f9ca..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class GlowTest : FileTestBase - { - [Fact] - public void ImageShouldApplyGlowFilter() - { - string path = this.CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Glow().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterColor() - { - string path = this.CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Color"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(Rgba32.HotPink).Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterRadius() - { - string path = this.CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Radius"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(image.Width / 4F).Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyGlowFilterInBox() - { - string path = this.CreateOutputDirectory("Glow"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Glow(new Rectangle(image.Width / 8, image.Height / 8, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs b/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs deleted file mode 100644 index 488433931..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/HueTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class HueTest : FileTestBase - { - public static readonly TheoryData HueValues - = new TheoryData - { - 180 , - -180 , - }; - - [Theory] - [MemberData(nameof(HueValues))] - public void ImageShouldApplyHueFilter(int value) - { - string path = this.CreateOutputDirectory("Hue"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Hue(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(HueValues))] - public void ImageShouldApplyHueFilterInBox(int value) - { - string path = this.CreateOutputDirectory("Hue"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Hue(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs b/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs deleted file mode 100644 index 6d375d09a..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/InvertTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class InvertTest : FileTestBase - { - [Fact] - public void ImageShouldApplyInvertFilter() - { - string path = this.CreateOutputDirectory("Invert"); - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Invert().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyInvertFilterInBox() - { - string path = this.CreateOutputDirectory("Invert"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs b/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs deleted file mode 100644 index 29a459f97..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/KodachromeTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class KodachromeTest : FileTestBase - { - [Fact] - public void ImageShouldApplyKodachromeFilter() - { - string path = this.CreateOutputDirectory("Kodachrome"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Kodachrome().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyKodachromeFilterInBox() - { - string path = this.CreateOutputDirectory("Kodachrome"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Kodachrome(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs b/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs deleted file mode 100644 index 6ceedecbd..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/LomographTest.cs +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class LomographTest : FileTestBase - { - [Fact] - public void ImageShouldApplyLomographFilter() - { - string path = this.CreateOutputDirectory("Lomograph"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Lomograph().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyLomographFilterInBox() - { - string path = this.CreateOutputDirectory("Lomograph"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Lomograph(new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs b/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs deleted file mode 100644 index 5facee346..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/OilPaintTest.cs +++ /dev/null @@ -1,66 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System; - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class OilPaintTest : FileTestBase - { - public static readonly TheoryData> OilPaintValues - = new TheoryData> - { - new Tuple(15, 10), - new Tuple(6, 5) - }; - - [Theory] - [MemberData(nameof(OilPaintValues))] - public void ImageShouldApplyOilPaintFilter(Tuple value) - { - string path = this.CreateOutputDirectory("OilPaint"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - if (image.Width > value.Item2 && image.Height > value.Item2) - { - image.OilPaint(value.Item1, value.Item2).Save(output); - } - } - } - } - - [Theory] - [MemberData(nameof(OilPaintValues))] - public void ImageShouldApplyOilPaintFilterInBox(Tuple value) - { - string path = this.CreateOutputDirectory("OilPaint"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - { - if (image.Width > value.Item2 && image.Height > value.Item2) - { - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.OilPaint(value.Item1, value.Item2, new Rectangle(image.Width / 4, image.Width / 4, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs deleted file mode 100644 index 6095410a9..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/PadTest.cs +++ /dev/null @@ -1,31 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class PadTest : FileTestBase - { - [Fact] - public void ImageShouldApplyPadSampler() - { - string path = this.CreateOutputDirectory("Pad"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Pad(image.Width + 50, image.Height + 50).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs deleted file mode 100644 index b21a8c969..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/PixelateTest.cs +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - using ImageSharp.PixelFormats; - using Xunit; - - public class PixelateTest - { - public static readonly TheoryData PixelateValues - = new TheoryData - { - 4 , - 8 - }; - - [Theory] - [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.StandardImageClass)] - public void ImageShouldApplyPixelateFilter(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - image.Pixelate(value) - .DebugSave(provider, new - { - size = value - }); - - using (PixelAccessor pixels = image.Lock()) - { - for (int y = 0; y < pixels.Height; y += value) - { - for (int x = 0; x < pixels.Width; x += value) - { - TPixel source = pixels[x, y]; - for (int pixY = y; pixY < y + value && pixY < pixels.Height; pixY++) - { - for (int pixX = x; pixX < x + value && pixX < pixels.Width; pixX++) - { - Assert.Equal(source, pixels[pixX, pixY]); - } - } - } - } - } - } - } - - [Theory] - [WithTestPatternImages(nameof(PixelateValues), 320, 240, PixelTypes.StandardImageClass)] - public void ImageShouldApplyPixelateFilterInBox(TestImageProvider provider, int value) - where TPixel : struct, IPixel - { - using (Image source = provider.GetImage()) - using (Image image = new Image(source)) - { - Rectangle rect = new Rectangle(image.Width/4, image.Height / 4, image.Width / 2, image.Height / 2); - - image.Pixelate(value, rect) - .DebugSave(provider, new - { - size = value - }); - - using (PixelAccessor pixels = image.Lock()) - using (PixelAccessor sourcePixels = source.Lock()) - { - for (int y = 0; y < pixels.Height; y++) - { - for (int x = 0; x < pixels.Width; x++) - { - var tx = x; - var ty = y; - TPixel sourceColor = sourcePixels[tx, ty]; - if (rect.Contains(tx, ty)) - { - var sourceX = tx - ((tx - rect.Left) % value) + (value / 2); - var sourceY = ty - ((ty - rect.Top) % value) + (value / 2); - - sourceColor = pixels[sourceX, sourceY]; - } - Assert.Equal(sourceColor, pixels[tx, ty]); - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs b/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs deleted file mode 100644 index df3803255..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/PolaroidTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class PolaroidTest : FileTestBase - { - [Fact] - public void ImageShouldApplyPolaroidFilter() - { - string path = this.CreateOutputDirectory("Polaroid"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Polaroid().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyPolaroidFilterInBox() - { - string path = this.CreateOutputDirectory("Polaroid"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Polaroid(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs deleted file mode 100644 index 31f4020b6..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ /dev/null @@ -1,350 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// -namespace ImageSharp.Tests -{ - using System; - using System.IO; - - using ImageSharp.PixelFormats; - - using Processing; - using Xunit; - - public class ResizeTests : FileTestBase - { - public static readonly TheoryData ReSamplers = - new TheoryData - { - { "Bicubic", new BicubicResampler() }, - { "Triangle", new TriangleResampler() }, - { "NearestNeighbor", new NearestNeighborResampler() }, - - // Perf: Enable for local testing only - // { "Box", new BoxResampler() }, - // { "Lanczos3", new Lanczos3Resampler() }, - // { "Lanczos5", new Lanczos5Resampler() }, - { "MitchellNetravali", new MitchellNetravaliResampler() }, - - // { "Lanczos8", new Lanczos8Resampler() }, - // { "Hermite", new HermiteResampler() }, - // { "Spline", new SplineResampler() }, - // { "Robidoux", new RobidouxResampler() }, - // { "RobidouxSharp", new RobidouxSharpResampler() }, - // { "RobidouxSoft", new RobidouxSoftResampler() }, - // { "Welch", new WelchResampler() } - }; - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResize(string name, IResampler sampler) - { - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(image.Width / 2, image.Height / 2, sampler, true).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeFromSourceRectangle(string name, IResampler sampler) - { - name = $"{name}-SourceRect"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - Rectangle sourceRectangle = new Rectangle(image.Width / 8, image.Height / 8, image.Width / 4, image.Height / 4); - Rectangle destRectangle = new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2); - image.Resize(image.Width, image.Height, sampler, sourceRectangle, destRectangle, false).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWidthAndKeepAspect(string name, IResampler sampler) - { - name = $"{name}-FixedWidth"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(image.Width / 3, 0, sampler, false).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeHeightAndKeepAspect(string name, IResampler sampler) - { - name = $"{name}-FixedHeight"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Resize(0, image.Height / 3, sampler, false).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithCropWidthMode(string name, IResampler sampler) - { - name = $"{name}-CropWidth"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size(image.Width / 2, image.Height) - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithCropHeightMode(string name, IResampler sampler) - { - name = $"{name}-CropHeight"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size(image.Width, image.Height / 2) - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithPadMode(string name, IResampler sampler) - { - name = $"{name}-Pad"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Size = new Size(image.Width + 200, image.Height), - Mode = ResizeMode.Pad - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithBoxPadMode(string name, IResampler sampler) - { - name = $"{name}-BoxPad"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size(image.Width + 200, image.Height + 200), - Mode = ResizeMode.BoxPad - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithMaxMode(string name, IResampler sampler) - { - name = $"{name}Max"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size(300, 300), - Mode = ResizeMode.Max - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithMinMode(string name, IResampler sampler) - { - name = $"{name}-Min"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size((int)Math.Round(image.Width * .75F), (int)Math.Round(image.Height * .95F)), - Mode = ResizeMode.Min - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(ReSamplers))] - public void ImageShouldResizeWithStretchMode(string name, IResampler sampler) - { - name = $"{name}Stretch"; - - string path = this.CreateOutputDirectory("Resize"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(name); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - ResizeOptions options = new ResizeOptions - { - Sampler = sampler, - Size = new Size(image.Width / 2, image.Height), - Mode = ResizeMode.Stretch - }; - - image.Resize(options).Save(output); - } - } - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void BicubicWindowOscillatesCorrectly(float x, float expected) - { - BicubicResampler sampler = new BicubicResampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void TriangleWindowOscillatesCorrectly(float x, float expected) - { - TriangleResampler sampler = new TriangleResampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } - - [Theory] - [InlineData(-2, 0)] - [InlineData(-1, 0)] - [InlineData(0, 1)] - [InlineData(1, 0)] - [InlineData(2, 0)] - public static void Lanczos3WindowOscillatesCorrectly(float x, float expected) - { - Lanczos3Resampler sampler = new Lanczos3Resampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } - - [Theory] - [InlineData(-4, 0)] - [InlineData(-2, 0)] - [InlineData(0, 1)] - [InlineData(2, 0)] - [InlineData(4, 0)] - public static void Lanczos5WindowOscillatesCorrectly(float x, float expected) - { - Lanczos5Resampler sampler = new Lanczos5Resampler(); - float result = sampler.GetValue(x); - - Assert.Equal(result, expected); - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs deleted file mode 100644 index c4c4fa8d7..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateFlipTest.cs +++ /dev/null @@ -1,44 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Processing; - using Xunit; - - public class RotateFlipTest : FileTestBase - { - public static readonly TheoryData RotateFlipValues - = new TheoryData - { - { RotateType.None, FlipType.Vertical }, - { RotateType.None, FlipType.Horizontal }, - { RotateType.Rotate90, FlipType.None }, - { RotateType.Rotate180, FlipType.None }, - { RotateType.Rotate270, FlipType.None }, - }; - - [Theory] - [MemberData(nameof(RotateFlipValues))] - public void ImageShouldRotateFlip(RotateType rotateType, FlipType flipType) - { - string path = this.CreateOutputDirectory("RotateFlip"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(rotateType + "-" + flipType); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.RotateFlip(rotateType, flipType).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs b/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs deleted file mode 100644 index b30c795ad..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/RotateTest.cs +++ /dev/null @@ -1,67 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Processing; - using Xunit; - - public class RotateTest : FileTestBase - { - public static readonly TheoryData RotateFloatValues - = new TheoryData - { - 170 , - -170 , - }; - - public static readonly TheoryData RotateEnumValues - = new TheoryData - { - RotateType.None, - RotateType.Rotate90, - RotateType.Rotate180, - RotateType.Rotate270 - }; - - [Theory] - [MemberData(nameof(RotateFloatValues))] - public void ImageShouldApplyRotateSampler(float value) - { - string path = this.CreateOutputDirectory("Rotate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Rotate(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(RotateEnumValues))] - public void ImageShouldApplyRotateSampler(RotateType value) - { - string path = this.CreateOutputDirectory("Rotate"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Rotate(value).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs deleted file mode 100644 index e28847fa3..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/SaturationTest.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class SaturationTest : FileTestBase - { - public static readonly TheoryData SaturationValues - = new TheoryData - { - 50 , - -50 , - }; - - [Theory] - [MemberData(nameof(SaturationValues))] - public void ImageShouldApplySaturationFilter(int value) - { - string path = CreateOutputDirectory("Saturation"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Saturation(value).Save(output); - } - } - } - - [Theory] - [MemberData(nameof(SaturationValues))] - public void ImageShouldApplySaturationFilterInBox(int value) - { - string path = this.CreateOutputDirectory("Saturation"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName(value + "-InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Saturation(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs deleted file mode 100644 index 490213ce7..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/SepiaTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class SepiaTest : FileTestBase - { - [Fact] - public void ImageShouldApplySepiaFilter() - { - string path = CreateOutputDirectory("Sepia"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Sepia().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplySepiaFilterInBox() - { - string path = this.CreateOutputDirectory("Sepia"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Sepia(new Rectangle(10, 10, image.Width / 2, image.Height / 2)).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs b/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs deleted file mode 100644 index d096b3913..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/SkewTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class SkewTest : FileTestBase - { - public static readonly TheoryData SkewValues - = new TheoryData - { - { 20, 10 }, - { -20, -10 } - }; - - [Theory] - [MemberData(nameof(SkewValues))] - public void ImageShouldApplySkewSampler(float x, float y) - { - string path = this.CreateOutputDirectory("Skew"); - - // Matches live example - // http://www.w3schools.com/css/tryit.asp?filename=trycss3_transform_skew - foreach (TestFile file in Files) - { - string filename = file.GetFileName(x + "-" + y); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Skew(x, y).Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs b/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs deleted file mode 100644 index 4191ae8fa..000000000 --- a/tests/ImageSharp.Tests/Processors/Filters/VignetteTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - - using ImageSharp.PixelFormats; - - using Xunit; - - public class VignetteTest : FileTestBase - { - [Fact] - public void ImageShouldApplyVignetteFilter() - { - string path = this.CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.Vignette().Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterColor() - { - string path = this.CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Color"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(Rgba32.HotPink).Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterRadius() - { - string path = this.CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("Radius"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(image.Width / 4F, image.Height / 4F).Save(output); - } - } - } - - [Fact] - public void ImageShouldApplyVignetteFilterInBox() - { - string path = this.CreateOutputDirectory("Vignette"); - - foreach (TestFile file in Files) - { - string filename = file.GetFileName("InBox"); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) - { - image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) - .Save(output); - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 44c8c34ee..471d61abb 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -36,9 +36,20 @@ namespace ImageSharp.Tests // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; - // Odd chunk lengths - public const string ChunkLength1 = "Png/chunklength1.png"; - public const string ChunkLength2 = "Png/chunklength2.png"; + public static class Bad + { + // Odd chunk lengths + public const string ChunkLength1 = "Png/chunklength1.png"; + public const string ChunkLength2 = "Png/chunklength2.png"; + } + + public static readonly string[] All = + { + P1, Pd, Blur, Splash, Cross, + Powerpoint, SplashInterlaced, Interlaced, + Filter0, Filter1, Filter2, Filter3, Filter4, + FilterVar + }; } public static class Jpeg @@ -105,6 +116,8 @@ namespace ImageSharp.Tests public const string Giphy = "Gif/giphy.gif"; public const string Cheers = "Gif/cheers.gif"; public const string Trans = "Gif/trans.gif"; + + public static readonly string[] All = { Rings, Giphy, Cheers, Trans }; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 379ce3d05..05e8c6136 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -21,25 +21,40 @@ namespace ImageSharp.Tests protected readonly PixelTypes PixelTypes; + /// + /// Initializes a new instance of the class. + /// + /// + /// + /// protected ImageDataAttributeBase(string memberName, PixelTypes pixelTypes, object[] additionalParameters) { this.PixelTypes = pixelTypes; this.AdditionalParameters = additionalParameters; this.MemberName = memberName; - } - public string MemberName { get; private set; } + /// + /// Gets the member name + /// + public string MemberName { get; } + /// + /// Gets the member type + /// public Type MemberType { get; set; } + /// Returns the data to be used to test the theory. + /// The method that is being tested + /// One or more sets of theory data. Each invocation of the test method + /// is represented by a single object array. public override IEnumerable GetData(MethodInfo testMethod) { - IEnumerable addedRows = Enumerable.Empty(); + IEnumerable addedRows = Enumerable.Empty().ToArray(); if (!string.IsNullOrWhiteSpace(this.MemberName)) { Type type = this.MemberType ?? testMethod.DeclaringType; - Func accessor = GetPropertyAccessor(type) ?? GetFieldAccessor(type);// ?? GetMethodAccessor(type); + Func accessor = this.GetPropertyAccessor(type) ?? this.GetFieldAccessor(type); if (accessor != null) { @@ -49,7 +64,7 @@ namespace ImageSharp.Tests addedRows = memberItems.Select(x => x as object[]); if (addedRows.Any(x => x == null)) { - throw new ArgumentException($"Property {MemberName} on {MemberType ?? testMethod.DeclaringType} yielded an item that is not an object[]"); + throw new ArgumentException($"Property {this.MemberName} on {this.MemberType ?? testMethod.DeclaringType} yielded an item that is not an object[]"); } } } @@ -60,18 +75,20 @@ namespace ImageSharp.Tests addedRows = new[] { new object[0] }; } - bool firstIsprovider = FirstIsProvider(testMethod); - IEnumerable dataItems = Enumerable.Empty(); + bool firstIsprovider = this.FirstIsProvider(testMethod); if (firstIsprovider) { - return InnerGetData(testMethod, addedRows); - } - else - { - return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); + return this.InnerGetData(testMethod, addedRows); } + + return addedRows.Select(x => x.Concat(this.AdditionalParameters).ToArray()); } + /// + /// Returns a value indicating whether the first parameter of the method is a test provider. + /// + /// + /// private bool FirstIsProvider(MethodInfo testMethod) { TypeInfo dataType = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); @@ -106,25 +123,45 @@ namespace ImageSharp.Tests } } + /// + /// Generates the collection of method arguments from the given test as a generic enumerable. + /// + /// The test method + /// The test image provider factory type + /// The protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); return Enumerable.Repeat(args, 1); } + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The protected virtual object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { throw new InvalidOperationException("Semi-abstract method"); } + /// + /// Generates the method name from the given test method. + /// + /// The test method + /// The protected abstract string GetFactoryMethodName(MethodInfo testMethod); + /// + /// Gets the field accessor for the given type. + /// Func GetFieldAccessor(Type type) { FieldInfo fieldInfo = null; for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - fieldInfo = reflectionType.GetRuntimeField(MemberName); + fieldInfo = reflectionType.GetRuntimeField(this.MemberName); if (fieldInfo != null) break; } @@ -135,39 +172,27 @@ namespace ImageSharp.Tests return () => fieldInfo.GetValue(null); } - //Func GetMethodAccessor(Type type) - //{ - // MethodInfo methodInfo = null; - // var parameterTypes = Parameters == null ? new Type[0] : Parameters.Select(p => p?.GetType()).ToArray(); - // for (var reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) - // { - // methodInfo = reflectionType.GetRuntimeMethods() - // .FirstOrDefault(m => m.Name == MemberName && ParameterTypesCompatible(m.GetParameters(), parameterTypes)); - // if (methodInfo != null) - // break; - // } - - // if (methodInfo == null || !methodInfo.IsStatic) - // return null; - - // return () => methodInfo.Invoke(null, Parameters); - //} - + /// + /// Gets the property accessor for the given type. + /// Func GetPropertyAccessor(Type type) { PropertyInfo propInfo = null; for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - propInfo = reflectionType.GetRuntimeProperty(MemberName); + propInfo = reflectionType.GetRuntimeProperty(this.MemberName); if (propInfo != null) + { break; + } } - if (propInfo == null || propInfo.GetMethod == null || !propInfo.GetMethod.IsStatic) + if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + { return null; + } return () => propInfo.GetValue(null, null); } - } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs index 32278d755..72be5abc2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithBlankImageAttribute.cs @@ -31,6 +31,7 @@ namespace ImageSharp.Tests /// /// Triggers passing an that produces a blank image of size width * height /// + /// The member data /// The required width /// The required height /// The requested parameter diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs index df8f8d090..1b37c45a9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs @@ -16,55 +16,62 @@ namespace ImageSharp.Tests /// public class WithFileCollectionAttribute : ImageDataAttributeBase { - private readonly string enumeratorMemberName; + private readonly string fileEnumeratorMemberName; /// /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName /// instances will be passed for each the pixel format defined by the pixelTypes parameter /// - /// The name of the static test class field/property enumerating the files + /// The name of the static test class field/property enumerating the files /// The requested pixel types /// Additional theory parameter values public WithFileCollectionAttribute( - string enumeratorMemberName, + string fileEnumeratorMemberName, PixelTypes pixelTypes, params object[] additionalParameters) : base(null, pixelTypes, additionalParameters) { - this.enumeratorMemberName = enumeratorMemberName; + this.fileEnumeratorMemberName = fileEnumeratorMemberName; } - /// + /// /// Triggers passing instances which read an image for each file being enumerated by the (static) test class field/property defined by enumeratorMemberName /// instances will be passed for each the pixel format defined by the pixelTypes parameter /// /// The name of the static test class field/property enumerating the files + /// The member name for enumerating method parameters /// The requested pixel types /// Additional theory parameter values public WithFileCollectionAttribute( string enumeratorMemberName, - string DataMemberName, + string memberName, PixelTypes pixelTypes, params object[] additionalParameters) - : base(DataMemberName, pixelTypes, additionalParameters) + : base(memberName, pixelTypes, additionalParameters) { - this.enumeratorMemberName = enumeratorMemberName; + this.fileEnumeratorMemberName = enumeratorMemberName; } + /// + /// Generates the collection of method arguments from the given test. + /// + /// The test method + /// The test image provider factory type + /// The protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType); - accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType); - IEnumerable files = (IEnumerable)accessor(); + var files = (IEnumerable)accessor(); return files.Select(f => new object[] { f }); } + /// protected override string GetFactoryMethodName(MethodInfo testMethod) => "File"; /// - /// Based on MemberData implementation + /// Gets the field accessor for the given type. /// private Func GetFieldAccessor(Type type) { @@ -73,17 +80,23 @@ namespace ImageSharp.Tests reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - fieldInfo = reflectionType.GetRuntimeField(this.enumeratorMemberName); - if (fieldInfo != null) break; + fieldInfo = reflectionType.GetRuntimeField(this.fileEnumeratorMemberName); + if (fieldInfo != null) + { + break; + } } - if (fieldInfo == null || !fieldInfo.IsStatic) return null; + if (fieldInfo == null || !fieldInfo.IsStatic) + { + return null; + } return () => fieldInfo.GetValue(null); } /// - /// Based on MemberData implementation + /// Gets the property accessor for the given type. /// private Func GetPropertyAccessor(Type type) { @@ -92,11 +105,14 @@ namespace ImageSharp.Tests reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { - propInfo = reflectionType.GetRuntimeProperty(this.enumeratorMemberName); + propInfo = reflectionType.GetRuntimeProperty(this.fileEnumeratorMemberName); if (propInfo != null) break; } - if (propInfo == null || propInfo.GetMethod == null || !propInfo.GetMethod.IsStatic) return null; + if (propInfo?.GetMethod == null || !propInfo.GetMethod.IsStatic) + { + return null; + } return () => propInfo.GetValue(null, null); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs index 77c60a943..f6ab65f71 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs @@ -29,6 +29,7 @@ namespace ImageSharp.Tests /// /// Triggers passing an that produces a test pattern image of size width * height /// + /// The member data to apply to theories /// The required width /// The required height /// The requested parameter @@ -40,7 +41,14 @@ namespace ImageSharp.Tests this.Height = height; } + /// + /// Gets the width + /// public int Width { get; } + + /// + /// Gets the height + /// public int Height { get; } protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index 4217d52b0..16eecd2fc 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -17,8 +17,8 @@ namespace ImageSharp.Tests { private class FileProvider : TestImageProvider, IXunitSerializable { - // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider - // are shared between PixelTypes.Color & PixelTypes.StandardImageClass + // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider + // are shared between PixelTypes.Color & PixelTypes.Rgba32 private class Key : Tuple { public Key(PixelTypes item1, string item2) @@ -27,8 +27,7 @@ namespace ImageSharp.Tests } } - private static ConcurrentDictionary> cache = - new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> cache = new ConcurrentDictionary>(); private string filePath; diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index bf30d4847..03a685d34 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -107,12 +107,12 @@ namespace ImageSharp.Tests this.TypeName = typeName; this.MethodName = methodName; - if (pixelTypeOverride == PixelTypes.StandardImageClass) + if (pixelTypeOverride == PixelTypes.Rgba32) { this.Factory = new ImageFactory() as GenericFactory; } - this.Utility = new ImagingTestCaseUtility() + this.Utility = new ImagingTestCaseUtility { SourceFileOrDescription = this.SourceFileOrDescription, PixelTypeName = this.PixelType.ToString() diff --git a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs index 1dadc4884..1a6bf5d8b 100644 --- a/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs +++ b/tests/ImageSharp.Tests/TestUtilities/MeasureFixture.cs @@ -1,4 +1,9 @@ -namespace ImageSharp.Tests +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests { using System; using System.Diagnostics; @@ -24,8 +29,12 @@ /// The name of the operation to print to the output public void Measure(int times, Action action, [CallerMemberName] string operationName = null) { - if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} X {times} ..."); - Stopwatch sw = Stopwatch.StartNew(); + if (this.EnablePrinting) + { + this.Output?.WriteLine($"{operationName} X {times} ..."); + } + + var sw = Stopwatch.StartNew(); for (int i = 0; i < times; i++) { @@ -33,7 +42,10 @@ } sw.Stop(); - if (this.EnablePrinting) this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + if (this.EnablePrinting) + { + this.Output?.WriteLine($"{operationName} finished in {sw.ElapsedMilliseconds} ms"); + } } /// diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index f64b77271..5c676d07c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -52,11 +52,6 @@ namespace ImageSharp.Tests Short4 = 1 << 17, - /// - /// Triggers instantiating the subclass of - /// - StandardImageClass = 1 << 29, - // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index dbd316423..bb97daaa4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -1,35 +1,61 @@ - +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp.Tests { using System; using System.Collections.Generic; using System.Linq; using System.Reflection; - using System.Text; using ImageSharp.PixelFormats; public static class TestImageExtensions { - public static void DebugSave(this Image img, ITestImageProvider provider, object settings = null, string extension = "png") - where TPixel : struct, IPixel + /// + /// Saves the image only when not running in the CI server. + /// + /// The pixel format + /// The image + /// The image provider + /// The settings + /// The extension + public static Image DebugSave(this Image image, ITestImageProvider provider, object settings = null, string extension = "png") + where TPixel : struct, IPixel { + if (bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCi) && isCi) + { + return image; + } + + // We are running locally then we want to save it out string tag = null; - if (settings is string) + string s = settings as string; + + if (s != null) { - tag = (string)settings; + tag = s; } else if (settings != null) { - var properties = settings.GetType().GetRuntimeProperties(); + Type type = settings.GetType(); + TypeInfo info = type.GetTypeInfo(); + if (info.IsPrimitive || info.IsEnum || type == typeof(decimal)) + { + tag = settings.ToString(); + } + else + { + IEnumerable properties = settings.GetType().GetRuntimeProperties(); - tag = string.Join("_", properties.ToDictionary(x => x.Name, x => x.GetValue(settings)).Select(x => $"{x.Key}-{x.Value}")); - } - if(!bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCI) || !isCI) - { - // we are running locally then we want to save it out - provider.Utility.SaveTestOutputFile(img, extension, tag: tag); + tag = string.Join("_", properties.ToDictionary(x => x.Name, x => x.GetValue(settings)).Select(x => $"{x.Key}-{x.Value}")); + } } + + provider.Utility.SaveTestOutputFile(image, extension, tag: tag); + return image; } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs index dfaf1c052..e42cbe193 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -38,14 +38,13 @@ namespace ImageSharp.Tests // Add PixelFormat types string nameSpace = typeof(Alpha8).FullName; nameSpace = nameSpace.Substring(0, nameSpace.Length - typeof(Alpha8).Name.Length - 1); - foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.StandardImageClass && pt != PixelTypes.Rgba32)) + foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.Rgba32)) { string typeName = $"{nameSpace}.{pt}"; Type t = ImageSharpAssembly.GetType(typeName); PixelTypes2ClrTypes[pt] = t ?? throw new InvalidOperationException($"Could not find: {typeName}"); ClrTypes2PixelTypes[t] = pt; } - PixelTypes2ClrTypes[PixelTypes.StandardImageClass] = defaultPixelFormatType; } public static bool HasFlag(this PixelTypes pixelTypes, PixelTypes flag) => (pixelTypes & flag) == flag; @@ -106,6 +105,11 @@ namespace ImageSharp.Tests public static Type ToType(this PixelTypes pixelType) => PixelTypes2ClrTypes[pixelType]; + /// + /// Returns the enumerations for the given type. + /// + /// + /// public static PixelTypes GetPixelType(this Type colorStructClrType) => ClrTypes2PixelTypes[colorStructClrType]; public static IEnumerable> ExpandAllTypes(this PixelTypes pixelTypes) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 4d3a0d991..6bd5b392d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -50,7 +50,7 @@ namespace ImageSharp.Tests [Theory] [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] [WithBlankImages(1, 1, PixelTypes.Alpha8, PixelTypes.Alpha8)] - [WithBlankImages(1, 1, PixelTypes.StandardImageClass, PixelTypes.StandardImageClass)] + [WithBlankImages(1, 1, PixelTypes.Rgba32, PixelTypes.Rgba32)] public void PixelType_PropertyValueIsCorrect(TestImageProvider provider, PixelTypes expected) where TPixel : struct, IPixel { @@ -58,8 +58,8 @@ namespace ImageSharp.Tests } [Theory] - [WithBlankImages(1, 1, PixelTypes.StandardImageClass)] - [WithFile(TestImages.Bmp.F, PixelTypes.StandardImageClass)] + [WithBlankImages(1, 1, PixelTypes.Rgba32)] + [WithFile(TestImages.Bmp.F, PixelTypes.Rgba32)] public void PixelTypes_ColorWithDefaultImageClass_TriggersCreatingTheNonGenericDerivedImageClass( TestImageProvider provider) where TPixel : struct, IPixel @@ -144,7 +144,7 @@ namespace ImageSharp.Tests { Image img = provider.GetImage(); Assert.Equal(img.Width, 3); - if (provider.PixelType == PixelTypes.StandardImageClass) + if (provider.PixelType == PixelTypes.Rgba32) { Assert.IsType>(img); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 9ff0ca64e..a35c73aef 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -89,7 +89,7 @@ namespace ImageSharp.Tests [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] [InlineData(PixelTypes.Argb32, typeof(Argb32))] [InlineData(PixelTypes.HalfVector4, typeof(HalfVector4))] - [InlineData(PixelTypes.StandardImageClass, typeof(Rgba32))] + [InlineData(PixelTypes.Rgba32, typeof(Rgba32))] public void ToType(PixelTypes pt, Type expectedType) { Assert.Equal(pt.ToType(), expectedType); @@ -114,17 +114,16 @@ namespace ImageSharp.Tests [Fact] public void ToTypes() { - PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.Rgba32 | PixelTypes.HalfVector2 | PixelTypes.StandardImageClass; + PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.HalfVector2 | PixelTypes.Rgba32; IEnumerable> expanded = pixelTypes.ExpandAllTypes(); - Assert.Equal(expanded.Count(), 5); + Assert.Equal(expanded.Count(), 4); AssertContainsPixelType(PixelTypes.Alpha8, expanded); AssertContainsPixelType(PixelTypes.Bgr565, expanded); - AssertContainsPixelType(PixelTypes.Rgba32, expanded); AssertContainsPixelType(PixelTypes.HalfVector2, expanded); - AssertContainsPixelType(PixelTypes.StandardImageClass, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); } [Fact] @@ -134,7 +133,7 @@ namespace ImageSharp.Tests Assert.True(expanded.Length >= TestUtilityExtensions.GetAllPixelTypes().Length - 2); AssertContainsPixelType(PixelTypes.Rgba32, expanded); - AssertContainsPixelType(PixelTypes.StandardImageClass, expanded); + AssertContainsPixelType(PixelTypes.Rgba32, expanded); } } }