diff --git a/ImageSharp.sln b/ImageSharp.sln index 223d7c3c16..2802e7ba8a 100644 --- a/ImageSharp.sln +++ b/ImageSharp.sln @@ -1,7 +1,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26228.4 +VisualStudioVersion = 15.0.26228.9 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}" ProjectSection(SolutionItems) = preProject @@ -22,6 +22,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionIt EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{815C0625-CD3D-440F-9F80-2D83856AB7AE}" + ProjectSection(SolutionItems) = preProject + src\README.md = src\README.md + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}" EndProject diff --git a/README.md b/README.md index 967bccf8aa..6ba113e922 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,11 @@ [![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) [![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers) [![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) -[![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south) +[![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) +[![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) + | |Build Status|Code Coverage| @@ -74,7 +77,7 @@ Here's an example of the code required to resize an image using the default Bicu On platforms supporting netstandard 1.3+ ```csharp -using (Image image = new Image("foo.jpg")) +using (Image image = Image.Load("foo.jpg")) { image.Resize(image.Width / 2, image.Height / 2) .Grayscale() @@ -85,7 +88,7 @@ on netstandard 1.1 - 1.2 ```csharp using (FileStream stream = File.OpenRead("foo.jpg")) using (FileStream output = File.OpenWrite("bar.jpg")) -using (Image image = new Image(stream)) +using (Image image = Image.Load(stream)) { image.Resize(image.Width / 2, image.Height / 2) .Grayscale() @@ -93,12 +96,6 @@ using (Image image = new Image(stream)) } ``` -Individual processors can be initialised and apply processing against images. This allows nesting which brings the potential for powerful combinations of processing methods: - -```csharp -new BrightnessProcessor(50).Apply(sourceImage, sourceImage.Bounds); -``` - Setting individual pixel values is perfomed as follows: ```csharp @@ -132,3 +129,73 @@ Core Team - [Anton Firsov](https://github.com/antonfirsov) - [Olivia Ifrim](https://github.com/olivif) - [Scott Williams](https://github.com/tocsoft) + +### Backers + +Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/imagesharp#backer)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Sponsors + +Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/imagesharp#sponsor)] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/appveyor.yml b/appveyor.yml index c456a8d722..6b7ba946ec 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,9 @@ version: 1.0.0.{build} image: Visual Studio 2017 +# prevent the double build when a branch has an active PR +skip_branch_with_pr: true + init: - ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps')) @@ -17,7 +20,7 @@ deploy: # MyGet Deployment for builds & releases - provider: NuGet server: https://www.myget.org/F/imagesharp/api/v2/package - symbol_server: https://www.myget.org/F/imagesharp/api/v2/package # https://nuget.symbolsource.org/MyGet/imagesharp + symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf artifact: /.*\.nupkg/ diff --git a/build/Program.cs b/build/Program.cs index 6e04dc1df4..4e59b22145 100644 --- a/build/Program.cs +++ b/build/Program.cs @@ -63,21 +63,21 @@ namespace ConsoleApplication /// The arguments. public static void Main(string[] args) { - var resetmode = args.Contains("reset"); + bool resetmode = args.Contains("reset"); // Find the project root - var root = Path.GetFullPath(Path.Combine(LibGit2Sharp.Repository.Discover("."), "..")); + string root = Path.GetFullPath(Path.Combine(LibGit2Sharp.Repository.Discover("."), "..")); // Lets find the repo - var repo = new LibGit2Sharp.Repository(root); + Repository repo = new LibGit2Sharp.Repository(root); // Lets find all the project.json files in the src folder (don't care about versioning `tests`) - var projectFiles = Directory.EnumerateFiles(Path.Combine(root, "src"), "*.csproj", SearchOption.AllDirectories); + IEnumerable projectFiles = Directory.EnumerateFiles(Path.Combine(root, "src"), "*.csproj", SearchOption.AllDirectories); ResetProject(projectFiles); // Open them and convert them to source projects - var projects = projectFiles.Select(x => ProjectRootElement.Open(x, ProjectCollection.GlobalProjectCollection, true)) + List projects = projectFiles.Select(x => ProjectRootElement.Open(x, ProjectCollection.GlobalProjectCollection, true)) .Select(x => new SourceProject(x, repo.Info.WorkingDirectory)) .ToList(); @@ -89,7 +89,7 @@ namespace ConsoleApplication CreateBuildScript(projects, root); - foreach (var p in projects) + foreach (SourceProject p in projects) { Console.WriteLine($"{p.Name} {p.FinalVersionNumber}"); } @@ -98,10 +98,10 @@ namespace ConsoleApplication private static void CreateBuildScript(IEnumerable projects, string root) { - var outputDir = Path.GetFullPath(Path.Combine(root, @"artifacts\bin\ImageSharp")); + string outputDir = Path.GetFullPath(Path.Combine(root, @"artifacts\bin\ImageSharp")); - var sb = new StringBuilder(); - foreach (var p in projects) + StringBuilder sb = new StringBuilder(); + foreach (SourceProject p in projects) { sb.AppendLine($@"dotnet pack --configuration Release --output ""{outputDir}"" ""{p.ProjectFilePath}"""); } @@ -111,17 +111,17 @@ namespace ConsoleApplication private static void UpdateVersionNumbers(IEnumerable projects) { - foreach (var p in projects) + foreach (SourceProject p in projects) { // create a backup file so we can rollback later without breaking formatting File.Copy(p.FullProjectFilePath, $"{p.FullProjectFilePath}.bak", true); } - foreach (var p in projects) + foreach (SourceProject p in projects) { // TODO force update of all dependent projects to point to the newest build. // we skip the build number and standard CI prefix on first commits - var newVersion = p.FinalVersionNumber; + string newVersion = p.FinalVersionNumber; p.UpdateVersion(newVersion); } @@ -133,13 +133,13 @@ namespace ConsoleApplication string branch = repo.Head.FriendlyName; // lets see if we are running in appveyor and if we are use the environment variables instead of the head - var appveryorBranch = Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH"); + string appveryorBranch = Environment.GetEnvironmentVariable("APPVEYOR_REPO_BRANCH"); if (!string.IsNullOrWhiteSpace(appveryorBranch)) { branch = appveryorBranch; } - var prNumber = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER"); + string prNumber = Environment.GetEnvironmentVariable("APPVEYOR_PULL_REQUEST_NUMBER"); if (!string.IsNullOrWhiteSpace(prNumber)) { branch = $"PR{int.Parse(prNumber):000}"; @@ -159,7 +159,7 @@ namespace ConsoleApplication private static void CaclulateProjectVersionNumber(List projects, Repository repo) { - var branch = CurrentBranch(repo); + string branch = CurrentBranch(repo); // populate the dependency chains projects.ForEach(x => x.PopulateDependencies(projects)); @@ -176,7 +176,7 @@ namespace ConsoleApplication } // revert the project.json change be reverting it but skipp all the git stuff as its not needed - foreach (var p in projectPaths) + foreach (string p in projectPaths) { if (File.Exists($"{p}.bak")) { @@ -303,7 +303,7 @@ namespace ConsoleApplication /// The branch. internal void CalculateVersion(Repository repo, string branch) { - foreach (var c in repo.Commits) + foreach (Commit c in repo.Commits) { if (!this.ApplyCommit(c, repo)) { @@ -335,7 +335,7 @@ namespace ConsoleApplication this.CommitCountSinceVersionChange++; // return false if this is a version number root - var projectFileChange = changes.Where(x => x.Path?.Equals(this.ProjectFilePath, StringComparison.OrdinalIgnoreCase) == true).FirstOrDefault(); + TreeEntryChanges projectFileChange = changes.Where(x => x.Path?.Equals(this.ProjectFilePath, StringComparison.OrdinalIgnoreCase) == true).FirstOrDefault(); if (projectFileChange != null) { if (projectFileChange.Status == ChangeKind.Added) @@ -345,13 +345,13 @@ namespace ConsoleApplication } else { - var blob = repo.Lookup(projectFileChange.Oid); - using (var s = blob.GetContentStream()) + Blob blob = repo.Lookup(projectFileChange.Oid); + using (Stream s = blob.GetContentStream()) { - using (var reader = XmlReader.Create(s)) + using (XmlReader reader = XmlReader.Create(s)) { - var proj = ProjectRootElement.Create(reader); - var version = new NuGetVersion(proj.Properties.FirstOrDefault(x => x.Name == "VersionPrefix").Value); + ProjectRootElement proj = ProjectRootElement.Create(reader); + NuGetVersion version = new NuGetVersion(proj.Properties.FirstOrDefault(x => x.Name == "VersionPrefix").Value); if (version != this.Version) { // version changed @@ -370,9 +370,9 @@ namespace ConsoleApplication private bool ApplyCommit(Commit commit, Repository repo) { - foreach (var parent in commit.Parents) + foreach (Commit parent in commit.Parents) { - var changes = repo.Diff.Compare(parent.Tree, commit.Tree); + TreeChanges changes = repo.Diff.Compare(parent.Tree, commit.Tree); foreach (TreeEntryChanges change in changes) { @@ -399,7 +399,7 @@ namespace ConsoleApplication private string CalculateVersionNumber(string branch) { - var version = this.Version.ToFullString(); + string version = this.Version.ToFullString(); // master only if (this.CommitCountSinceVersionChange == 1 && branch == "master") @@ -414,12 +414,12 @@ namespace ConsoleApplication return version; } - var rootSpecialVersion = string.Empty; + string rootSpecialVersion = string.Empty; if (this.Version.IsPrerelease) { // probably a much easy way for doing this but it work sell enough for a build script - var parts = version.Split(new[] { '-' }, 2); + string[] parts = version.Split(new[] { '-' }, 2); version = parts[0]; rootSpecialVersion = parts[1]; } @@ -447,7 +447,7 @@ namespace ConsoleApplication branch = "-" + branch; } - var maxLength = 20; // dotnet will fail to populate the package if the tag is > 20 + int maxLength = 20; // dotnet will fail to populate the package if the tag is > 20 maxLength -= rootSpecialVersion.Length; // this is a required tag maxLength -= 7; // for the counter and dashes diff --git a/build/icons/imagesharp-logo-128.png b/build/icons/imagesharp-logo-128.png index 6d05c222cd..3fd4963dd3 100644 Binary files a/build/icons/imagesharp-logo-128.png and b/build/icons/imagesharp-logo-128.png differ diff --git a/build/icons/imagesharp-logo-256.png b/build/icons/imagesharp-logo-256.png index fc59b03e1a..d40880a63e 100644 Binary files a/build/icons/imagesharp-logo-256.png and b/build/icons/imagesharp-logo-256.png differ diff --git a/build/icons/imagesharp-logo-32.png b/build/icons/imagesharp-logo-32.png index 0fd4411ce4..8fcae249de 100644 Binary files a/build/icons/imagesharp-logo-32.png and b/build/icons/imagesharp-logo-32.png differ diff --git a/build/icons/imagesharp-logo-512.png b/build/icons/imagesharp-logo-512.png index 5d5fb854ea..fcde03ec48 100644 Binary files a/build/icons/imagesharp-logo-512.png and b/build/icons/imagesharp-logo-512.png differ diff --git a/build/icons/imagesharp-logo-64.png b/build/icons/imagesharp-logo-64.png index 4e97906e51..b43538a6fa 100644 Binary files a/build/icons/imagesharp-logo-64.png and b/build/icons/imagesharp-logo-64.png differ diff --git a/build/icons/imagesharp-logo-heading.png b/build/icons/imagesharp-logo-heading.png index b10d367bfd..35a945930a 100644 Binary files a/build/icons/imagesharp-logo-heading.png and b/build/icons/imagesharp-logo-heading.png differ diff --git a/build/icons/imagesharp-logo.png b/build/icons/imagesharp-logo.png index ed1c36c5ea..fcde03ec48 100644 Binary files a/build/icons/imagesharp-logo.png and b/build/icons/imagesharp-logo.png differ diff --git a/build/icons/imagesharp-logo.svg b/build/icons/imagesharp-logo.svg index 2df3cc80ce..cd4dfa117d 100644 --- a/build/icons/imagesharp-logo.svg +++ b/build/icons/imagesharp-logo.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs index d77a6d5941..6e092bf185 100644 --- a/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/Brushes{TColor}.cs @@ -18,10 +18,9 @@ namespace ImageSharp.Drawing.Brushes /// /// Percent10 Hatch Pattern /// - /// note 2d arrays when configured using initalizer look inverted - /// ---> Y axis + /// ---> x axis /// ^ - /// | X - axis + /// | y - axis /// | /// see PatternBrush for details about how to make new patterns work private static readonly bool[,] Percent10Pattern = @@ -37,10 +36,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] Percent20Pattern = { - { true, false, true, false }, - { false, false, false, false }, - { false, true, false, true }, - { false, false, false, false } + { true, false, false, false }, + { false, false, true, false }, + { true, false, false, false }, + { false, false, true, false } }; /// @@ -48,7 +47,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] HorizontalPattern = { - { false, true, false, false }, + { false }, + { true }, + { false }, + { false } }; /// @@ -56,7 +58,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] MinPattern = { - { false, false, false, true }, + { false }, + { false }, + { false }, + { true } }; /// @@ -64,10 +69,7 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] VerticalPattern = { - { false }, - { true }, - { false }, - { false } + { false, true, false, false }, }; /// @@ -75,10 +77,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] ForwardDiagonalPattern = { - { true, false, false, false }, - { false, true, false, false }, + { false, false, false, true }, { false, false, true, false }, - { false, false, false, true } + { false, true, false, false }, + { true, false, false, false } }; /// @@ -86,10 +88,10 @@ namespace ImageSharp.Drawing.Brushes /// private static readonly bool[,] BackwardDiagonalPattern = { - { false, false, false, true }, - { false, false, true, false }, + { true, false, false, false }, { false, true, false, false }, - { true, false, false, false } + { false, false, true, false }, + { false, false, false, true } }; /// diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs index 2707c00642..ace929bd6a 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TColor}.cs @@ -34,7 +34,7 @@ namespace ImageSharp.Drawing.Brushes /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new ImageBrushApplicator(this.image, region); + return new ImageBrushApplicator(sourcePixels, this.image, region); } /// @@ -71,12 +71,16 @@ namespace ImageSharp.Drawing.Brushes /// /// The region. /// - public ImageBrushApplicator(IImageBase image, RectangleF region) + /// + /// The sourcePixels. + /// + public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region) + : base(sourcePixels) { this.source = image.Lock(); this.xLength = image.Width; this.yLength = image.Height; - this.offset = new Vector2((float)Math.Max(Math.Floor(region.Top), 0), (float)Math.Max(Math.Floor(region.Left), 0)); + this.offset = new Vector2(MathF.Max(MathF.Floor(region.Top), 0), MathF.Max(MathF.Floor(region.Left), 0)); } /// @@ -87,18 +91,18 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { - var point = new Vector2(x, y); + Vector2 point = new Vector2(x, y); // Offset the requested pixel by the value in the rectangle (the shapes position) point = point - this.offset; - x = (int)point.X % this.xLength; - y = (int)point.Y % this.yLength; + int srcX = (int)point.X % this.xLength; + int srcY = (int)point.Y % this.yLength; - return this.source[x, y]; + return this.source[srcX, srcY]; } } @@ -107,6 +111,37 @@ namespace ImageSharp.Drawing.Brushes { this.source.Dispose(); } + + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + { + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + { + BufferSpan slice = buffer.Slice(offset); + + for (int xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = this[targetX, targetY].ToVector4(); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs index 741ab3f005..df492a764e 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TColor}.cs @@ -30,15 +30,6 @@ namespace ImageSharp.Drawing.Brushes /// 0 /// 0 /// - /// Warning when use array initializer across multiple lines the bools look inverted i.e. - /// new bool[,]{ - /// {true, false, false}, - /// {false,true, false} - /// } - /// would be - /// 10 - /// 01 - /// 00 /// /// The pixel format. public class PatternBrush : IBrush @@ -47,12 +38,19 @@ namespace ImageSharp.Drawing.Brushes /// /// The pattern. /// - private readonly TColor[][] pattern; + private readonly Fast2DArray pattern; + private readonly Fast2DArray patternVector; /// - /// The stride width. + /// Initializes a new instance of the class. /// - private readonly int stride; + /// Color of the fore. + /// Color of the back. + /// The pattern. + public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + : this(foreColor, backColor, new Fast2DArray(pattern)) + { + } /// /// Initializes a new instance of the class. @@ -60,26 +58,23 @@ namespace ImageSharp.Drawing.Brushes /// Color of the fore. /// Color of the back. /// The pattern. - public PatternBrush(TColor foreColor, TColor backColor, bool[,] pattern) + internal PatternBrush(TColor foreColor, TColor backColor, Fast2DArray pattern) { - this.stride = pattern.GetLength(1); - - // Convert the multidimension array into a jagged one. - int height = pattern.GetLength(0); - this.pattern = new TColor[height][]; - for (int x = 0; x < height; x++) + Vector4 foreColorVector = foreColor.ToVector4(); + Vector4 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++) { - this.pattern[x] = new TColor[this.stride]; - for (int y = 0; y < this.stride; y++) + if (pattern.Data[i]) { - if (pattern[x, y]) - { - this.pattern[x][y] = foreColor; - } - else - { - this.pattern[x][y] = backColor; - } + this.pattern.Data[i] = foreColor; + this.patternVector.Data[i] = foreColorVector; + } + else + { + this.pattern.Data[i] = backColor; + this.patternVector.Data[i] = backColorVector; } } } @@ -91,13 +86,13 @@ namespace ImageSharp.Drawing.Brushes internal PatternBrush(PatternBrush brush) { this.pattern = brush.pattern; - this.stride = brush.stride; + this.patternVector = brush.patternVector; } /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new PatternBrushApplicator(this.pattern, this.stride); + return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector); } /// @@ -105,31 +100,23 @@ namespace ImageSharp.Drawing.Brushes /// private class PatternBrushApplicator : BrushApplicator { - /// - /// The patter x-length. - /// - private readonly int xLength; - - /// - /// The stride width. - /// - private readonly int stride; - /// /// The pattern. /// - private readonly TColor[][] pattern; + private readonly Fast2DArray pattern; + private readonly Fast2DArray patternVector; /// /// Initializes a new instance of the class. /// + /// The sourcePixels. /// The pattern. - /// The stride. - public PatternBrushApplicator(TColor[][] pattern, int stride) + /// The patternVector. + public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) + : base(sourcePixels) { this.pattern = pattern; - this.xLength = pattern.Length; - this.stride = stride; + this.patternVector = patternVector; } /// @@ -140,14 +127,15 @@ namespace ImageSharp.Drawing.Brushes /// /// The Color. /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { - x = x % this.xLength; - y = y % this.stride; + x = x % this.pattern.Width; + y = y % this.pattern.Height; - return this.pattern[x][y]; + // 2d array index at row/column + return this.pattern[y, x]; } } @@ -156,6 +144,38 @@ namespace ImageSharp.Drawing.Brushes { // noop } + + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + { + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + { + BufferSpan slice = buffer.Slice(offset); + + for (int xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + // 2d array index at row/column + Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width]; + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index b66827e491..46444e5503 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Drawing.Processors { using System; using System.Numerics; + using System.Runtime.CompilerServices; /// /// primitive that converts a point in to a color for discovering the fill color based on an implementation @@ -16,15 +17,68 @@ namespace ImageSharp.Drawing.Processors public abstract class BrushApplicator : IDisposable // disposable will be required if/when there is an ImageBrush where TColor : struct, IPixel { + /// + /// Initializes a new instance of the class. + /// + /// The target. + internal BrushApplicator(PixelAccessor target) + { + this.Target = target; + } + + /// + /// Gets the destinaion + /// + protected PixelAccessor Target { get; } + /// /// Gets the color for a single pixel. /// /// The x cordinate. /// The y cordinate. /// The a that should be applied to the pixel. - public abstract TColor this[int x, int y] { get; } + internal abstract TColor this[int x, int y] { get; } /// public abstract void Dispose(); + + /// + /// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush. + /// + /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. + /// The number of pixels effected by this scanline. + /// The offset fromthe begining of the opacity data starts. + /// The x position in the target pixel space that the start of the scanline data corresponds to. + /// The y position in the target pixel space that whole scanline corresponds to. + /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. + internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + { + DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + { + BufferSpan slice = buffer.Slice(offset); + + for (int xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = this[targetX, targetY].ToVector4(); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs index 542c3cfed6..257eeb3ae5 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TColor}.cs @@ -65,11 +65,6 @@ namespace ImageSharp.Drawing.Brushes /// private class RecolorBrushApplicator : BrushApplicator { - /// - /// The source pixel accessor. - /// - private readonly PixelAccessor source; - /// /// The source color. /// @@ -93,8 +88,8 @@ namespace ImageSharp.Drawing.Brushes /// Color of the target. /// The threshold . public RecolorBrushApplicator(PixelAccessor sourcePixels, TColor sourceColor, TColor targetColor, float threshold) + : base(sourcePixels) { - this.source = sourcePixels; this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); @@ -114,17 +109,17 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] + internal override TColor this[int x, int y] { get { // Offset the requested pixel by the value in the rectangle (the shapes position) - TColor result = this.source[x, y]; + TColor result = this.Target[x, y]; Vector4 background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) { - var lerpAmount = (this.threshold - distance) / this.threshold; + float lerpAmount = (this.threshold - distance) / this.threshold; Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( background, this.targetColor, @@ -140,6 +135,46 @@ namespace ImageSharp.Drawing.Brushes public override void Dispose() { } + + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + { + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + { + BufferSpan slice = buffer.Slice(offset); + + for (int xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + + Vector4 sourceVector = backgroundVector; + float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor); + if (distance <= this.threshold) + { + float lerpAmount = (this.threshold - distance) / this.threshold; + sourceVector = Vector4BlendTransforms.PremultipliedLerp( + sourceVector, + this.targetColor, + lerpAmount); + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs index 30351dbe1b..125b07bcac 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TColor}.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Drawing.Brushes /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) { - return new SolidBrushApplicator(this.color); + return new SolidBrushApplicator(sourcePixels, this.color); } /// @@ -54,14 +54,18 @@ namespace ImageSharp.Drawing.Brushes /// The solid color. /// private readonly TColor color; + private readonly Vector4 colorVector; /// /// Initializes a new instance of the class. /// /// The color. - public SolidBrushApplicator(TColor color) + /// The sourcePixels. + public SolidBrushApplicator(PixelAccessor sourcePixels, TColor color) + : base(sourcePixels) { this.color = color; + this.colorVector = color.ToVector4(); } /// @@ -72,13 +76,43 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - public override TColor this[int x, int y] => this.color; + internal override TColor this[int x, int y] => this.color; /// public override void Dispose() { // noop } + + /// + internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + { + Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + + using (PinnedBuffer buffer = new PinnedBuffer(scanlineBuffer)) + { + BufferSpan slice = buffer.Slice(offset); + + for (int xPos = 0; xPos < scanlineWidth; xPos++) + { + int targetX = xPos + x; + int targetY = y; + + float opacity = slice[xPos]; + if (opacity > Constants.Epsilon) + { + Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + Vector4 sourceVector = this.colorVector; + + Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); + + TColor packed = default(TColor); + packed.PackFromVector4(finalColor); + this.Target[targetX, targetY] = packed; + } + } + } + } } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp.Drawing/GraphicsOptions.cs index 7e569c9a5f..a21617eadf 100644 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ b/src/ImageSharp.Drawing/GraphicsOptions.cs @@ -20,6 +20,11 @@ namespace ImageSharp.Drawing /// public bool Antialias; + /// + /// The number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth; + /// /// Initializes a new instance of the struct. /// @@ -27,6 +32,7 @@ namespace ImageSharp.Drawing public GraphicsOptions(bool enableAntialiasing) { this.Antialias = enableAntialiasing; + this.AntialiasSubpixelDepth = 16; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index cc1c526ae3..351764bb95 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -2,7 +2,7 @@ An extension to ImageSharp that allows the drawing of images, paths, and text. ImageSharp.Drawing - 1.0.0-alpha3 + 1.0.0-alpha5 James Jackson-South and contributors netstandard1.1 true @@ -40,7 +40,7 @@ All - + ..\..\ImageSharp.ruleset diff --git a/src/ImageSharp.Drawing/Paths/RectangleExtensions.cs b/src/ImageSharp.Drawing/Paths/RectangleExtensions.cs index 2fa5fe43f2..1b5df75742 100644 --- a/src/ImageSharp.Drawing/Paths/RectangleExtensions.cs +++ b/src/ImageSharp.Drawing/Paths/RectangleExtensions.cs @@ -19,10 +19,10 @@ namespace ImageSharp.Drawing /// A representation of this public static Rectangle Convert(this SixLabors.Shapes.Rectangle source) { - int left = (int)Math.Floor(source.Left); - int right = (int)Math.Ceiling(source.Right); - int top = (int)Math.Floor(source.Top); - int bottom = (int)Math.Ceiling(source.Bottom); + int left = (int)MathF.Floor(source.Left); + int right = (int)MathF.Ceiling(source.Right); + int top = (int)MathF.Floor(source.Top); + int bottom = (int)MathF.Ceiling(source.Bottom); return new Rectangle(left, top, right - left, bottom - top); } } diff --git a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs index b02c5c2e5b..0868945318 100644 --- a/src/ImageSharp.Drawing/Paths/ShapeRegion.cs +++ b/src/ImageSharp.Drawing/Paths/ShapeRegion.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Drawing { + using System; using System.Buffers; using System.Numerics; @@ -39,30 +40,7 @@ namespace ImageSharp.Drawing 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.Shape.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) + public override int Scan(float 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); diff --git a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs index 79a5d6b15a..e3716124e3 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TColor}.cs @@ -146,7 +146,7 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - var result = default(ColoredPointInfo); + ColoredPointInfo result = default(ColoredPointInfo); result.Color = this.brush[x, y]; if (info.DistanceFromPath < this.halfWidth) @@ -178,7 +178,7 @@ namespace ImageSharp.Drawing.Pens this.pattern = new float[pattern.Length + 1]; this.pattern[0] = 0; - for (var i = 0; i < pattern.Length; i++) + for (int i = 0; i < pattern.Length; i++) { this.totalLength += pattern[i] * width; this.pattern[i + 1] = this.totalLength; @@ -199,10 +199,10 @@ namespace ImageSharp.Drawing.Pens public override ColoredPointInfo GetColor(int x, int y, PointInfo info) { - var infoResult = default(ColoredPointInfo); + ColoredPointInfo infoResult = default(ColoredPointInfo); infoResult.DistanceFromElement = float.MaxValue; // is really outside the element - var length = info.DistanceAlongPath % this.totalLength; + 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 @@ -221,10 +221,10 @@ namespace ImageSharp.Drawing.Pens distanceWAway = info.DistanceFromPath - this.halfWidth; } - for (var i = 0; i < this.pattern.Length - 1; i++) + for (int i = 0; i < this.pattern.Length - 1; i++) { - var start = this.pattern[i]; - var end = this.pattern[i + 1]; + float start = this.pattern[i]; + float end = this.pattern[i + 1]; if (length >= start && length < end) { @@ -238,12 +238,12 @@ namespace ImageSharp.Drawing.Pens else { // this is a none solid part - var distanceFromStart = length - start; - var distanceFromEnd = end - length; + float distanceFromStart = length - start; + float distanceFromEnd = end - length; - var closestEdge = Math.Min(distanceFromStart, distanceFromEnd); + float closestEdge = MathF.Min(distanceFromStart, distanceFromEnd); - var distanceAcross = closestEdge; + float distanceAcross = closestEdge; if (distanceWAway > 0) { diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 1c1de45cbc..c67ba0370b 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Drawing.Processors /// Combines two images together by blending the pixels. /// /// The pixel format. - public class DrawImageProcessor : ImageProcessor + internal class DrawImageProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 95f4ab4726..89ba0968bb 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Drawing.Processors /// /// The type of the color. /// - public class DrawPathProcessor : ImageProcessor + internal class DrawPathProcessor : ImageProcessor where TColor : struct, IPixel { private const float AntialiasFactor = 1f; diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 9fa01075d4..635829e9f1 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -16,8 +16,8 @@ namespace ImageSharp.Drawing.Processors /// Using the bursh as a source of pixels colors blends the brush color with source. /// /// The pixel format. - public class FillProcessor : ImageProcessor - where TColor : struct, IPixel + internal class FillProcessor : ImageProcessor + where TColor : struct, IPixel { /// /// The brush. diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index 4f468c7070..80a3e67932 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -17,7 +17,7 @@ namespace ImageSharp.Drawing.Processors /// /// The type of the color. /// - public class FillRegionProcessor : ImageProcessor + internal class FillRegionProcessor : ImageProcessor where TColor : struct, IPixel { private const float AntialiasFactor = 1f; @@ -57,315 +57,136 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - Rectangle rect = this.Region.Bounds; - - int polyStartY = sourceRectangle.Y - DrawPadding; - int polyEndY = sourceRectangle.Bottom + DrawPadding; - int startX = sourceRectangle.X - DrawPadding; - int endX = sourceRectangle.Right + DrawPadding; - - int minX = Math.Max(sourceRectangle.Left, startX); - int maxX = Math.Min(sourceRectangle.Right - 1, endX); - int minY = Math.Max(sourceRectangle.Top, polyStartY); - int maxY = Math.Min(sourceRectangle.Bottom - 1, polyEndY); + Region region = this.Region; + Rectangle rect = region.Bounds; // 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); + int minX = Math.Max(0, rect.Left); + int maxX = Math.Min(source.Width, rect.Right); + int minY = Math.Max(0, rect.Top); + int maxY = Math.Min(source.Height, rect.Bottom); + if (minX >= maxX) + { + return; // no effect inside image; + } + + if (minY >= maxY) + { + return; // no effect inside image; + } ArrayPool arrayPool = ArrayPool.Shared; - int maxIntersections = this.Region.MaxIntersections; + int maxIntersections = region.MaxIntersections; + float subpixelCount = 4; + if (this.Options.Antialias) + { + subpixelCount = this.Options.AntialiasSubpixelDepth; + if (subpixelCount < 4) + { + subpixelCount = 4; + } + } using (PixelAccessor sourcePixels = source.Lock()) using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - (int y) => + float[] buffer = arrayPool.Rent(maxIntersections); + int scanlineWidth = maxX - minX; + float[] scanline = ArrayPool.Shared.Rent(scanlineWidth); + try { - float[] buffer = arrayPool.Rent(maxIntersections); - - try + bool scanlineDirty = true; + for (int y = minY; y < maxY; y++) { - float right = endX; - - // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.Region.ScanY(y, buffer, maxIntersections, 0); - if (pointsFound == 0) + if (scanlineDirty) { - // nothing on this line skip - return; - } - - QuickSort(buffer, pointsFound); - - int currentIntersection = 0; - float nextPoint = buffer[0]; - float lastPoint = float.MinValue; - bool isInside = false; - - for (int x = minX; x < maxX; x++) - { - if (!isInside) - { - if (x < (nextPoint - DrawPadding) && x > (lastPoint + DrawPadding)) - { - if (nextPoint == right) - { - // we are in the ends run skip it - x = maxX; - continue; - } - - // lets just jump forward - x = (int)Math.Floor(nextPoint) - DrawPadding; - } - } - - bool onCorner = false; - - // there seems to be some issue with this switch. - if (x >= nextPoint) + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) { - currentIntersection++; - lastPoint = nextPoint; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - - // double point from a corner flip the bit back and move on again - if (nextPoint == lastPoint) - { - onCorner = true; - isInside ^= true; - currentIntersection++; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - } - } - } - - isInside ^= true; + scanline[x] = 0; } - float opacity = 1; - if (!isInside && !onCorner) - { - if (this.Options.Antialias) - { - float distance = float.MaxValue; - if (x == lastPoint || x == nextPoint) - { - // we are to far away from the line - distance = 0; - } - else if (nextPoint - AntialiasFactor < x) - { - // we are near the left of the line - distance = nextPoint - x; - } - else if (lastPoint + AntialiasFactor > x) - { - // we are near the right of the line - distance = x - lastPoint; - } - else - { - // we are to far away from the line - continue; - } - opacity = 1 - (distance / AntialiasFactor); - } - else - { - continue; - } - } - - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator[x, y].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[x, y] = packed; - } + scanlineDirty = false; } - } - finally - { - arrayPool.Return(buffer); - } - }); - - if (this.Options.Antialias) - { - // we only need to do the X can for antialiasing purposes - Parallel.For( - minX, - maxX, - this.ParallelOptions, - (int x) => - { - float[] buffer = arrayPool.Rent(maxIntersections); - try + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - float left = polyStartY; - float right = polyEndY; - - // foreach line we get all the points where this line crosses the polygon - int pointsFound = this.Region.ScanX(x, buffer, maxIntersections, 0); + int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); if (pointsFound == 0) { - // nothign on this line skip - return; + // nothing on this line skip + continue; } QuickSort(buffer, pointsFound); - int currentIntersection = 0; - float nextPoint = buffer[0]; - float lastPoint = left; - bool isInside = false; - - for (int y = minY; y < maxY; y++) + for (int point = 0; point < pointsFound; point += 2) { - if (!isInside) + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart); + int endX = (int)MathF.Floor(scanEnd); + + if (startX >= 0 && startX < scanline.Length) { - if (y < (nextPoint - DrawPadding) && y > (lastPoint + DrawPadding)) + for (float x = scanStart; x < startX + 1; x += subpixelFraction) { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } - - // lets just jump forward - y = (int)Math.Floor(nextPoint) - DrawPadding; + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; } } - else + + if (endX >= 0 && endX < scanline.Length) { - if (y < nextPoint - DrawPadding) + for (float x = endX; x < scanEnd; x += subpixelFraction) { - if (nextPoint == right) - { - // we are in the ends run skip it - y = maxY; - continue; - } - - // lets just jump forward - y = (int)Math.Floor(nextPoint); + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; } } - bool onCorner = false; - - if (y >= nextPoint) + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + if (nextX >= 0) { - currentIntersection++; - lastPoint = nextPoint; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else + for (int x = nextX; x < endX; x++) { - nextPoint = buffer[currentIntersection]; - - // double point from a corner flip the bit back and move on again - if (nextPoint == lastPoint) - { - onCorner = true; - isInside ^= true; - currentIntersection++; - if (currentIntersection == pointsFound) - { - nextPoint = right; - } - else - { - nextPoint = buffer[currentIntersection]; - } - } + scanline[x] += subpixelFraction; + scanlineDirty = true; } - - isInside ^= true; } + } + } - float opacity = 1; - if (!isInside && !onCorner) + if (scanlineDirty) + { + if (!this.Options.Antialias) + { + for (int x = 0; x < scanlineWidth; x++) { - if (this.Options.Antialias) + if (scanline[x] > 0.5) { - float distance = float.MaxValue; - if (y == lastPoint || y == nextPoint) - { - // we are to far away from the line - distance = 0; - } - else if (nextPoint - AntialiasFactor < y) - { - // we are near the left of the line - distance = nextPoint - y; - } - else if (lastPoint + AntialiasFactor > y) - { - // we are near the right of the line - distance = y - lastPoint; - } - else - { - // we are to far away from the line - continue; - } - opacity = 1 - (distance / AntialiasFactor); + scanline[x] = 1; } else { - continue; + scanline[x] = 0; } } - - // don't set full opactiy color as it will have been gotten by the first scan - if (opacity > Constants.Epsilon && opacity < 1) - { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = applicator[x, y].ToVector4(); - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - finalColor.W = backgroundVector.W; - - TColor packed = default(TColor); - packed.PackFromVector4(finalColor); - sourcePixels[x, y] = packed; - } } + + applicator.Apply(scanline, scanlineWidth, 0, minX, y); } - finally - { - arrayPool.Return(buffer); - } - }); + } + } + finally + { + arrayPool.Return(buffer); + ArrayPool.Shared.Return(scanline); } } } diff --git a/src/ImageSharp.Drawing/Region.cs b/src/ImageSharp.Drawing/Region.cs index fe1dc52221..8cab885021 100644 --- a/src/ImageSharp.Drawing/Region.cs +++ b/src/ImageSharp.Drawing/Region.cs @@ -19,28 +19,18 @@ namespace ImageSharp.Drawing /// Gets the bounding box that entirely surrounds this region. /// /// - /// This should always contains all possible points returned from either or . + /// This should always contains all possible points returned from . /// public abstract Rectangle Bounds { get; } /// - /// Scans the X axis for intersections. - /// - /// The position along the X axis to find intersections. - /// 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. + /// Scans the X axis for intersections at the Y axis position. /// /// 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); + public abstract int Scan(float y, float[] buffer, int length, int offset); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Text/DrawText.cs b/src/ImageSharp.Drawing/Text/DrawText.cs index 28781fab22..1f5f4cdb12 100644 --- a/src/ImageSharp.Drawing/Text/DrawText.cs +++ b/src/ImageSharp.Drawing/Text/DrawText.cs @@ -18,6 +18,8 @@ namespace ImageSharp /// public static partial class ImageExtensions { + private static readonly Vector2 DefaultTextDpi = new Vector2(72); + /// /// Draws the text onto the the image filled via the brush. /// @@ -169,7 +171,12 @@ namespace ImageSharp TextRenderer renderer = new TextRenderer(glyphBuilder); - Vector2 dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + Vector2 dpi = DefaultTextDpi; + if (options.UseImageResolution) + { + dpi = new Vector2((float)source.MetaData.HorizontalResolution, (float)source.MetaData.VerticalResolution); + } + FontSpan style = new FontSpan(font) { ApplyKerning = options.ApplyKerning, diff --git a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs index e707ef5e50..b58a40b34d 100644 --- a/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs +++ b/src/ImageSharp.Drawing/Text/TextGraphicsOptions.cs @@ -20,6 +20,11 @@ namespace ImageSharp.Drawing /// public bool Antialias; + /// + /// The number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth; + /// /// Whether the text should be drawing with kerning enabled. /// @@ -30,6 +35,12 @@ namespace ImageSharp.Drawing /// public float TabWidth; + /// + /// Flag weather to use the current image resultion to for point size scaling. + /// If this is [false] the text renders at 72dpi otherwise it renders at Image resolution + /// + public bool UseImageResolution; + /// /// Initializes a new instance of the struct. /// @@ -39,6 +50,8 @@ namespace ImageSharp.Drawing this.Antialias = enableAntialiasing; this.ApplyKerning = true; this.TabWidth = 4; + this.AntialiasSubpixelDepth = 16; + this.UseImageResolution = false; } /// @@ -50,7 +63,10 @@ namespace ImageSharp.Drawing /// public static implicit operator TextGraphicsOptions(GraphicsOptions options) { - return new TextGraphicsOptions(options.Antialias); + return new TextGraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth + }; } /// @@ -62,7 +78,10 @@ namespace ImageSharp.Drawing /// public static explicit operator GraphicsOptions(TextGraphicsOptions options) { - return new GraphicsOptions(options.Antialias); + return new GraphicsOptions(options.Antialias) + { + AntialiasSubpixelDepth = options.AntialiasSubpixelDepth + }; } } } \ No newline at end of file diff --git a/src/ImageSharp/Colors/Color.BulkOperations.cs b/src/ImageSharp/Colors/Color.BulkOperations.cs index 5c040e04c5..7d3b12ea0b 100644 --- a/src/ImageSharp/Colors/Color.BulkOperations.cs +++ b/src/ImageSharp/Colors/Color.BulkOperations.cs @@ -24,8 +24,8 @@ namespace ImageSharp /// SIMD optimized bulk implementation of /// that works only with `count` divisible by . /// - /// The to the source colors. - /// The to the dstination vectors. + /// The to the source colors. + /// The to the dstination vectors. /// The number of pixels to convert. /// /// Implementation adapted from: @@ -38,10 +38,16 @@ namespace ImageSharp /// /// internal static unsafe void ToVector4SimdAligned( - BufferPointer sourceColors, - BufferPointer destVectors, + BufferSpan sourceColors, + BufferSpan destVectors, int count) { + if (!Vector.IsHardwareAccelerated) + { + throw new InvalidOperationException( + "Color.BulkOperations.ToVector4SimdAligned() should not be called when Vector.IsHardwareAccelerated == false!"); + } + int vecSize = Vector.Count; DebugGuard.IsTrue( @@ -85,14 +91,14 @@ namespace ImageSharp vf.CopyTo(fTemp, i); } - BufferPointer.Copy(tempBuf, (BufferPointer)destVectors, unpackedRawCount); + BufferSpan.Copy(tempBuf, (BufferSpan)destVectors, unpackedRawCount); } } /// - internal override void ToVector4(BufferPointer sourceColors, BufferPointer destVectors, int count) + internal override void ToVector4(BufferSpan sourceColors, BufferSpan destVectors, int count) { - if (count < 256) + if (count < 256 || !Vector.IsHardwareAccelerated) { // Doesn't worth to bother with SIMD: base.ToVector4(sourceColors, destVectors, count); @@ -117,7 +123,7 @@ namespace ImageSharp } /// - internal override unsafe void PackFromXyzBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromXyzBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -132,7 +138,7 @@ namespace ImageSharp } /// - internal override unsafe void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; @@ -149,19 +155,19 @@ namespace ImageSharp } /// - internal override void PackFromXyzwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override void PackFromXyzwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { - BufferPointer.Copy(sourceBytes, destColors, count); + BufferSpan.Copy(sourceBytes, destColors, count); } /// - internal override void ToXyzwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override void ToXyzwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { - BufferPointer.Copy(sourceColors, destBytes, count); + BufferSpan.Copy(sourceColors, destBytes, count); } /// - internal override unsafe void PackFromZyxBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromZyxBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -176,7 +182,7 @@ namespace ImageSharp } /// - internal override unsafe void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; @@ -193,7 +199,7 @@ namespace ImageSharp } /// - internal override unsafe void PackFromZyxwBytes(BufferPointer sourceBytes, BufferPointer destColors, int count) + internal override unsafe void PackFromZyxwBytes(BufferSpan sourceBytes, BufferSpan destColors, int count) { byte* source = (byte*)sourceBytes; byte* destination = (byte*)destColors; @@ -208,7 +214,7 @@ namespace ImageSharp } /// - internal override unsafe void ToZyxwBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal override unsafe void ToZyxwBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* source = (byte*)sourceColors; byte* destination = (byte*)destBytes; diff --git a/src/ImageSharp/Colors/ColorspaceTransforms.cs b/src/ImageSharp/Colors/ColorspaceTransforms.cs index cda7022705..74f5cb7175 100644 --- a/src/ImageSharp/Colors/ColorspaceTransforms.cs +++ b/src/ImageSharp/Colors/ColorspaceTransforms.cs @@ -105,12 +105,12 @@ namespace ImageSharp float s = color.S; float v = color.V; - if (Math.Abs(s) < Constants.Epsilon) + if (MathF.Abs(s) < Constants.Epsilon) { return new Color(v, v, v, 1); } - float h = (Math.Abs(color.H - 360) < Constants.Epsilon) ? 0 : color.H / 60; + float h = (MathF.Abs(color.H - 360) < Constants.Epsilon) ? 0 : color.H / 60; int i = (int)Math.Truncate(h); float f = h - i; @@ -178,9 +178,9 @@ namespace ImageSharp float s = color.S; float l = color.L; - if (Math.Abs(l) > Constants.Epsilon) + if (MathF.Abs(l) > Constants.Epsilon) { - if (Math.Abs(s) < Constants.Epsilon) + if (MathF.Abs(s) < Constants.Epsilon) { r = g = b = l; } diff --git a/src/ImageSharp/Colors/PackedPixel/Argb.cs b/src/ImageSharp/Colors/PackedPixel/Argb.cs index 70fd7de8a7..d03c098cdd 100644 --- a/src/ImageSharp/Colors/PackedPixel/Argb.cs +++ b/src/ImageSharp/Colors/PackedPixel/Argb.cs @@ -307,7 +307,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(float x, float y, float z, float w) { - var value = new Vector4(x, y, z, w); + Vector4 value = new Vector4(x, y, z, w); return Pack(ref value); } @@ -333,7 +333,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint Pack(ref Vector3 vector) { - var value = new Vector4(vector, 1); + Vector4 value = new Vector4(vector, 1); return Pack(ref value); } diff --git a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs index 77d9434785..2975d4ad90 100644 --- a/src/ImageSharp/Colors/PackedPixel/Bgr565.cs +++ b/src/ImageSharp/Colors/PackedPixel/Bgr565.cs @@ -110,9 +110,9 @@ namespace ImageSharp public void ToXyzBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); } /// @@ -120,10 +120,10 @@ namespace ImageSharp public void ToXyzwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -131,9 +131,9 @@ namespace ImageSharp public void ToZyxBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -141,10 +141,10 @@ namespace ImageSharp public void ToZyxwBytes(byte[] bytes, int startIndex) { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// diff --git a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs index 259b1c9b47..7b6169f9c6 100644 --- a/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs +++ b/src/ImageSharp/Colors/PackedPixel/BulkPixelOperations{TColor}.cs @@ -29,12 +29,12 @@ namespace ImageSharp /// /// Bulk version of /// - /// The to the source vectors. - /// The to the destination colors. + /// The to the source vectors. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromVector4( - BufferPointer sourceVectors, - BufferPointer destColors, + BufferSpan sourceVectors, + BufferSpan destColors, int count) { Vector4* sp = (Vector4*)sourceVectors.PointerAtOffset; @@ -55,12 +55,12 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination vectors. + /// The to the source colors. + /// The to the destination vectors. /// The number of pixels to convert. internal virtual void ToVector4( - BufferPointer sourceColors, - BufferPointer destVectors, + BufferSpan sourceColors, + BufferSpan destVectors, int count) { byte* sp = (byte*)sourceColors; @@ -78,12 +78,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -102,15 +102,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToXyzBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal virtual void ToXyzBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToXyzBytes(dest, i); @@ -121,12 +121,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromXyzwBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -145,18 +145,18 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToXyzwBytes( - BufferPointer sourceColors, - BufferPointer destBytes, + BufferSpan sourceColors, + BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToXyzwBytes(dest, i); @@ -167,12 +167,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -191,15 +191,15 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. - internal virtual void ToZyxBytes(BufferPointer sourceColors, BufferPointer destBytes, int count) + internal virtual void ToZyxBytes(BufferSpan sourceColors, BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 3); i += 3) + for (int i = destBytes.Start; i < destBytes.Start + (count * 3); i += 3) { TColor c = Unsafe.Read(sp); c.ToZyxBytes(dest, i); @@ -210,12 +210,12 @@ namespace ImageSharp /// /// Bulk version of that converts data in . /// - /// The to the source bytes. - /// The to the destination colors. + /// The to the source bytes. + /// The to the destination colors. /// The number of pixels to convert. internal virtual void PackFromZyxwBytes( - BufferPointer sourceBytes, - BufferPointer destColors, + BufferSpan sourceBytes, + BufferSpan destColors, int count) { byte* sp = (byte*)sourceBytes; @@ -234,18 +234,18 @@ namespace ImageSharp /// /// Bulk version of . /// - /// The to the source colors. - /// The to the destination bytes. + /// The to the source colors. + /// The to the destination bytes. /// The number of pixels to convert. internal virtual void ToZyxwBytes( - BufferPointer sourceColors, - BufferPointer destBytes, + BufferSpan sourceColors, + BufferSpan destBytes, int count) { byte* sp = (byte*)sourceColors; byte[] dest = destBytes.Array; - for (int i = destBytes.Offset; i < destBytes.Offset + (count * 4); i += 4) + for (int i = destBytes.Start; i < destBytes.Start + (count * 4); i += 4) { TColor c = Unsafe.Read(sp); c.ToZyxwBytes(dest, i); diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs index b34c1e88b7..46c24be6f9 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort2.cs @@ -127,8 +127,8 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); bytes[startIndex + 2] = 0; } @@ -143,8 +143,8 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); bytes[startIndex + 2] = 0; bytes[startIndex + 3] = 255; } @@ -161,8 +161,8 @@ namespace ImageSharp vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); bytes[startIndex] = 0; - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -177,8 +177,8 @@ namespace ImageSharp vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); bytes[startIndex] = 0; - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); bytes[startIndex + 3] = 255; } @@ -237,8 +237,8 @@ namespace ImageSharp // Clamp the value between min and max values // Round rather than truncate. - uint word2 = (uint)((int)(float)Math.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF); - uint word1 = (uint)(((int)(float)Math.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10); + uint word2 = (uint)((int)MathF.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF); + uint word1 = (uint)(((int)MathF.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10); return word2 | word1; } diff --git a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs index f33ac25a64..74229a914f 100644 --- a/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs +++ b/src/ImageSharp/Colors/PackedPixel/NormalizedShort4.cs @@ -135,9 +135,9 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); } /// @@ -151,10 +151,10 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -168,9 +168,9 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -184,10 +184,10 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -231,10 +231,10 @@ namespace ImageSharp const float MinNeg = -MaxPos; // Clamp the value between min and max values - ulong word4 = ((ulong)(float)Math.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x00; - ulong word3 = ((ulong)(float)Math.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10; - ulong word2 = ((ulong)(float)Math.Round(z * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x20; - ulong word1 = ((ulong)(float)Math.Round(w * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x30; + ulong word4 = ((ulong)MathF.Round(x * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x00; + ulong word3 = ((ulong)MathF.Round(y * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x10; + ulong word2 = ((ulong)MathF.Round(z * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x20; + ulong word1 = ((ulong)MathF.Round(w * MaxPos).Clamp(MinNeg, MaxPos) & 0xFFFF) << 0x30; return word4 | word3 | word2 | word1; } diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs index 56f3040703..65a5e7a5f6 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba1010102.cs @@ -109,9 +109,9 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); } /// @@ -120,10 +120,10 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -132,9 +132,9 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -143,10 +143,10 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs index 816401d4e0..becc4d072a 100644 --- a/src/ImageSharp/Colors/PackedPixel/Rgba64.cs +++ b/src/ImageSharp/Colors/PackedPixel/Rgba64.cs @@ -108,9 +108,9 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); } /// @@ -119,10 +119,10 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -131,9 +131,9 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -142,10 +142,10 @@ namespace ImageSharp { Vector4 vector = this.ToVector4() * 255F; - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// diff --git a/src/ImageSharp/Colors/PackedPixel/Short2.cs b/src/ImageSharp/Colors/PackedPixel/Short2.cs index 802df7c1d4..167a1e786f 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short2.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short2.cs @@ -125,8 +125,8 @@ namespace ImageSharp vector += Round; vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); bytes[startIndex + 2] = 0; } @@ -141,8 +141,8 @@ namespace ImageSharp vector += Round; vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); bytes[startIndex + 2] = 0; bytes[startIndex + 3] = 255; } @@ -159,8 +159,8 @@ namespace ImageSharp vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); bytes[startIndex] = 0; - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -175,8 +175,8 @@ namespace ImageSharp vector = Vector2.Clamp(vector, Vector2.Zero, MaxBytes); bytes[startIndex] = 0; - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); bytes[startIndex + 3] = 255; } diff --git a/src/ImageSharp/Colors/PackedPixel/Short4.cs b/src/ImageSharp/Colors/PackedPixel/Short4.cs index 2517ef7a84..e1a559c326 100644 --- a/src/ImageSharp/Colors/PackedPixel/Short4.cs +++ b/src/ImageSharp/Colors/PackedPixel/Short4.cs @@ -131,9 +131,9 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); } /// @@ -147,10 +147,10 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.X); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// @@ -164,9 +164,9 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); } /// @@ -180,10 +180,10 @@ namespace ImageSharp vector += Round; vector = Vector4.Clamp(vector, Vector4.Zero, MaxBytes); - bytes[startIndex] = (byte)(float)Math.Round(vector.Z); - bytes[startIndex + 1] = (byte)(float)Math.Round(vector.Y); - bytes[startIndex + 2] = (byte)(float)Math.Round(vector.X); - bytes[startIndex + 3] = (byte)(float)Math.Round(vector.W); + bytes[startIndex] = (byte)MathF.Round(vector.Z); + bytes[startIndex + 1] = (byte)MathF.Round(vector.Y); + bytes[startIndex + 2] = (byte)MathF.Round(vector.X); + bytes[startIndex + 3] = (byte)MathF.Round(vector.W); } /// diff --git a/src/ImageSharp/Colors/Spaces/CieLab.cs b/src/ImageSharp/Colors/Spaces/CieLab.cs index ecc1bca5ad..921158174c 100644 --- a/src/ImageSharp/Colors/Spaces/CieLab.cs +++ b/src/ImageSharp/Colors/Spaces/CieLab.cs @@ -95,11 +95,11 @@ namespace ImageSharp.Colors.Spaces // y /= 1F; z /= 1.08883F; - x = x > 0.008856F ? (float)Math.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F; - y = y > 0.008856F ? (float)Math.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F; - z = z > 0.008856F ? (float)Math.Pow(z, 0.3333333F) : ((903.3F * z) + 16F) / 116F; + x = x > 0.008856F ? MathF.Pow(x, 0.3333333F) : ((903.3F * x) + 16F) / 116F; + y = y > 0.008856F ? MathF.Pow(y, 0.3333333F) : ((903.3F * y) + 16F) / 116F; + z = z > 0.008856F ? MathF.Pow(z, 0.3333333F) : ((903.3F * z) + 16F) / 116F; - float l = Math.Max(0, (116F * y) - 16F); + float l = MathF.Max(0, (116F * y) - 16F); float a = 500F * (x - y); float b = 200F * (y - z); diff --git a/src/ImageSharp/Colors/Spaces/Cmyk.cs b/src/ImageSharp/Colors/Spaces/Cmyk.cs index 53618312c7..c81a55c0bb 100644 --- a/src/ImageSharp/Colors/Spaces/Cmyk.cs +++ b/src/ImageSharp/Colors/Spaces/Cmyk.cs @@ -93,9 +93,9 @@ namespace ImageSharp.Colors.Spaces float m = 1f - (color.G / 255F); float y = 1f - (color.B / 255F); - float k = Math.Min(c, Math.Min(m, y)); + float k = MathF.Min(c, MathF.Min(m, y)); - if (Math.Abs(k - 1.0f) <= Constants.Epsilon) + if (MathF.Abs(k - 1.0f) <= Constants.Epsilon) { return new Cmyk(0, 0, 0, 1); } diff --git a/src/ImageSharp/Colors/Spaces/Hsl.cs b/src/ImageSharp/Colors/Spaces/Hsl.cs index 66f4a52bcc..1d655ec326 100644 --- a/src/ImageSharp/Colors/Spaces/Hsl.cs +++ b/src/ImageSharp/Colors/Spaces/Hsl.cs @@ -83,27 +83,27 @@ namespace ImageSharp.Colors.Spaces float g = color.G / 255F; float b = color.B / 255F; - float max = Math.Max(r, Math.Max(g, b)); - float min = Math.Min(r, Math.Min(g, b)); + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); float chroma = max - min; float h = 0; float s = 0; float l = (max + min) / 2; - if (Math.Abs(chroma) < Constants.Epsilon) + if (MathF.Abs(chroma) < Constants.Epsilon) { return new Hsl(0, s, l); } - if (Math.Abs(r - max) < Constants.Epsilon) + if (MathF.Abs(r - max) < Constants.Epsilon) { h = (g - b) / chroma; } - else if (Math.Abs(g - max) < Constants.Epsilon) + else if (MathF.Abs(g - max) < Constants.Epsilon) { h = 2 + ((b - r) / chroma); } - else if (Math.Abs(b - max) < Constants.Epsilon) + else if (MathF.Abs(b - max) < Constants.Epsilon) { h = 4 + ((r - g) / chroma); } diff --git a/src/ImageSharp/Colors/Spaces/Hsv.cs b/src/ImageSharp/Colors/Spaces/Hsv.cs index b34977e2d9..e171c95282 100644 --- a/src/ImageSharp/Colors/Spaces/Hsv.cs +++ b/src/ImageSharp/Colors/Spaces/Hsv.cs @@ -83,27 +83,27 @@ namespace ImageSharp.Colors.Spaces float g = color.G / 255F; float b = color.B / 255F; - float max = Math.Max(r, Math.Max(g, b)); - float min = Math.Min(r, Math.Min(g, b)); + float max = MathF.Max(r, MathF.Max(g, b)); + float min = MathF.Min(r, MathF.Min(g, b)); float chroma = max - min; float h = 0; float s = 0; float v = max; - if (Math.Abs(chroma) < Constants.Epsilon) + if (MathF.Abs(chroma) < Constants.Epsilon) { return new Hsv(0, s, v); } - if (Math.Abs(r - max) < Constants.Epsilon) + if (MathF.Abs(r - max) < Constants.Epsilon) { h = (g - b) / chroma; } - else if (Math.Abs(g - max) < Constants.Epsilon) + else if (MathF.Abs(g - max) < Constants.Epsilon) { h = 2 + ((b - r) / chroma); } - else if (Math.Abs(b - max) < Constants.Epsilon) + else if (MathF.Abs(b - max) < Constants.Epsilon) { h = 4 + ((r - g) / chroma); } diff --git a/src/ImageSharp/Colors/Vector4BlendTransforms.cs b/src/ImageSharp/Colors/Vector4BlendTransforms.cs index 2fa6aad4b4..a7e2e0e919 100644 --- a/src/ImageSharp/Colors/Vector4BlendTransforms.cs +++ b/src/ImageSharp/Colors/Vector4BlendTransforms.cs @@ -5,7 +5,6 @@ namespace ImageSharp { - using System; using System.Numerics; /// @@ -198,13 +197,13 @@ namespace ImageSharp amount = amount.Clamp(0, 1); // Santize on zero alpha - if (Math.Abs(backdrop.W) < Constants.Epsilon) + if (MathF.Abs(backdrop.W) < Constants.Epsilon) { source.W *= amount; return source; } - if (Math.Abs(source.W) < Constants.Epsilon) + if (MathF.Abs(source.W) < Constants.Epsilon) { return backdrop; } @@ -248,7 +247,7 @@ namespace ImageSharp /// private static float BlendSoftLight(float b, float s) { - return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (float)((Math.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s))); + return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (MathF.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s)); } /// @@ -261,7 +260,7 @@ namespace ImageSharp /// private static float BlendDodge(float b, float s) { - return Math.Abs(s - 1F) < Constants.Epsilon ? s : Math.Min(b / (1F - s), 1F); + return MathF.Abs(s - 1F) < Constants.Epsilon ? s : MathF.Min(b / (1F - s), 1F); } /// @@ -274,7 +273,7 @@ namespace ImageSharp /// private static float BlendBurn(float b, float s) { - return Math.Abs(s) < Constants.Epsilon ? s : Math.Max(1F - ((1F - b) / s), 0F); + return MathF.Abs(s) < Constants.Epsilon ? s : MathF.Max(1F - ((1F - b) / s), 0F); } /// diff --git a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs index 23fce0173c..9f3aa405ed 100644 --- a/src/ImageSharp/Common/Extensions/Vector4Extensions.cs +++ b/src/ImageSharp/Common/Extensions/Vector4Extensions.cs @@ -57,7 +57,7 @@ namespace ImageSharp return signal * 12.92F; } - return (1.055F * (float)Math.Pow(signal, 0.41666666F)) - 0.055F; + return (1.055F * MathF.Pow(signal, 0.41666666F)) - 0.055F; } /// @@ -77,7 +77,7 @@ namespace ImageSharp return signal / 12.92F; } - return (float)Math.Pow((signal + 0.055F) / 1.055F, 2.4F); + return MathF.Pow((signal + 0.055F) / 1.055F, 2.4F); } } } diff --git a/src/ImageSharp/Common/Helpers/ImageMaths.cs b/src/ImageSharp/Common/Helpers/ImageMaths.cs index b59cf54f52..224b267e40 100644 --- a/src/ImageSharp/Common/Helpers/ImageMaths.cs +++ b/src/ImageSharp/Common/Helpers/ImageMaths.cs @@ -53,13 +53,13 @@ namespace ImageSharp public static float Gaussian(float x, float sigma) { const float Numerator = 1.0f; - float denominator = (float)(Math.Sqrt(2 * Math.PI) * sigma); + float denominator = MathF.Sqrt(2 * MathF.PI) * sigma; float exponentNumerator = -x * x; float exponentDenominator = (float)(2 * Math.Pow(sigma, 2)); float left = Numerator / denominator; - float right = (float)Math.Exp(exponentNumerator / exponentDenominator); + float right = MathF.Exp(exponentNumerator / exponentDenominator); return left * right; } @@ -110,10 +110,10 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float SinC(float x) { - if (Math.Abs(x) > Constants.Epsilon) + if (MathF.Abs(x) > Constants.Epsilon) { - x *= (float)Math.PI; - return Clean((float)Math.Sin(x) / x); + x *= MathF.PI; + return Clean(MathF.Sin(x) / x); } return 1.0f; @@ -129,7 +129,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DegreesToRadians(float degrees) { - return degrees * (float)(Math.PI / 180); + return degrees * (MathF.PI / 180); } /// @@ -196,19 +196,19 @@ namespace ImageSharp switch (channel) { case RgbaComponent.R: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().X - b) > Constants.Epsilon; break; case RgbaComponent.G: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon; + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Y - b) > Constants.Epsilon; break; case RgbaComponent.B: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon; + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().Z - b) > Constants.Epsilon; break; default: - delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon; + delegateFunc = (pixels, x, y, b) => MathF.Abs(pixels[x, y].ToVector4().W - b) > Constants.Epsilon; break; } @@ -297,7 +297,7 @@ namespace ImageSharp [MethodImpl(MethodImplOptions.AggressiveInlining)] private static float Clean(float x) { - if (Math.Abs(x) < Constants.Epsilon) + if (MathF.Abs(x) < Constants.Epsilon) { return 0F; } diff --git a/src/ImageSharp/Common/Helpers/MathF.cs b/src/ImageSharp/Common/Helpers/MathF.cs new file mode 100644 index 0000000000..2ee700789c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/MathF.cs @@ -0,0 +1,144 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Provides single-precision floating point constants and static methods for trigonometric, logarithmic, and other common mathematical functions. + /// + // ReSharper disable InconsistentNaming + internal static class MathF + { + /// + /// Represents the ratio of the circumference of a circle to its diameter, specified by the constant, π. + /// + public const float PI = (float)Math.PI; + + /// Returns the absolute value of a single-precision floating-point number. + /// A number that is greater than or equal to , but less than or equal to . + /// A single-precision floating-point number, x, such that 0 ≤ x ≤. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Abs(float f) + { + return Math.Abs(f); + } + + /// Returns the smallest integral value that is greater than or equal to the specified single-precision floating-point number. + /// A single-precision floating-point number. + /// The smallest integral value that is greater than or equal to . + /// If is equal to , , + /// or , that value is returned. + /// Note that this method returns a instead of an integral type. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Ceiling(float f) + { + return (float)Math.Ceiling(f); + } + + /// Returns e raised to the specified power. + /// A number specifying a power. + /// + /// The number e raised to the power . + /// If equals or , that value is returned. + /// If equals , 0 is returned. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Exp(float f) + { + return (float)Math.Exp(f); + } + + /// Returns the largest integer less than or equal to the specified single-precision floating-point number. + /// A single-precision floating-point number. + /// The largest integer less than or equal to . + /// If is equal to , , + /// or , that value is returned. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Floor(float f) + { + return (float)Math.Floor(f); + } + + /// Returns the larger of two single-precision floating-point numbers. + /// The first of two single-precision floating-point numbers to compare. + /// The second of two single-precision floating-point numbers to compare. + /// Parameter or , whichever is larger. + /// If , or , or both and are + /// equal to , is returned. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Max(float val1, float val2) + { + return Math.Max(val1, val2); + } + + /// Returns the smaller of two single-precision floating-point numbers. + /// The first of two single-precision floating-point numbers to compare. + /// The second of two single-precision floating-point numbers to compare. + /// Parameter or , whichever is smaller. + /// If , , or both and are equal + /// to , is returned. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Min(float val1, float val2) + { + return Math.Min(val1, val2); + } + + /// Returns a specified number raised to the specified power. + /// A single-precision floating-point number to be raised to a power. + /// A single-precision floating-point number that specifies a power. + /// The number raised to the power . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Pow(float x, float y) + { + return (float)Math.Pow(x, y); + } + + /// Rounds a single-precision floating-point value to the nearest integral value. + /// A single-precision floating-point number to be rounded. + /// + /// The integer nearest . + /// If the fractional component of is halfway between two integers, one of which is even and the other odd, then the even number is returned. + /// Note that this method returns a instead of an integral type. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Round(float f) + { + return (float)Math.Round(f); + } + + /// Returns the sine of the specified angle. + /// An angle, measured in radians. + /// + /// The sine of . + /// If is equal to , , + /// or , this method returns . + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sin(float f) + { + return (float)Math.Sin(f); + } + + /// Returns the square root of a specified number. + /// The number whose square root is to be found. + /// + /// One of the values in the following table. + /// parameter Return value Zero or positive The positive square root of . + /// Negative Equals + /// Equals + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static float Sqrt(float f) + { + return (float)Math.Sqrt(f); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs b/src/ImageSharp/Common/Memory/BufferPointer{T}.cs deleted file mode 100644 index 441f6b8ce0..0000000000 --- a/src/ImageSharp/Common/Memory/BufferPointer{T}.cs +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; - - /// - /// Provides access to elements in an array from a given position. - /// This type shares many similarities with corefx System.Span<T> but there are significant differences in it's functionalities and semantics: - /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays - /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like - /// - There is no bounds checking for performance reasons. Therefore we don't need to store length. (However this could be added as DEBUG-only feature.) - /// This makes an unsafe type! - /// - Currently the arrays provided to BufferPointer need to be pinned. This behaviour could be changed using C#7 features. - /// - /// The type of elements of the array - internal unsafe struct BufferPointer - where T : struct - { - /// - /// Initializes a new instance of the struct from a pinned array and an offset. - /// - /// The pinned array - /// Pointer to the beginning of array - /// The offset inside the array - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer(T[] array, void* pointerToArray, int offset) - { - DebugGuard.NotNull(array, nameof(array)); - - this.Array = array; - this.Offset = offset; - this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * offset); - } - - /// - /// Initializes a new instance of the struct from a pinned array. - /// - /// The pinned array - /// Pointer to the start of 'array' - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer(T[] array, void* pointerToArray) - { - DebugGuard.NotNull(array, nameof(array)); - - this.Array = array; - this.Offset = 0; - this.PointerAtOffset = (IntPtr)pointerToArray; - } - - /// - /// Gets the array - /// - public T[] Array { get; private set; } - - /// - /// Gets the offset inside - /// - public int Offset { get; private set; } - - /// - /// Gets the offset inside in bytes. - /// - public int ByteOffset => this.Offset * Unsafe.SizeOf(); - - /// - /// Gets the pointer to the offseted array position - /// - public IntPtr PointerAtOffset { get; private set; } - - /// - /// Convertes instance to a raw 'void*' pointer - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator void*(BufferPointer bufferPointer) - { - return (void*)bufferPointer.PointerAtOffset; - } - - /// - /// Converts instance to a raw 'byte*' pointer - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator byte*(BufferPointer bufferPointer) - { - return (byte*)bufferPointer.PointerAtOffset; - } - - /// - /// Converts instance to - /// setting it's and to correct values. - /// - /// The to convert - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator BufferPointer(BufferPointer source) - { - BufferPointer result = default(BufferPointer); - result.Array = Unsafe.As(source.Array); - result.Offset = source.Offset * Unsafe.SizeOf(); - result.PointerAtOffset = source.PointerAtOffset; - return result; - } - - /// - /// Forms a slice out of the given BufferPointer, beginning at 'offset'. - /// - /// The offset in number of elements - /// The offseted (sliced) BufferPointer - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public BufferPointer Slice(int offset) - { - BufferPointer result = default(BufferPointer); - result.Array = this.Array; - result.Offset = this.Offset + offset; - result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * offset); - return result; - } - - /// - /// Clears `count` elements beginning from the pointed position. - /// - /// The number of elements to clear - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Clear(int count) - { - if (count < 256) - { - Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferPointer.USizeOf(count)); - } - else - { - System.Array.Clear(this.Array, this.Offset, count); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/BufferPointer.cs b/src/ImageSharp/Common/Memory/BufferSpan.cs similarity index 81% rename from src/ImageSharp/Common/Memory/BufferPointer.cs rename to src/ImageSharp/Common/Memory/BufferSpan.cs index 523889611f..42a6fbc6be 100644 --- a/src/ImageSharp/Common/Memory/BufferPointer.cs +++ b/src/ImageSharp/Common/Memory/BufferSpan.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -11,9 +11,9 @@ namespace ImageSharp using System.Runtime.InteropServices; /// - /// Utility methods for + /// Utility methods for /// - internal static class BufferPointer + internal static class BufferSpan { /// /// It's worth to use Marshal.Copy() or Buffer.BlockCopy() over this size. @@ -24,11 +24,11 @@ namespace ImageSharp /// Copy 'count' number of elements of the same type from 'source' to 'dest' /// /// The element type. - /// The input - /// The destination . + /// The input + /// The destination . /// The number of elements to copy [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferPointer source, BufferPointer destination, int count) + public static void Copy(BufferSpan source, BufferSpan destination, int count) where T : struct { CopyImpl(source, destination, count); @@ -42,7 +42,7 @@ namespace ImageSharp /// The destination buffer. /// The number of elements to copy from 'source' [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Copy(BufferPointer source, BufferPointer destination, int countInSource) + public static void Copy(BufferSpan source, BufferSpan destination, int countInSource) where T : struct { CopyImpl(source, destination, countInSource); @@ -56,14 +56,14 @@ namespace ImageSharp /// The destination buffer"/> /// The number of elements to copy. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static unsafe void Copy(BufferPointer source, BufferPointer destination, int countInDest) + public static unsafe void Copy(BufferSpan source, BufferSpan destination, int countInDest) where T : struct { int byteCount = SizeOf(countInDest); if (byteCount > (int)ByteCountThreshold) { - Marshal.Copy(source.Array, source.Offset, destination.PointerAtOffset, byteCount); + Marshal.Copy(source.Array, source.Start, destination.PointerAtOffset, byteCount); } else { @@ -93,7 +93,7 @@ namespace ImageSharp => (uint)SizeOf(count); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void CopyImpl(BufferPointer source, BufferPointer destination, int count) + private static unsafe void CopyImpl(BufferSpan source, BufferSpan destination, int count) where T : struct where TDest : struct { @@ -103,22 +103,22 @@ namespace ImageSharp { if (Unsafe.SizeOf() == sizeof(long)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(int)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(short)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } else if (Unsafe.SizeOf() == sizeof(byte)) { - Marshal.Copy(Unsafe.As(source.Array), source.Offset, destination.PointerAtOffset, count); + Marshal.Copy(Unsafe.As(source.Array), source.Start, destination.PointerAtOffset, count); return; } } diff --git a/src/ImageSharp/Common/Memory/BufferSpan{T}.cs b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs new file mode 100644 index 0000000000..8ef88814cd --- /dev/null +++ b/src/ImageSharp/Common/Memory/BufferSpan{T}.cs @@ -0,0 +1,237 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + + /// + /// Represents a contiguous region of a pinned managed array. + /// The array is usually owned by a instance. + /// + /// + /// is very similar to corefx System.Span<T>, and we try to maintain a compatible API. + /// There are several differences though: + /// - It's not possible to use it with stack objects or pointers to unmanaged memory, only with managed arrays. + /// - It's possible to retrieve a reference to the array () so we can pass it to API-s like + /// - It's possible to retrieve the pinned pointer. This enables optimized (unchecked) unsafe operations. + /// - There is no bounds checking for performance reasons, only in debug mode. This makes an unsafe type! + /// + /// The type of elements of the array + internal unsafe struct BufferSpan + where T : struct + { + /// + /// Initializes a new instance of the struct from a pinned array and an start. + /// + /// The pinned array + /// Pointer to the beginning of the array + /// The index at which to begin the span. + /// The length + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray, int start, int length) + { + GuardArrayAndPointer(array, pointerToArray); + + DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); + DebugGuard.MustBeLessThanOrEqualTo(length, array.Length - start, nameof(length)); + + this.Array = array; + this.Length = length; + this.Start = start; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); + } + + /// + /// Initializes a new instance of the struct from a pinned array and an start. + /// + /// The pinned array + /// Pointer to the beginning of the array + /// The index at which to begin the span. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray, int start) + { + GuardArrayAndPointer(array, pointerToArray); + DebugGuard.MustBeLessThanOrEqualTo(start, array.Length, nameof(start)); + + this.Array = array; + this.Length = array.Length - start; + this.Start = start; + this.PointerAtOffset = (IntPtr)pointerToArray + (Unsafe.SizeOf() * start); + } + + /// + /// Initializes a new instance of the struct from a pinned array. + /// + /// The pinned array + /// Pointer to the start of 'array' + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan(T[] array, void* pointerToArray) + { + GuardArrayAndPointer(array, pointerToArray); + + this.Array = array; + this.Start = 0; + this.Length = array.Length; + this.PointerAtOffset = (IntPtr)pointerToArray; + } + + /// + /// Gets the backing array + /// + public T[] Array { get; private set; } + + /// + /// Gets the length of the + /// + public int Length { get; private set; } + + /// + /// Gets the start inside + /// + public int Start { get; private set; } + + /// + /// Gets the start inside in bytes. + /// + public int ByteOffset => this.Start * Unsafe.SizeOf(); + + /// + /// Gets the pointer to the offseted array position + /// + public IntPtr PointerAtOffset { get; private set; } + + /// + /// Returns a reference to specified element of the span. + /// + /// The index + /// The reference to the specified element + public ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + byte* ptr = (byte*)this.PointerAtOffset + BufferSpan.SizeOf(index); + return ref Unsafe.AsRef(ptr); + } + } + + /// + /// Convertes instance to a raw 'void*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator void*(BufferSpan bufferSpan) + { + return (void*)bufferSpan.PointerAtOffset; + } + + /// + /// Converts instance to a raw 'byte*' pointer + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator byte*(BufferSpan bufferSpan) + { + return (byte*)bufferSpan.PointerAtOffset; + } + + /// + /// Converts generic to a of bytes + /// setting it's and to correct values. + /// + /// The to convert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator BufferSpan(BufferSpan source) + { + BufferSpan result = default(BufferSpan); + result.Array = Unsafe.As(source.Array); + result.Start = source.Start * Unsafe.SizeOf(); + result.PointerAtOffset = source.PointerAtOffset; + return result; + } + + /// + /// Forms a slice out of the given BufferSpan, beginning at 'start'. + /// + /// TThe index at which to begin this slice. + /// The offseted (sliced) BufferSpan + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan Slice(int start) + { + DebugGuard.MustBeLessThan(start, this.Length, nameof(start)); + + BufferSpan result = default(BufferSpan); + result.Array = this.Array; + result.Start = this.Start + start; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); + result.Length = this.Length - start; + return result; + } + + /// + /// Forms a slice out of the given BufferSpan, beginning at 'start'. + /// + /// The index at which to begin this slice. + /// The desired length for the slice (exclusive). + /// The sliced BufferSpan + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public BufferSpan Slice(int start, int length) + { + DebugGuard.MustBeLessThanOrEqualTo(start, this.Length, nameof(start)); + DebugGuard.MustBeLessThanOrEqualTo(length, this.Length - start, nameof(length)); + + BufferSpan result = default(BufferSpan); + result.Array = this.Array; + result.Start = this.Start + start; + result.PointerAtOffset = this.PointerAtOffset + (Unsafe.SizeOf() * start); + result.Length = length; + return result; + } + + /// + /// Clears `count` elements from the beginning of the span. + /// + /// The number of elements to clear + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear(int count) + { + DebugGuard.MustBeLessThanOrEqualTo(count, this.Length, nameof(count)); + + if (count < 256) + { + Unsafe.InitBlock((void*)this.PointerAtOffset, 0, BufferSpan.USizeOf(count)); + } + else + { + System.Array.Clear(this.Array, this.Start, count); + } + } + + /// + /// Clears the the span + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Clear() + { + this.Clear(this.Length); + } + + [Conditional("DEBUG")] + private static void GuardArrayAndPointer(T[] array, void* pointerToArray) + { + DebugGuard.NotNull(array, nameof(array)); + DebugGuard.IsFalse( + pointerToArray == (void*)0, + nameof(pointerToArray), + "pointerToArray should not be null pointer!"); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs index 3455031fdb..401c83ce61 100644 --- a/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs +++ b/src/ImageSharp/Common/Memory/Fast2DArray{T}.cs @@ -13,7 +13,7 @@ namespace ImageSharp /// Provides fast access to 2D arrays. /// /// The type of elements in the array. - public struct Fast2DArray + internal struct Fast2DArray { /// /// The 1D representation of the 2D array. diff --git a/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs new file mode 100644 index 0000000000..374cbed997 --- /dev/null +++ b/src/ImageSharp/Common/Memory/IPinnedImageBuffer{T}.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + /// + /// An interface that represents a pinned buffer of value type objects + /// interpreted as a 2D region of x elements. + /// + /// The value type. + internal interface IPinnedImageBuffer + where T : struct + { + /// + /// Gets the width. + /// + int Width { get; } + + /// + /// Gets the height. + /// + int Height { get; } + + /// + /// Gets a to the backing buffer. + /// + BufferSpan Span { get; } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs index 2d3d44dda8..611688c995 100644 --- a/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs +++ b/src/ImageSharp/Common/Memory/PinnedBuffer{T}.cs @@ -11,7 +11,7 @@ namespace ImageSharp using System.Runtime.InteropServices; /// - /// Manages a pinned buffer of value type data 'T' as a Disposable resource. + /// Manages a pinned buffer of value type objects as a Disposable resource. /// The backing array is either pooled or comes from the outside. /// /// The value type. @@ -32,11 +32,11 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The desired count of elements. (Minimum size for ) - public PinnedBuffer(int count) + /// The desired count of elements. (Minimum size for ) + public PinnedBuffer(int length) { - this.Count = count; - this.Array = PixelDataPool.Rent(count); + this.Length = length; + this.Array = PixelDataPool.Rent(length); this.isPoolingOwner = true; this.Pin(); } @@ -47,7 +47,7 @@ namespace ImageSharp /// The array to pin. public PinnedBuffer(T[] array) { - this.Count = array.Length; + this.Length = array.Length; this.Array = array; this.isPoolingOwner = false; this.Pin(); @@ -56,16 +56,16 @@ namespace ImageSharp /// /// Initializes a new instance of the class. /// - /// The count of "relevant" elements in 'array'. /// The array to pin. - public PinnedBuffer(int count, T[] array) + /// The count of "relevant" elements in 'array'. + public PinnedBuffer(T[] array, int length) { - if (array.Length < count) + if (array.Length < length) { throw new ArgumentException("Can't initialize a PinnedBuffer with array.Length < count", nameof(array)); } - this.Count = count; + this.Length = length; this.Array = array; this.isPoolingOwner = false; this.Pin(); @@ -85,9 +85,9 @@ namespace ImageSharp public bool IsDisposedOrLostArrayOwnership { get; private set; } /// - /// Gets the count of "relevant" elements. Usually be smaller than 'Array.Length' when is pooled. + /// Gets the count of "relevant" elements. It's usually smaller than 'Array.Length' when is pooled. /// - public int Count { get; private set; } + public int Length { get; private set; } /// /// Gets the backing pinned array. @@ -100,34 +100,71 @@ namespace ImageSharp public IntPtr Pointer { get; private set; } /// - /// Converts to an . + /// Gets a to the backing buffer. + /// + public BufferSpan Span => this; + + /// + /// Returns a reference to specified element of the buffer. + /// + /// The index + /// The reference to the specified element + public unsafe ref T this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + DebugGuard.MustBeLessThan(index, this.Length, nameof(index)); + + byte* ptr = (byte*)this.Pointer + BufferSpan.SizeOf(index); + return ref Unsafe.AsRef(ptr); + } + } + + /// + /// Converts to an . /// /// The to convert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static implicit operator BufferPointer(PinnedBuffer buffer) + public static unsafe implicit operator BufferSpan(PinnedBuffer buffer) + { + return new BufferSpan(buffer.Array, (void*)buffer.Pointer, 0, buffer.Length); + } + + /// + /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// + /// The desired count of elements. (Minimum size for ) + /// The instance + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PinnedBuffer CreateClean(int count) { - return buffer.Slice(); + PinnedBuffer buffer = new PinnedBuffer(count); + buffer.Clear(); + return buffer; } /// - /// Gets a to the beginning of the raw data of the buffer. + /// Gets a to an offseted position inside the buffer. /// - /// The + /// The start + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice() + public unsafe BufferSpan Slice(int start) { - return new BufferPointer(this.Array, (void*)this.Pointer); + return new BufferSpan(this.Array, (void*)this.Pointer, start, this.Length - start); } /// - /// Gets a to an offseted position inside the buffer. + /// Gets a to an offseted position inside the buffer. /// - /// The offset - /// The + /// The start + /// The length of the slice + /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - public unsafe BufferPointer Slice(int offset) + public unsafe BufferSpan Slice(int start, int length) { - return new BufferPointer(this.Array, (void*)this.Pointer, offset); + return new BufferSpan(this.Array, (void*)this.Pointer, start, length); } /// @@ -151,7 +188,7 @@ namespace ImageSharp this.isPoolingOwner = false; this.Array = null; - this.Count = 0; + this.Length = 0; GC.SuppressFinalize(this); } @@ -178,12 +215,12 @@ namespace ImageSharp } /// - /// Clears the buffer, filling elements between 0 and -1 with default(T) + /// Clears the buffer, filling elements between 0 and -1 with default(T) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Clear() { - this.Slice().Clear(this.Count); + ((BufferSpan)this).Clear(); } /// diff --git a/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs b/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs new file mode 100644 index 0000000000..fcd5b3831e --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedImageBufferExtensions.cs @@ -0,0 +1,45 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Defines extension methods for . + /// + internal static class PinnedImageBufferExtensions + { + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The buffer + /// The x coordinate (position in the row) + /// The y (row) coordinate + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int x, int y) + where T : struct + { + return buffer.Span.Slice((y * buffer.Width) + x, buffer.Width - x); + } + + /// + /// Gets a to the row 'y' beginning from the pixel at 'x'. + /// + /// The buffer + /// The y (row) coordinate + /// The element type + /// The + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BufferSpan GetRowSpan(this IPinnedImageBuffer buffer, int y) + where T : struct + { + return buffer.Span.Slice(y * buffer.Width, buffer.Width); + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs new file mode 100644 index 0000000000..3ff174c5dd --- /dev/null +++ b/src/ImageSharp/Common/Memory/PinnedImageBuffer{T}.cs @@ -0,0 +1,78 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Runtime.CompilerServices; + + /// + /// Represents a pinned buffer of value type objects + /// interpreted as a 2D region of x elements. + /// + /// The value type. + internal class PinnedImageBuffer : PinnedBuffer, IPinnedImageBuffer + where T : struct + { + /// + /// Initializes a new instance of the class. + /// + /// The number of elements in a row + /// The number of rows + public PinnedImageBuffer(int width, int height) + : base(width * height) + { + this.Width = width; + this.Height = height; + } + + /// + /// Initializes a new instance of the class. + /// + /// The array to pin + /// The number of elements in a row + /// The number of rows + public PinnedImageBuffer(T[] array, int width, int height) + : base(array, width * height) + { + this.Width = width; + this.Height = height; + } + + /// + public int Width { get; } + + /// + public int Height { get; } + + /// + /// Gets a reference to the element at the specified position. + /// + /// The x coordinate (row) + /// The y coordinate (position at row) + /// A reference to the element. + public ref T this[int x, int y] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return ref this.Array[(this.Width * y) + x]; + } + } + + /// + /// Creates a clean instance of initializing it's elements with 'default(T)'. + /// + /// The number of elements in a row + /// The number of rows + /// The instance + public static PinnedImageBuffer CreateClean(int width, int height) + { + PinnedImageBuffer buffer = new PinnedImageBuffer(width, height); + buffer.Clear(); + return buffer; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs index dcd031f6e2..f5c7871409 100644 --- a/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs +++ b/src/ImageSharp/Common/Memory/PixelDataPool{T}.cs @@ -12,7 +12,7 @@ namespace ImageSharp /// Provides a resource pool that enables reusing instances of value type arrays for image data . /// /// The value type. - public class PixelDataPool + internal class PixelDataPool where T : struct { /// diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index e9120aa479..fa983d3557 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -12,6 +12,7 @@ namespace ImageSharp using System.Threading.Tasks; using Formats; + using ImageSharp.IO; /// /// Provides initialization code which allows extending the library. @@ -33,6 +34,25 @@ namespace ImageSharp /// private readonly List imageFormatsList = new List(); + /// + /// Initializes a new instance of the class. + /// + public Configuration() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The inital set of image formats. + public Configuration(params IImageFormat[] providers) + { + foreach (IImageFormat p in providers) + { + this.AddImageFormat(p); + } + } + /// /// Gets the default instance. /// @@ -53,6 +73,13 @@ namespace ImageSharp /// internal int MaxHeaderSize { get; private set; } +#if !NETSTANDARD1_1 + /// + /// Gets or sets the fielsystem helper for accessing the local file system. + /// + internal IFileSystem FileSystem { get; set; } = new LocalFileSystem(); +#endif + /// /// Adds a new to the collection of supported image formats. /// diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs index 20a45d4df6..cde146f1e5 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/ErrorDiffuser.cs @@ -5,12 +5,11 @@ namespace ImageSharp.Dithering { - using System; using System.Numerics; using System.Runtime.CompilerServices; /// - /// The base class for performing effor diffusion based dithering. + /// The base class for performing error diffusion based dithering. /// public abstract class ErrorDiffuser : IErrorDiffuser { @@ -34,19 +33,24 @@ namespace ImageSharp.Dithering /// private readonly int startingOffset; + /// + /// The diffusion matrix + /// + private readonly Fast2DArray matrix; + /// /// Initializes a new instance of the class. /// /// The dithering matrix. /// The divisor. - protected ErrorDiffuser(Fast2DArray matrix, byte divisor) + internal ErrorDiffuser(Fast2DArray matrix, byte divisor) { Guard.NotNull(matrix, nameof(matrix)); Guard.MustBeGreaterThan(divisor, 0, nameof(divisor)); - this.Matrix = matrix; - this.matrixWidth = this.Matrix.Width; - this.matrixHeight = this.Matrix.Height; + this.matrix = matrix; + this.matrixWidth = this.matrix.Width; + this.matrixHeight = this.matrix.Height; this.divisorVector = new Vector4(divisor); this.startingOffset = 0; @@ -62,9 +66,6 @@ namespace ImageSharp.Dithering } } - /// - public Fast2DArray Matrix { get; } - /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dither(PixelAccessor pixels, TColor source, TColor transformed, int x, int y, int width, int height) @@ -87,9 +88,9 @@ namespace ImageSharp.Dithering if (matrixX > 0 && matrixX < width && matrixY > 0 && matrixY < height) { - float coefficient = this.Matrix[row, col]; + float coefficient = this.matrix[row, col]; - // Good to disable here as we are not comparing matematical output. + // Good to disable here as we are not comparing mathematical output. // ReSharper disable once CompareOfFloatsByEqualityOperator if (coefficient == 0) { diff --git a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs index 4fb31c13e4..18079b1fb2 100644 --- a/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs +++ b/src/ImageSharp/Dithering/ErrorDiffusion/IErrorDiffuser.cs @@ -12,11 +12,6 @@ namespace ImageSharp.Dithering /// public interface IErrorDiffuser { - /// - /// Gets the dithering matrix - /// - Fast2DArray Matrix { get; } - /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// diff --git a/src/ImageSharp/Dithering/Ordered/Bayer.cs b/src/ImageSharp/Dithering/Ordered/Bayer.cs index 1027e51d94..3792c3c023 100644 --- a/src/ImageSharp/Dithering/Ordered/Bayer.cs +++ b/src/ImageSharp/Dithering/Ordered/Bayer.cs @@ -5,13 +5,11 @@ namespace ImageSharp.Dithering.Ordered { - using System; - /// /// Applies error diffusion based dithering using the 4x4 Bayer dithering matrix. /// /// - public class Bayer : IOrderedDither + public sealed class Bayer : OrderedDither4x4 { /// /// The threshold matrix. @@ -26,15 +24,12 @@ namespace ImageSharp.Dithering.Ordered { 255, 127, 223, 95 } }; - /// - public Fast2DArray Matrix { get; } = ThresholdMatrix; - - /// - public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) - where TColor : struct, IPixel + /// + /// Initializes a new instance of the class. + /// + public Bayer() + : base(ThresholdMatrix) { - source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs index 162cdb6a18..5c98973747 100644 --- a/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs +++ b/src/ImageSharp/Dithering/Ordered/IOrderedDither.cs @@ -5,18 +5,11 @@ namespace ImageSharp.Dithering { - using System; - /// /// Encapsulates properties and methods required to perfom ordered dithering on an image. /// public interface IOrderedDither { - /// - /// Gets the dithering matrix - /// - Fast2DArray Matrix { get; } - /// /// Transforms the image applying the dither matrix. This method alters the input pixels array /// @@ -34,4 +27,4 @@ namespace ImageSharp.Dithering void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) where TColor : struct, IPixel; } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/Ordered.cs b/src/ImageSharp/Dithering/Ordered/Ordered.cs index aabca31aad..ae75b87f21 100644 --- a/src/ImageSharp/Dithering/Ordered/Ordered.cs +++ b/src/ImageSharp/Dithering/Ordered/Ordered.cs @@ -5,13 +5,11 @@ namespace ImageSharp.Dithering.Ordered { - using System; - /// /// Applies error diffusion based dithering using the 4x4 ordered dithering matrix. /// /// - public class Ordered : IOrderedDither + public sealed class Ordered : OrderedDither4x4 { /// /// The threshold matrix. @@ -26,15 +24,12 @@ namespace ImageSharp.Dithering.Ordered { 240, 112, 208, 80 } }; - /// - public Fast2DArray Matrix { get; } = ThresholdMatrix; - - /// - public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) - where TColor : struct, IPixel + /// + /// Initializes a new instance of the class. + /// + public Ordered() + : base(ThresholdMatrix) { - source.ToXyzwBytes(bytes, 0); - pixels[x, y] = ThresholdMatrix[y % 3, x % 3] >= bytes[index] ? lower : upper; } } } \ No newline at end of file diff --git a/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs new file mode 100644 index 0000000000..c2b55d98eb --- /dev/null +++ b/src/ImageSharp/Dithering/Ordered/OrderedDither4x4.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Dithering.Ordered +{ + /// + /// The base class for performing ordered ditheroing using a 4x4 matrix. + /// + public abstract class OrderedDither4x4 : IOrderedDither + { + /// + /// The dithering matrix + /// + private Fast2DArray matrix; + + /// + /// Initializes a new instance of the class. + /// + /// The thresholding matrix. + internal OrderedDither4x4(Fast2DArray matrix) + { + this.matrix = matrix; + } + + /// + public void Dither(PixelAccessor pixels, TColor source, TColor upper, TColor lower, byte[] bytes, int index, int x, int y, int width, int height) + where TColor : 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 + // 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; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index 9f490a3a9b..da5d246372 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -26,13 +26,13 @@ namespace ImageSharp.Formats public class BmpDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel { - Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - new BmpDecoderCore().Decode(image, stream); + return new BmpDecoderCore(configuration).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index a75031ea19..18e4e858b8 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -43,21 +43,29 @@ namespace ImageSharp.Formats /// private BmpInfoHeader infoHeader; + private Configuration configuration; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public BmpDecoderCore(Configuration configuration) + { + this.configuration = configuration; + } + /// /// Decodes the image from the specified this._stream and sets /// the data to image. /// /// The pixel format. - /// The image, where the data should be set to. - /// Cannot be null (Nothing in Visual Basic). /// The stream, where the image should be /// decoded from. Cannot be null (Nothing in Visual Basic). /// - /// is null. - /// - or - /// is null. /// - public void Decode(Image image, Stream stream) + /// The decoded image. + public Image Decode(Stream stream) where TColor : struct, IPixel { this.currentStream = stream; @@ -110,15 +118,14 @@ namespace ImageSharp.Formats this.currentStream.Read(palette, 0, colorMapSize); } - if (this.infoHeader.Width > image.MaxWidth || this.infoHeader.Height > image.MaxHeight) + if (this.infoHeader.Width > Image.MaxWidth || this.infoHeader.Height > Image.MaxHeight) { throw new ArgumentOutOfRangeException( $"The input bitmap '{this.infoHeader.Width}x{this.infoHeader.Height}' is " - + $"bigger then the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + + $"bigger then the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'"); } - image.InitPixels(this.infoHeader.Width, this.infoHeader.Height); - + Image image = Image.Create(this.infoHeader.Width, this.infoHeader.Height, this.configuration); using (PixelAccessor pixels = image.Lock()) { switch (this.infoHeader.Compression) @@ -151,6 +158,8 @@ namespace ImageSharp.Formats throw new NotSupportedException("Does not support this kind of bitmap files."); } } + + return image; } catch (IndexOutOfRangeException e) { diff --git a/src/ImageSharp/Formats/Gif/GifDecoder.cs b/src/ImageSharp/Formats/Gif/GifDecoder.cs index b1e8ba928d..2eb89de8ff 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoder.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoder.cs @@ -14,25 +14,27 @@ namespace ImageSharp.Formats public class GifDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel { IGifDecoderOptions gifOptions = GifDecoderOptions.Create(options); - this.Decode(image, stream, gifOptions); + return this.Decode(configuration, stream, gifOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. + /// The configuration. /// The containing image data. /// The options for the decoder. - public void Decode(Image image, Stream stream, IGifDecoderOptions options) + /// The image thats been decoded. + public Image Decode(Configuration configuration, Stream stream, IGifDecoderOptions options) where TColor : struct, IPixel { - new GifDecoderCore(options).Decode(image, stream); + return new GifDecoderCore(options, configuration).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index ab1edc2c76..4c119ca737 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -28,9 +28,9 @@ namespace ImageSharp.Formats private readonly IGifDecoderOptions options; /// - /// The image to decode the information to. + /// The global configuration. /// - private Image decodedImage; + private readonly Configuration configuration; /// /// The currently loaded stream. @@ -67,25 +67,37 @@ namespace ImageSharp.Formats /// private GifGraphicsControlExtension graphicsControlExtension; + /// + /// The metadata + /// + private ImageMetaData metaData; + + /// + /// The image to decode the information to. + /// + private Image image; + /// /// Initializes a new instance of the class. /// /// The decoder options. - public GifDecoderCore(IGifDecoderOptions options) + /// The configuration. + public GifDecoderCore(IGifDecoderOptions options, Configuration configuration) { this.options = options ?? new GifDecoderOptions(); + this.configuration = configuration ?? Configuration.Default; } /// /// Decodes the stream to the image. /// - /// The image to decode to. /// The stream containing image data. - public void Decode(Image image, Stream stream) + /// The decoded image + public Image Decode(Stream stream) { try { - this.decodedImage = image; + this.metaData = new ImageMetaData(); this.currentStream = stream; @@ -144,6 +156,8 @@ namespace ImageSharp.Formats ArrayPool.Shared.Return(this.globalColorTable); } } + + return this.image; } /// @@ -212,11 +226,13 @@ namespace ImageSharp.Formats throw new ImageFormatException($"Invalid gif colormap size '{this.logicalScreenDescriptor.GlobalColorTableSize}'"); } - if (this.logicalScreenDescriptor.Width > this.decodedImage.MaxWidth || this.logicalScreenDescriptor.Height > this.decodedImage.MaxHeight) + /* // No point doing this as the max width/height is always int.Max and that always bigger than the max size of a gif which is stored in a short. + if (this.logicalScreenDescriptor.Width > Image.MaxWidth || this.logicalScreenDescriptor.Height > Image.MaxHeight) { throw new ArgumentOutOfRangeException( - $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{this.decodedImage.MaxWidth}x{this.decodedImage.MaxHeight}'"); + $"The input gif '{this.logicalScreenDescriptor.Width}x{this.logicalScreenDescriptor.Height}' is bigger then the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'"); } + */ } /// @@ -261,7 +277,7 @@ namespace ImageSharp.Formats { this.currentStream.Read(commentsBuffer, 0, length); string comments = this.options.TextEncoding.GetString(commentsBuffer, 0, length); - this.decodedImage.MetaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); + this.metaData.Properties.Add(new ImageProperty(GifConstants.Comments, comments)); } finally { @@ -343,14 +359,14 @@ namespace ImageSharp.Formats if (this.previousFrame == null) { - this.decodedImage.MetaData.Quality = colorTableLength / 3; + this.metaData.Quality = colorTableLength / 3; // This initializes the image to become fully transparent because the alpha channel is zero. - this.decodedImage.InitPixels(imageWidth, imageHeight); + this.image = Image.Create(imageWidth, imageHeight, this.metaData, this.configuration); - this.SetFrameDelay(this.decodedImage.MetaData); + this.SetFrameDelay(this.metaData); - image = this.decodedImage; + image = this.image; } else { @@ -368,7 +384,7 @@ namespace ImageSharp.Formats this.RestoreToBackground(image); - this.decodedImage.Frames.Add(currentFrame); + this.image.Frames.Add(currentFrame); } int i = 0; @@ -441,7 +457,7 @@ namespace ImageSharp.Formats return; } - this.previousFrame = currentFrame == null ? this.decodedImage.ToFrame() : currentFrame; + this.previousFrame = currentFrame == null ? this.image.ToFrame() : currentFrame; if (this.graphicsControlExtension != null && this.graphicsControlExtension.DisposalMethod == DisposalMethod.RestoreToBackground) @@ -462,8 +478,8 @@ namespace ImageSharp.Formats } // Optimization for when the size of the frame is the same as the image size. - if (this.restoreArea.Value.Width == this.decodedImage.Width && - this.restoreArea.Value.Height == this.decodedImage.Height) + if (this.restoreArea.Value.Width == this.image.Width && + this.restoreArea.Value.Height == this.image.Height) { using (PixelAccessor pixelAccessor = frame.Lock()) { diff --git a/src/ImageSharp/Formats/IImageDecoder.cs b/src/ImageSharp/Formats/IImageDecoder.cs index df98870ddd..c85fbef101 100644 --- a/src/ImageSharp/Formats/IImageDecoder.cs +++ b/src/ImageSharp/Formats/IImageDecoder.cs @@ -17,10 +17,11 @@ namespace ImageSharp.Formats /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. + /// The configuration for the image. /// The containing image data. /// The options for the decoder. - void Decode(Image image, Stream stream, IDecoderOptions options) + /// The decoded image + Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel; } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs index 85018a06f1..0acee3a10b 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockProcessor.cs @@ -71,8 +71,8 @@ namespace ImageSharp.Formats.Jpg DCT.TransformIDCT(ref *b, ref *this.pointers.Temp1, ref *this.pointers.Temp2); - var destChannel = decoder.GetDestinationChannel(this.componentIndex); - var destArea = destChannel.GetOffsetedSubAreaForBlock(decodedBlock.Bx, decodedBlock.By); + JpegPixelArea destChannel = decoder.GetDestinationChannel(this.componentIndex); + JpegPixelArea destArea = destChannel.GetOffsetedSubAreaForBlock(decodedBlock.Bx, decodedBlock.By); destArea.LoadColorsFrom(this.pointers.Temp1, this.pointers.Temp2); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs index 9fe6fecec9..728da8d02e 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegPixelArea.cs @@ -78,7 +78,7 @@ namespace ImageSharp.Formats.Jpg public static JpegPixelArea CreatePooled(int width, int height) { int size = width * height; - var pixels = BytePool.Rent(size); + byte[] pixels = BytePool.Rent(size); return new JpegPixelArea(pixels, width, 0); } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs index 10f859e420..c6362a8713 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegScanDecoder.cs @@ -305,7 +305,7 @@ namespace ImageSharp.Formats.Jpg /// The index of the scan private void DecodeBlock(JpegDecoderCore decoder, int scanIndex) { - var b = this.pointers.Block; + Block8x8F* b = this.pointers.Block; int huffmannIdx = (HuffmanTree.AcTableIndex * HuffmanTree.ThRowSize) + this.pointers.ComponentScan[scanIndex].AcTableSelector; if (this.ah != 0) { @@ -630,7 +630,7 @@ namespace ImageSharp.Formats.Jpg /// The private int RefineNonZeroes(ref InputProcessor bp, int zig, int nz, int delta) { - var b = this.pointers.Block; + Block8x8F* b = this.pointers.Block; for (; zig <= this.zigEnd; zig++) { int u = this.pointers.Unzig[zig]; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index eeb371d1e7..0aac316035 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -14,15 +14,14 @@ namespace ImageSharp.Formats public class JpegDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) where TColor : struct, IPixel { - Guard.NotNull(image, "image"); Guard.NotNull(stream, "stream"); - using (JpegDecoderCore decoder = new JpegDecoderCore(options)) + using (JpegDecoderCore decoder = new JpegDecoderCore(options, configuration)) { - decoder.Decode(image, stream, false); + return decoder.Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index f1b85fa0bf..33533aa12b 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -42,6 +42,11 @@ namespace ImageSharp.Formats /// private readonly IDecoderOptions options; + /// + /// The global configuration + /// + private readonly Configuration configuration; + /// /// The App14 marker color-space /// @@ -91,8 +96,10 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The decoder options. - public JpegDecoderCore(IDecoderOptions options) + /// The configuration. + public JpegDecoderCore(IDecoderOptions options, Configuration configuration) { + this.configuration = configuration ?? Configuration.Default; this.options = options ?? new DecoderOptions(); this.HuffmanTrees = HuffmanTree.CreateHuffmanTrees(); this.QuantizationTables = new Block8x8F[MaxTq + 1]; @@ -180,18 +187,17 @@ namespace ImageSharp.Formats /// the data to image. /// /// The pixel format. - /// The image, where the data should be set to. /// The stream, where the image should be. - /// Whether to decode metadata only. - public void Decode(Image image, Stream stream, bool metadataOnly) + /// The decoded image. + public Image Decode(Stream stream) where TColor : struct, IPixel { - this.ProcessStream(image, stream, metadataOnly); - if (!metadataOnly) - { - this.ProcessBlocksIntoJpegImageChannels(); - this.ConvertJpegPixelsToImagePixels(image); - } + ImageMetaData metadata = new ImageMetaData(); + this.ProcessStream(metadata, stream, false); + this.ProcessBlocksIntoJpegImageChannels(); + Image image = this.ConvertJpegPixelsToImagePixels(metadata); + + return image; } /// @@ -276,12 +282,10 @@ namespace ImageSharp.Formats /// /// Read metadata from stream and read the blocks in the scans into . /// - /// The pixel type - /// The + /// The metadata /// The stream /// Whether to decode metadata only. - private void ProcessStream(Image image, Stream stream, bool metadataOnly) - where TColor : struct, IPixel + private void ProcessStream(ImageMetaData metadata, Stream stream, bool metadataOnly) { this.InputStream = stream; this.InputProcessor = new InputProcessor(stream, this.Temp); @@ -429,7 +433,7 @@ namespace ImageSharp.Formats this.ProcessApplicationHeader(remaining); break; case JpegConstants.Markers.APP1: - this.ProcessApp1Marker(remaining, image); + this.ProcessApp1Marker(remaining, metadata); break; case JpegConstants.Markers.APP14: this.ProcessApp14Marker(remaining); @@ -496,13 +500,17 @@ namespace ImageSharp.Formats /// Convert the pixel data in and/or into pixels of /// /// The pixel type - /// The destination image - private void ConvertJpegPixelsToImagePixels(Image image) + /// The metadata for the image. + /// The decoded image. + private Image ConvertJpegPixelsToImagePixels(ImageMetaData metadata) where TColor : struct, IPixel { + Image image = Image.Create(this.ImageWidth, this.ImageHeight, metadata, this.configuration); + if (this.grayImage.IsInitialized) { - this.ConvertFromGrayScale(this.ImageWidth, this.ImageHeight, image); + this.ConvertFromGrayScale(image); + return image; } else if (this.ycbcrImage != null) { @@ -519,27 +527,27 @@ namespace ImageSharp.Formats // TODO: YCbCrA? if (this.adobeTransform == JpegConstants.Adobe.ColorTransformYcck) { - this.ConvertFromYcck(this.ImageWidth, this.ImageHeight, image); + this.ConvertFromYcck(image); } else if (this.adobeTransform == JpegConstants.Adobe.ColorTransformUnknown) { // Assume CMYK - this.ConvertFromCmyk(this.ImageWidth, this.ImageHeight, image); + this.ConvertFromCmyk(image); } - return; + return image; } if (this.ComponentCount == 3) { if (this.IsRGB()) { - this.ConvertFromRGB(this.ImageWidth, this.ImageHeight, image); - return; + this.ConvertFromRGB(image); + return image; } - this.ConvertFromYCbCr(this.ImageWidth, this.ImageHeight, image); - return; + this.ConvertFromYCbCr(image); + return image; } throw new ImageFormatException("JpegDecoder only supports RGB, CMYK and Grayscale color spaces."); @@ -582,28 +590,24 @@ namespace ImageSharp.Formats /// Converts the image from the original CMYK image pixels. /// /// The pixel format. - /// The image width. - /// The image height. /// The image. - private void ConvertFromCmyk(int width, int height, Image image) + private void ConvertFromCmyk(Image image) where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - image.InitPixels(width, height); - using (PixelAccessor pixels = image.Lock()) { Parallel.For( 0, - height, + image.Height, y => { // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); - for (int x = 0; x < width; x++) + for (int x = 0; x < image.Width; x++) { byte cyan = this.ycbcrImage.YChannel.Pixels[yo + x]; byte magenta = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; @@ -623,24 +627,20 @@ namespace ImageSharp.Formats /// Converts the image from the original grayscale image pixels. /// /// The pixel format. - /// The image width. - /// The image height. /// The image. - private void ConvertFromGrayScale(int width, int height, Image image) + private void ConvertFromGrayScale(Image image) where TColor : struct, IPixel { - image.InitPixels(width, height); - using (PixelAccessor pixels = image.Lock()) { Parallel.For( 0, - height, + image.Height, image.Configuration.ParallelOptions, y => { int yoff = this.grayImage.GetRowOffset(y); - for (int x = 0; x < width; x++) + for (int x = 0; x < image.Width; x++) { byte rgb = this.grayImage.Pixels[yoff + x]; @@ -658,20 +658,17 @@ namespace ImageSharp.Formats /// Converts the image from the original RBG image pixels. /// /// The pixel format. - /// The image width. - /// The height. /// The image. - private void ConvertFromRGB(int width, int height, Image image) + private void ConvertFromRGB(Image image) where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - image.InitPixels(width, height); using (PixelAccessor pixels = image.Lock()) { Parallel.For( 0, - height, + image.Height, image.Configuration.ParallelOptions, y => { @@ -679,7 +676,7 @@ namespace ImageSharp.Formats int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); - for (int x = 0; x < width; x++) + for (int x = 0; x < image.Width; x++) { byte red = this.ycbcrImage.YChannel.Pixels[yo + x]; byte green = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; @@ -699,20 +696,16 @@ namespace ImageSharp.Formats /// Converts the image from the original YCbCr image pixels. /// /// The pixel format. - /// The image width. - /// The image height. /// The image. - private void ConvertFromYCbCr(int width, int height, Image image) + private void ConvertFromYCbCr(Image image) where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - image.InitPixels(width, height); - using (PixelAccessor pixels = image.Lock()) { Parallel.For( 0, - height, + image.Height, image.Configuration.ParallelOptions, y => { @@ -720,7 +713,7 @@ namespace ImageSharp.Formats int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); - for (int x = 0; x < width; x++) + for (int x = 0; x < image.Width; x++) { byte yy = this.ycbcrImage.YChannel.Pixels[yo + x]; byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; @@ -740,28 +733,24 @@ namespace ImageSharp.Formats /// Converts the image from the original YCCK image pixels. /// /// The pixel format. - /// The image width. - /// The image height. /// The image. - private void ConvertFromYcck(int width, int height, Image image) + private void ConvertFromYcck(Image image) where TColor : struct, IPixel { int scale = this.ComponentArray[0].HorizontalFactor / this.ComponentArray[1].HorizontalFactor; - image.InitPixels(width, height); - using (PixelAccessor pixels = image.Lock()) { Parallel.For( 0, - height, + image.Height, y => { // TODO: Simplify + optimize + share duplicate code across converter methods int yo = this.ycbcrImage.GetRowYOffset(y); int co = this.ycbcrImage.GetRowCOffset(y); - for (int x = 0; x < width; x++) + for (int x = 0; x < image.Width; x++) { byte yy = this.ycbcrImage.YChannel.Pixels[yo + x]; byte cb = this.ycbcrImage.CbChannel.Pixels[co + (x / scale)]; @@ -959,11 +948,9 @@ namespace ImageSharp.Formats /// /// Processes the App1 marker retrieving any stored metadata /// - /// The pixel format. /// The remaining bytes in the segment block. - /// The image. - private void ProcessApp1Marker(int remaining, Image image) - where TColor : struct, IPixel + /// The image. + private void ProcessApp1Marker(int remaining, ImageMetaData metadata) { if (remaining < 6 || this.options.IgnoreMetadata) { @@ -978,7 +965,7 @@ namespace ImageSharp.Formats && profile[5] == '\0') { this.isExif = true; - image.MetaData.ExifProfile = new ExifProfile(profile); + metadata.ExifProfile = new ExifProfile(profile); } } diff --git a/src/ImageSharp/Formats/Png/PngDecoder.cs b/src/ImageSharp/Formats/Png/PngDecoder.cs index d527e1654d..d0a820c17f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoder.cs +++ b/src/ImageSharp/Formats/Png/PngDecoder.cs @@ -31,25 +31,27 @@ namespace ImageSharp.Formats public class PngDecoder : IImageDecoder { /// - public void Decode(Image image, Stream stream, IDecoderOptions options) + public Image Decode(Configuration configuration, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel { IPngDecoderOptions pngOptions = PngDecoderOptions.Create(options); - this.Decode(image, stream, pngOptions); + return this.Decode(configuration, stream, pngOptions); } /// /// Decodes the image from the specified stream to the . /// /// The pixel format. - /// The to decode to. + /// The configuration for the image. /// The containing image data. /// The options for the decoder. - public void Decode(Image image, Stream stream, IPngDecoderOptions options) + /// The decoded image. + public Image Decode(Configuration configuration, Stream stream, IPngDecoderOptions options) where TColor : struct, IPixel { - new PngDecoderCore(options).Decode(image, stream); + return new PngDecoderCore(options, configuration).Decode(stream); } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index fd03ed39b8..d6529940e2 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -74,6 +74,11 @@ namespace ImageSharp.Formats /// private readonly Crc32 crc = new Crc32(); + /// + /// The global configuration. + /// + private readonly Configuration configuration; + /// /// The stream to decode from. /// @@ -134,8 +139,10 @@ namespace ImageSharp.Formats /// Initializes a new instance of the class. /// /// The decoder options. - public PngDecoderCore(IPngDecoderOptions options) + /// The configuration. + public PngDecoderCore(IPngDecoderOptions options, Configuration configuration) { + this.configuration = configuration ?? Configuration.Default; this.options = options ?? new PngDecoderOptions(); } @@ -148,7 +155,6 @@ namespace ImageSharp.Formats /// Decodes the stream to the image. /// /// The pixel format. - /// The image to decode to. /// The stream containing image data. /// /// Thrown if the stream does not contain and end chunk. @@ -156,10 +162,11 @@ namespace ImageSharp.Formats /// /// Thrown if the image is larger than the maximum allowable size. /// - public void Decode(Image image, Stream stream) + /// The decoded image + public Image Decode(Stream stream) where TColor : struct, IPixel { - Image currentImage = image; + ImageMetaData metadata = new ImageMetaData(); this.currentStream = stream; this.currentStream.Skip(8); @@ -177,7 +184,7 @@ namespace ImageSharp.Formats this.ValidateHeader(); break; case PngChunkTypes.Physical: - this.ReadPhysicalChunk(currentImage, currentChunk.Data); + this.ReadPhysicalChunk(metadata, currentChunk.Data); break; case PngChunkTypes.Data: dataStream.Write(currentChunk.Data, 0, currentChunk.Length); @@ -186,7 +193,7 @@ namespace ImageSharp.Formats byte[] pal = new byte[currentChunk.Length]; Buffer.BlockCopy(currentChunk.Data, 0, pal, 0, currentChunk.Length); this.palette = pal; - image.MetaData.Quality = pal.Length / 3; + metadata.Quality = pal.Length / 3; break; case PngChunkTypes.PaletteAlpha: byte[] alpha = new byte[currentChunk.Length]; @@ -194,7 +201,7 @@ namespace ImageSharp.Formats this.paletteAlpha = alpha; break; case PngChunkTypes.Text: - this.ReadTextChunk(currentImage, currentChunk.Data, currentChunk.Length); + this.ReadTextChunk(metadata, currentChunk.Data, currentChunk.Length); break; case PngChunkTypes.End: this.isEndChunkReached = true; @@ -208,17 +215,19 @@ namespace ImageSharp.Formats } } - if (this.header.Width > image.MaxWidth || this.header.Height > image.MaxHeight) + if (this.header.Width > Image.MaxWidth || this.header.Height > Image.MaxHeight) { - throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{image.MaxWidth}x{image.MaxHeight}'"); + throw new ArgumentOutOfRangeException($"The input png '{this.header.Width}x{this.header.Height}' is bigger than the max allowed size '{Image.MaxWidth}x{Image.MaxHeight}'"); } - image.InitPixels(this.header.Width, this.header.Height); + Image image = Image.Create(this.header.Width, this.header.Height, metadata, this.configuration); using (PixelAccessor pixels = image.Lock()) { this.ReadScanlines(dataStream, pixels); } + + return image; } } @@ -270,18 +279,16 @@ namespace ImageSharp.Formats /// /// Reads the data chunk containing physical dimension data. /// - /// The pixel format. - /// The image to read to. + /// The metadata to read to. /// The data containing physical data. - private void ReadPhysicalChunk(Image image, byte[] data) - where TColor : struct, IPixel + private void ReadPhysicalChunk(ImageMetaData metadata, byte[] data) { data.ReverseBytes(0, 4); data.ReverseBytes(4, 4); // 39.3700787 = inches in a meter. - image.MetaData.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; - image.MetaData.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; + metadata.HorizontalResolution = BitConverter.ToInt32(data, 0) / 39.3700787d; + metadata.VerticalResolution = BitConverter.ToInt32(data, 4) / 39.3700787d; } /// @@ -768,12 +775,10 @@ namespace ImageSharp.Formats /// /// Reads a text chunk containing image properties from the data. /// - /// The pixel format. - /// The image to decode to. + /// The metadata to decode to. /// The containing data. /// The maximum length to read. - private void ReadTextChunk(Image image, byte[] data, int length) - where TColor : struct, IPixel + private void ReadTextChunk(ImageMetaData metadata, byte[] data, int length) { if (this.options.IgnoreMetadata) { @@ -794,7 +799,7 @@ namespace ImageSharp.Formats string name = this.options.TextEncoding.GetString(data, 0, zeroIndex); string value = this.options.TextEncoding.GetString(data, zeroIndex + 1, length - zeroIndex - 1); - image.MetaData.Properties.Add(new ImageProperty(name, value)); + metadata.Properties.Add(new ImageProperty(name, value)); } /// diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs index 7950d260c7..498ae578c3 100644 --- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs @@ -544,12 +544,11 @@ namespace ImageSharp.Formats /// /// The pixel format. /// The containing image data. - /// The image base. - private void WritePhysicalChunk(Stream stream, ImageBase imageBase) + /// The image. + private void WritePhysicalChunk(Stream stream, Image image) where TColor : struct, IPixel { - Image image = imageBase as Image; - if (image != null && image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) + if (image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0) { // 39.3700787 = inches in a meter. int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D); diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs index e32e808c1f..ec3b8ebe73 100644 --- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs +++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs @@ -13,11 +13,11 @@ namespace ImageSharp.Formats /// /// Non interlaced /// - None, + None = 0, /// /// Adam 7 interlacing. /// - Adam7 + Adam7 = 1 } } \ No newline at end of file diff --git a/src/ImageSharp/IO/BigEndianBitConverter.cs b/src/ImageSharp/IO/BigEndianBitConverter.cs index 0841027282..debe1706c9 100644 --- a/src/ImageSharp/IO/BigEndianBitConverter.cs +++ b/src/ImageSharp/IO/BigEndianBitConverter.cs @@ -6,43 +6,81 @@ namespace ImageSharp.IO { /// - /// Implementation of EndianBitConverter which converts to/from big-endian - /// byte arrays. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// + /// Implementation of EndianBitConverter which converts to/from big-endian byte arrays. /// internal sealed class BigEndianBitConverter : EndianBitConverter { /// - public override Endianness Endianness => Endianness.BigEndian; + public override Endianness Endianness + { + get { return Endianness.BigEndian; } + } /// - public override bool IsLittleEndian() => false; + public override bool IsLittleEndian + { + get { return false; } + } /// - protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + public override void CopyBytes(short value, byte[] buffer, int index) { - int endOffset = index + bytes - 1; - for (int i = 0; i < bytes; i++) - { - buffer[endOffset - i] = unchecked((byte)(value & 0xff)); - value = value >> 8; - } + CheckByteArgument(buffer, index, 2); + + buffer[index] = (byte)(value >> 8); + buffer[index + 1] = (byte)value; + } + + /// + public override void CopyBytes(int value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 4); + + buffer[index] = (byte)(value >> 24); + buffer[index + 1] = (byte)(value >> 16); + buffer[index + 2] = (byte)(value >> 8); + buffer[index + 3] = (byte)value; + } + + /// + public override void CopyBytes(long value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 8); + + buffer[index] = (byte)(value >> 56); + buffer[index + 1] = (byte)(value >> 48); + buffer[index + 2] = (byte)(value >> 40); + buffer[index + 3] = (byte)(value >> 32); + buffer[index + 4] = (byte)(value >> 24); + buffer[index + 5] = (byte)(value >> 16); + buffer[index + 6] = (byte)(value >> 8); + buffer[index + 7] = (byte)value; + } + + /// + public override short ToInt16(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 2); + + return (short)((value[startIndex] << 8) | value[startIndex + 1]); + } + + /// + public override int ToInt32(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 4); + + return (value[startIndex] << 24) | (value[startIndex + 1] << 16) | (value[startIndex + 2] << 8) | value[startIndex + 3]; } /// - protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + public override long ToInt64(byte[] value, int startIndex) { - long ret = 0; - for (int i = 0; i < bytesToConvert; i++) - { - ret = unchecked((ret << 8) | buffer[startIndex + i]); - } + CheckByteArgument(value, startIndex, 8); - return ret; + long p1 = (value[startIndex] << 24) | (value[startIndex + 1] << 16) | (value[startIndex + 2] << 8) | value[startIndex + 3]; + long p2 = (value[startIndex + 4] << 24) | (value[startIndex + 5] << 16) | (value[startIndex + 6] << 8) | value[startIndex + 7]; + return p2 | (p1 << 32); } } } \ No newline at end of file diff --git a/src/ImageSharp/IO/EndianBinaryReader.cs b/src/ImageSharp/IO/EndianBinaryReader.cs index d12d0b9761..e21d3d3db3 100644 --- a/src/ImageSharp/IO/EndianBinaryReader.cs +++ b/src/ImageSharp/IO/EndianBinaryReader.cs @@ -68,7 +68,7 @@ namespace ImageSharp.IO { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoding, nameof(encoding)); - Guard.IsTrue(stream.CanRead, nameof(stream), "Stream isn't readable."); + Guard.IsTrue(stream.CanRead, nameof(stream), "Stream isn't readable"); this.BaseStream = stream; this.BitConverter = EndianBitConverter.GetConverter(endianness); @@ -510,7 +510,7 @@ namespace ImageSharp.IO { if (this.disposed) { - throw new ObjectDisposedException("EndianBinaryReader"); + throw new ObjectDisposedException(nameof(EndianBinaryReader)); } } diff --git a/src/ImageSharp/IO/EndianBinaryWriter.cs b/src/ImageSharp/IO/EndianBinaryWriter.cs index 027c2b1f06..ef026f00c2 100644 --- a/src/ImageSharp/IO/EndianBinaryWriter.cs +++ b/src/ImageSharp/IO/EndianBinaryWriter.cs @@ -52,31 +52,12 @@ namespace ImageSharp.IO /// public EndianBinaryWriter(Endianness endianness, Stream stream, Encoding encoding) { - var bitConverter = EndianBitConverter.GetConverter(endianness); - - // TODO: Use Guard - if (bitConverter == null) - { - throw new ArgumentNullException("bitConverter"); - } - - if (stream == null) - { - throw new ArgumentNullException("stream"); - } - - if (encoding == null) - { - throw new ArgumentNullException("encoding"); - } - - if (!stream.CanWrite) - { - throw new ArgumentException("Stream isn't writable", "stream"); - } + Guard.NotNull(stream, nameof(stream)); + Guard.NotNull(stream, nameof(encoding)); + Guard.IsTrue(stream.CanWrite, nameof(stream), "Stream isn't writable"); this.BaseStream = stream; - this.BitConverter = bitConverter; + this.BitConverter = EndianBitConverter.GetConverter(endianness); this.Encoding = encoding; } @@ -256,13 +237,10 @@ namespace ImageSharp.IO /// Writes an array of bytes to the stream. /// /// The values to write + /// value is null public void Write(byte[] value) { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - + Guard.NotNull(value, nameof(value)); this.WriteInternal(value, value.Length); } @@ -272,6 +250,7 @@ namespace ImageSharp.IO /// An array containing the bytes to write /// The index of the first byte to write within the array /// The number of bytes to write + /// value is null public void Write(byte[] value, int offset, int count) { this.CheckDisposed(); @@ -292,12 +271,10 @@ namespace ImageSharp.IO /// Writes an array of characters to the stream, using the encoding for this writer. /// /// An array containing the characters to write + /// value is null public void Write(char[] value) { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } + Guard.NotNull(value, nameof(value)); this.CheckDisposed(); byte[] data = this.Encoding.GetBytes(value, 0, value.Length); @@ -305,16 +282,13 @@ namespace ImageSharp.IO } /// - /// Writes a string to the stream, using the encoding for this writer. + /// Writes a length-prefixed string to the stream, using the encoding for this writer. /// /// The value to write. Must not be null. - /// value is null + /// value is null public void Write(string value) { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } + Guard.NotNull(value, nameof(value)); this.CheckDisposed(); byte[] data = this.Encoding.GetBytes(value); @@ -368,7 +342,7 @@ namespace ImageSharp.IO { if (this.disposed) { - throw new ObjectDisposedException("EndianBinaryWriter"); + throw new ObjectDisposedException(nameof(EndianBinaryWriter)); } } diff --git a/src/ImageSharp/IO/EndianBitConverter.Conversion.cs b/src/ImageSharp/IO/EndianBitConverter.Conversion.cs new file mode 100644 index 0000000000..0858acfedc --- /dev/null +++ b/src/ImageSharp/IO/EndianBitConverter.Conversion.cs @@ -0,0 +1,63 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + + /// + /// Equivalent of , but with either endianness. + /// + internal abstract partial class EndianBitConverter + { + /// + /// Converts the specified double-precision floating point number to a + /// 64-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 64-bit signed integer whose value is equivalent to value. + public unsafe long DoubleToInt64Bits(double value) + { + return *((long*)&value); + } + + /// + /// Converts the specified 64-bit signed integer to a double-precision + /// floating point number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A double-precision floating point number whose value is equivalent to value. + public unsafe double Int64BitsToDouble(long value) + { + return *((double*)&value); + } + + /// + /// Converts the specified single-precision floating point number to a + /// 32-bit signed integer. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A 32-bit signed integer whose value is equivalent to value. + public unsafe int SingleToInt32Bits(float value) + { + return *((int*)&value); + } + + /// + /// Converts the specified 32-bit signed integer to a single-precision floating point + /// number. Note: the endianness of this converter does not + /// affect the returned value. + /// + /// The number to convert. + /// A single-precision floating point number whose value is equivalent to value. + public unsafe float Int32BitsToSingle(int value) + { + return *((float*)&value); + } + } +} diff --git a/src/ImageSharp/IO/EndianBitConverter.CopyBytes.cs b/src/ImageSharp/IO/EndianBitConverter.CopyBytes.cs new file mode 100644 index 0000000000..b46a453a4f --- /dev/null +++ b/src/ImageSharp/IO/EndianBitConverter.CopyBytes.cs @@ -0,0 +1,145 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + + /// + /// Equivalent of , but with either endianness. + /// + internal abstract partial class EndianBitConverter + { + /// + /// Copies the specified 16-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public abstract void CopyBytes(short value, byte[] buffer, int index); + + /// + /// Copies the specified 32-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public abstract void CopyBytes(int value, byte[] buffer, int index); + + /// + /// Copies the specified 64-bit signed integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public abstract void CopyBytes(long value, byte[] buffer, int index); + + /// + /// Copies the specified 16-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ushort value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((short)value), buffer, index); + } + + /// + /// Copies the specified 32-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(uint value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((int)value), buffer, index); + } + + /// + /// Copies the specified 64-bit unsigned integer value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(ulong value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((long)value), buffer, index); + } + + /// + /// Copies the specified Boolean value into the specified byte array, + /// beginning at the specified index. + /// + /// A Boolean value. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(bool value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 1); + buffer[index] = value ? (byte)1 : (byte)0; + } + + /// + /// Copies the specified Unicode character value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public void CopyBytes(char value, byte[] buffer, int index) + { + this.CopyBytes(unchecked((short)value), buffer, index); + } + + /// + /// Copies the specified double-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public unsafe void CopyBytes(double value, byte[] buffer, int index) + { + this.CopyBytes(*((long*)&value), buffer, index); + } + + /// + /// Copies the specified single-precision floating point value into the specified byte array, + /// beginning at the specified index. + /// + /// The number to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public unsafe void CopyBytes(float value, byte[] buffer, int index) + { + this.CopyBytes(*((int*)&value), buffer, index); + } + + /// + /// Copies the specified decimal value into the specified byte array, + /// beginning at the specified index. + /// + /// A character to convert. + /// The byte array to copy the bytes into + /// The first index into the array to copy the bytes into + public unsafe void CopyBytes(decimal value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 16); + + int* pvalue = (int*)&value; + this.CopyBytes(pvalue[0], buffer, index); + this.CopyBytes(pvalue[1], buffer, index + 4); + this.CopyBytes(pvalue[2], buffer, index + 8); + this.CopyBytes(pvalue[3], buffer, index + 12); + } + } +} diff --git a/src/ImageSharp/IO/EndianBitConverter.GetBytes.cs b/src/ImageSharp/IO/EndianBitConverter.GetBytes.cs new file mode 100644 index 0000000000..b3e0133e43 --- /dev/null +++ b/src/ImageSharp/IO/EndianBitConverter.GetBytes.cs @@ -0,0 +1,139 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + + /// + /// Equivalent of , but with either endianness. + /// + internal abstract partial class EndianBitConverter + { + /// + /// Returns the specified 16-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(short value) + { + byte[] result = new byte[2]; + this.CopyBytes(value, result, 0); + return result; + } + + /// + /// Returns the specified 32-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(int value) + { + byte[] result = new byte[4]; + this.CopyBytes(value, result, 0); + return result; + } + + /// + /// Returns the specified 64-bit signed integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(long value) + { + byte[] result = new byte[8]; + this.CopyBytes(value, result, 0); + return result; + } + + /// + /// Returns the specified 16-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 2. + public byte[] GetBytes(ushort value) + { + return this.GetBytes(unchecked((short)value)); + } + + /// + /// Returns the specified 32-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public byte[] GetBytes(uint value) + { + return this.GetBytes(unchecked((int)value)); + } + + /// + /// Returns the specified 64-bit unsigned integer value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public byte[] GetBytes(ulong value) + { + return this.GetBytes(unchecked((long)value)); + } + + /// + /// Returns the specified Boolean value as an array of bytes. + /// + /// A Boolean value. + /// An array of bytes with length 1. + /// + /// The . + /// + public byte[] GetBytes(bool value) + { + return new byte[1] { value ? (byte)1 : (byte)0 }; + } + + /// + /// Returns the specified Unicode character value as an array of bytes. + /// + /// A character to convert. + /// An array of bytes with length 2. + /// + /// The . + /// + public byte[] GetBytes(char value) + { + return this.GetBytes((short)value); + } + + /// + /// Returns the specified double-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 8. + public unsafe byte[] GetBytes(double value) + { + return this.GetBytes(*((long*)&value)); + } + + /// + /// Returns the specified single-precision floating point value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 4. + public unsafe byte[] GetBytes(float value) + { + return this.GetBytes(*((int*)&value)); + } + + /// + /// Returns the specified decimal value as an array of bytes. + /// + /// The number to convert. + /// An array of bytes with length 16. + public byte[] GetBytes(decimal value) + { + byte[] result = new byte[16]; + this.CopyBytes(value, result, 0); + return result; + } + } +} diff --git a/src/ImageSharp/IO/EndianBitConverter.ToType.cs b/src/ImageSharp/IO/EndianBitConverter.ToType.cs new file mode 100644 index 0000000000..93b49558ad --- /dev/null +++ b/src/ImageSharp/IO/EndianBitConverter.ToType.cs @@ -0,0 +1,141 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + + /// + /// Equivalent of , but with either endianness. + /// + internal abstract partial class EndianBitConverter + { + /// + /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit signed integer formed by two bytes beginning at startIndex. + public abstract short ToInt16(byte[] value, int startIndex); + + /// + /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit signed integer formed by four bytes beginning at startIndex. + public abstract int ToInt32(byte[] value, int startIndex); + + /// + /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit signed integer formed by eight bytes beginning at startIndex. + public abstract long ToInt64(byte[] value, int startIndex); + + /// + /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. + public ushort ToUInt16(byte[] value, int startIndex) + { + return unchecked((ushort)this.ToInt16(value, startIndex)); + } + + /// + /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. + public uint ToUInt32(byte[] value, int startIndex) + { + return unchecked((uint)this.ToInt32(value, startIndex)); + } + + /// + /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. + public ulong ToUInt64(byte[] value, int startIndex) + { + return unchecked((ulong)this.ToInt64(value, startIndex)); + } + + /// + /// Returns a Boolean value converted from one byte at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// true if the byte at startIndex in value is nonzero; otherwise, false. + public bool ToBoolean(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 1); + return value[startIndex] != 0; + } + + /// + /// Returns a Unicode character converted from two bytes at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A character formed by two bytes beginning at startIndex. + public char ToChar(byte[] value, int startIndex) + { + return unchecked((char)this.ToInt16(value, startIndex)); + } + + /// + /// Returns a double-precision floating point number converted from eight bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A double precision floating point number formed by eight bytes beginning at startIndex. + public unsafe double ToDouble(byte[] value, int startIndex) + { + long intValue = this.ToInt64(value, startIndex); + return *((double*)&intValue); + } + + /// + /// Returns a single-precision floating point number converted from four bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A single precision floating point number formed by four bytes beginning at startIndex. + public unsafe float ToSingle(byte[] value, int startIndex) + { + int intValue = this.ToInt32(value, startIndex); + return *((float*)&intValue); + } + + /// + /// Returns a decimal value converted from sixteen bytes + /// at a specified position in a byte array. + /// + /// An array of bytes. + /// The starting position within value. + /// A decimal formed by sixteen bytes beginning at startIndex. + public unsafe decimal ToDecimal(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 16); + + decimal result = 0m; + int* presult = (int*)&result; + presult[0] = this.ToInt32(value, startIndex); + presult[1] = this.ToInt32(value, startIndex + 4); + presult[2] = this.ToInt32(value, startIndex + 8); + presult[3] = this.ToInt32(value, startIndex + 12); + return result; + } + } +} diff --git a/src/ImageSharp/IO/EndianBitConverter.cs b/src/ImageSharp/IO/EndianBitConverter.cs index 812823e7a2..06b88dbc90 100644 --- a/src/ImageSharp/IO/EndianBitConverter.cs +++ b/src/ImageSharp/IO/EndianBitConverter.cs @@ -6,291 +6,56 @@ namespace ImageSharp.IO { using System; - using System.Diagnostics.CodeAnalysis; - using System.Runtime.InteropServices; + using System.Runtime.CompilerServices; /// - /// Equivalent of , but with either endianness. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// + /// Equivalent of , but with either endianness. /// - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "Reviewed. Suppression is OK here. Better readability.")] - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Reviewed. Suppression is OK here. Better readability.")] - [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "Reviewed. Suppression is OK here. Better readability.")] - [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1124:DoNotUseRegions", Justification = "Reviewed. Suppression is OK here. Better readability.")] - internal abstract class EndianBitConverter + internal abstract partial class EndianBitConverter { - #region Endianness of this converter - /// - /// Indicates the byte order ("endianness") in which data is converted using this class. + /// The little-endian bit converter. /// - /// - /// Different computer architectures store data using different byte orders. "Big-endian" - /// means the most significant byte is on the left end of a word. "Little-endian" means the - /// most significant byte is on the right end of a word. - /// - /// true if this converter is little-endian, false otherwise. - public abstract bool IsLittleEndian(); + public static readonly LittleEndianBitConverter LittleEndianConverter = new LittleEndianBitConverter(); /// - /// Gets the byte order ("endianness") in which data is converted using this class. + /// The big-endian bit converter. /// - public abstract Endianness Endianness { get; } - #endregion - - #region Factory properties + public static readonly BigEndianBitConverter BigEndianConverter = new BigEndianBitConverter(); /// - /// The little-endian bit converter. + /// Gets the byte order ("endianness") in which data is converted using this class. /// - private static readonly LittleEndianBitConverter LittleConverter = new LittleEndianBitConverter(); + public abstract Endianness Endianness { get; } /// - /// The big-endian bit converter. + /// Gets a value indicating whether the byte order ("endianness") in which data is converted is little endian. /// - private static readonly BigEndianBitConverter BigConverter = new BigEndianBitConverter(); + /// + /// Different computer architectures store data using different byte orders. "Big-endian" + /// means the most significant byte is on the left end of a word. "Little-endian" means the + /// most significant byte is on the right end of a word. + /// + public abstract bool IsLittleEndian { get; } /// /// Gets the converter. /// /// The endianness. /// an - /// Not a valid form of Endianness - endianness - internal static EndianBitConverter GetConverter(Endianness endianness) + /// Not a valid form of Endianness - endianness + public static EndianBitConverter GetConverter(Endianness endianness) { switch (endianness) { case Endianness.LittleEndian: - return LittleConverter; + return LittleEndianConverter; case Endianness.BigEndian: - return BigConverter; + return BigEndianConverter; default: throw new ArgumentException("Not a valid form of Endianness", nameof(endianness)); } } - #endregion - - #region Double/primitive conversions - - /// - /// Converts the specified double-precision floating point number to a - /// 64-bit signed integer. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A 64-bit signed integer whose value is equivalent to value. - public long DoubleToInt64Bits(double value) - { - return BitConverter.DoubleToInt64Bits(value); - } - - /// - /// Converts the specified 64-bit signed integer to a double-precision - /// floating point number. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A double-precision floating point number whose value is equivalent to value. - public double Int64BitsToDouble(long value) - { - return BitConverter.Int64BitsToDouble(value); - } - - /// - /// Converts the specified single-precision floating point number to a - /// 32-bit signed integer. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A 32-bit signed integer whose value is equivalent to value. - public int SingleToInt32Bits(float value) - { - return new Int32SingleUnion(value).AsInt32; - } - - /// - /// Converts the specified 32-bit signed integer to a single-precision floating point - /// number. Note: the endianness of this converter does not - /// affect the returned value. - /// - /// The number to convert. - /// A single-precision floating point number whose value is equivalent to value. - public float Int32BitsToSingle(int value) - { - return new Int32SingleUnion(value).AsSingle; - } - #endregion - - #region To(PrimitiveType) conversions - - /// - /// Returns a Boolean value converted from one byte at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// true if the byte at startIndex in value is nonzero; otherwise, false. - public bool ToBoolean(byte[] value, int startIndex) - { - CheckByteArgument(value, startIndex, 1); - return BitConverter.ToBoolean(value, startIndex); - } - - /// - /// Returns a Unicode character converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A character formed by two bytes beginning at startIndex. - public char ToChar(byte[] value, int startIndex) - { - return unchecked((char)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a double-precision floating point number converted from eight bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A double precision floating point number formed by eight bytes beginning at startIndex. - public double ToDouble(byte[] value, int startIndex) - { - return this.Int64BitsToDouble(this.ToInt64(value, startIndex)); - } - - /// - /// Returns a single-precision floating point number converted from four bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A single precision floating point number formed by four bytes beginning at startIndex. - public float ToSingle(byte[] value, int startIndex) - { - return this.Int32BitsToSingle(this.ToInt32(value, startIndex)); - } - - /// - /// Returns a 16-bit signed integer converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 16-bit signed integer formed by two bytes beginning at startIndex. - public short ToInt16(byte[] value, int startIndex) - { - return unchecked((short)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a 32-bit signed integer converted from four bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 32-bit signed integer formed by four bytes beginning at startIndex. - public int ToInt32(byte[] value, int startIndex) - { - return unchecked((int)this.CheckedFromBytes(value, startIndex, 4)); - } - - /// - /// Returns a 64-bit signed integer converted from eight bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 64-bit signed integer formed by eight bytes beginning at startIndex. - public long ToInt64(byte[] value, int startIndex) - { - return this.CheckedFromBytes(value, startIndex, 8); - } - - /// - /// Returns a 16-bit unsigned integer converted from two bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 16-bit unsigned integer formed by two bytes beginning at startIndex. - public ushort ToUInt16(byte[] value, int startIndex) - { - return unchecked((ushort)this.CheckedFromBytes(value, startIndex, 2)); - } - - /// - /// Returns a 32-bit unsigned integer converted from four bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 32-bit unsigned integer formed by four bytes beginning at startIndex. - public uint ToUInt32(byte[] value, int startIndex) - { - return unchecked((uint)this.CheckedFromBytes(value, startIndex, 4)); - } - - /// - /// Returns a 64-bit unsigned integer converted from eight bytes at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A 64-bit unsigned integer formed by eight bytes beginning at startIndex. - public ulong ToUInt64(byte[] value, int startIndex) - { - return unchecked((ulong)this.CheckedFromBytes(value, startIndex, 8)); - } - - /// - /// Convert the given number of bytes from the given array, from the given start - /// position, into a long, using the bytes as the least significant part of the long. - /// By the time this is called, the arguments have been checked for validity. - /// - /// The bytes to convert - /// The index of the first byte to convert - /// The number of bytes to use in the conversion - /// The converted number - protected internal abstract long FromBytes(byte[] value, int startIndex, int bytesToConvert); - - /// - /// Checks the given argument for validity. - /// - /// The byte array passed in - /// The start index passed in - /// The number of bytes required - /// value is a null reference - /// - /// startIndex is less than zero or greater than the length of value minus bytesRequired. - /// - [SuppressMessage("ReSharper", "UnusedParameter.Local", Justification = "Keeps code DRY")] - private static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - if (startIndex < 0 || startIndex > value.Length - bytesRequired) - { - throw new ArgumentOutOfRangeException(nameof(startIndex)); - } - } - - /// - /// Checks the arguments for validity before calling FromBytes - /// (which can therefore assume the arguments are valid). - /// - /// The bytes to convert after checking - /// The index of the first byte to convert - /// The number of bytes to convert - /// The - private long CheckedFromBytes(byte[] value, int startIndex, int bytesToConvert) - { - CheckByteArgument(value, startIndex, bytesToConvert); - return this.FromBytes(value, startIndex, bytesToConvert); - } - #endregion - - #region ToString conversions /// /// Returns a String converted from the elements of a byte array. @@ -336,406 +101,29 @@ namespace ImageSharp.IO { return BitConverter.ToString(value, startIndex, length); } - #endregion - - #region Decimal conversions /// - /// Returns a decimal value converted from sixteen bytes - /// at a specified position in a byte array. - /// - /// An array of bytes. - /// The starting position within value. - /// A decimal formed by sixteen bytes beginning at startIndex. - public decimal ToDecimal(byte[] value, int startIndex) - { - // HACK: This always assumes four parts, each in their own endianness, - // starting with the first part at the start of the byte array. - // On the other hand, there's no real format specified... - int[] parts = new int[4]; - for (int i = 0; i < 4; i++) - { - parts[i] = this.ToInt32(value, startIndex + (i * 4)); - } - - return new decimal(parts); - } - - /// - /// Returns the specified decimal value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 16. - public byte[] GetBytes(decimal value) - { - byte[] bytes = new byte[16]; - int[] parts = decimal.GetBits(value); - for (int i = 0; i < 4; i++) - { - this.CopyBytesImpl(parts[i], 4, bytes, i * 4); - } - - return bytes; - } - - /// - /// Copies the specified decimal value into the specified byte array, - /// beginning at the specified index. - /// - /// A character to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(decimal value, byte[] buffer, int index) - { - int[] parts = decimal.GetBits(value); - for (int i = 0; i < 4; i++) - { - this.CopyBytesImpl(parts[i], 4, buffer, (i * 4) + index); - } - } - #endregion - - #region GetBytes conversions - - /// - /// Returns an array with the given number of bytes formed - /// from the least significant bytes of the specified value. - /// This is used to implement the other GetBytes methods. - /// - /// The value to get bytes for - /// The number of significant bytes to return - /// - /// The . - /// - private byte[] GetBytes(long value, int bytes) - { - byte[] buffer = new byte[bytes]; - this.CopyBytes(value, bytes, buffer, 0); - return buffer; - } - - /// - /// Returns the specified Boolean value as an array of bytes. - /// - /// A Boolean value. - /// An array of bytes with length 1. - /// - /// The . - /// - public byte[] GetBytes(bool value) - { - return BitConverter.GetBytes(value); - } - - /// - /// Returns the specified Unicode character value as an array of bytes. - /// - /// A character to convert. - /// An array of bytes with length 2. - /// - /// The . - /// - public byte[] GetBytes(char value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified double-precision floating point value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(double value) - { - return this.GetBytes(this.DoubleToInt64Bits(value), 8); - } - - /// - /// Returns the specified 16-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 2. - public byte[] GetBytes(short value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified 32-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(int value) - { - return this.GetBytes(value, 4); - } - - /// - /// Returns the specified 64-bit signed integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(long value) - { - return this.GetBytes(value, 8); - } - - /// - /// Returns the specified single-precision floating point value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(float value) - { - return this.GetBytes(this.SingleToInt32Bits(value), 4); - } - - /// - /// Returns the specified 16-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 2. - public byte[] GetBytes(ushort value) - { - return this.GetBytes(value, 2); - } - - /// - /// Returns the specified 32-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 4. - public byte[] GetBytes(uint value) - { - return this.GetBytes(value, 4); - } - - /// - /// Returns the specified 64-bit unsigned integer value as an array of bytes. - /// - /// The number to convert. - /// An array of bytes with length 8. - public byte[] GetBytes(ulong value) - { - return this.GetBytes(unchecked((long)value), 8); - } - - #endregion - - #region CopyBytes conversions - - /// - /// Copies the given number of bytes from the least-specific - /// end of the specified value into the specified byte array, beginning - /// at the specified index. - /// This is used to implement the other CopyBytes methods. - /// - /// The value to copy bytes for - /// The number of significant bytes to copy - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - private void CopyBytes(long value, int bytes, byte[] buffer, int index) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), "Byte array must not be null"); - } - - if (buffer.Length < index + bytes) - { - throw new ArgumentOutOfRangeException(nameof(buffer), "Buffer not big enough for value"); - } - - this.CopyBytesImpl(value, bytes, buffer, index); - } - - /// - /// Copies the given number of bytes from the least-specific - /// end of the specified value into the specified byte array, beginning - /// at the specified index. - /// This must be implemented in concrete derived classes, but the implementation - /// may assume that the value will fit into the buffer. - /// - /// The value to copy bytes for - /// The number of significant bytes to copy - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - protected internal abstract void CopyBytesImpl(long value, int bytes, byte[] buffer, int index); - - /// - /// Copies the specified Boolean value into the specified byte array, - /// beginning at the specified index. - /// - /// A Boolean value. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(bool value, byte[] buffer, int index) - { - this.CopyBytes(value ? 1 : 0, 1, buffer, index); - } - - /// - /// Copies the specified Unicode character value into the specified byte array, - /// beginning at the specified index. - /// - /// A character to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(char value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified double-precision floating point value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(double value, byte[] buffer, int index) - { - this.CopyBytes(this.DoubleToInt64Bits(value), 8, buffer, index); - } - - /// - /// Copies the specified 16-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(short value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified 32-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(int value, byte[] buffer, int index) - { - this.CopyBytes(value, 4, buffer, index); - } - - /// - /// Copies the specified 64-bit signed integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(long value, byte[] buffer, int index) - { - this.CopyBytes(value, 8, buffer, index); - } - - /// - /// Copies the specified single-precision floating point value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(float value, byte[] buffer, int index) - { - this.CopyBytes(this.SingleToInt32Bits(value), 4, buffer, index); - } - - /// - /// Copies the specified 16-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(ushort value, byte[] buffer, int index) - { - this.CopyBytes(value, 2, buffer, index); - } - - /// - /// Copies the specified 32-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(uint value, byte[] buffer, int index) - { - this.CopyBytes(value, 4, buffer, index); - } - - /// - /// Copies the specified 64-bit unsigned integer value into the specified byte array, - /// beginning at the specified index. - /// - /// The number to convert. - /// The byte array to copy the bytes into - /// The first index into the array to copy the bytes into - public void CopyBytes(ulong value, byte[] buffer, int index) - { - this.CopyBytes(unchecked((long)value), 8, buffer, index); - } - - #endregion - - #region Private struct used for Single/Int32 conversions - - /// - /// Union used solely for the equivalent of DoubleToInt64Bits and vice versa. + /// Checks the given argument for validity. /// - [StructLayout(LayoutKind.Explicit)] - private struct Int32SingleUnion + /// The byte array passed in + /// The start index passed in + /// The number of bytes required + /// value is a null reference + /// + /// startIndex is less than zero or greater than the length of value minus bytesRequired. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected static void CheckByteArgument(byte[] value, int startIndex, int bytesRequired) { - /// - /// Int32 version of the value. - /// - [FieldOffset(0)] - private readonly int i; - - /// - /// Single version of the value. - /// - [FieldOffset(0)] - private readonly float f; - - /// - /// Initializes a new instance of the struct. - /// - /// The integer value of the new instance. - internal Int32SingleUnion(int i) + if (value == null) { - this.f = 0; // Just to keep the compiler happy - this.i = i; + throw new ArgumentNullException(nameof(value)); } - /// - /// Initializes a new instance of the struct. - /// - /// - /// The floating point value of the new instance. - /// - internal Int32SingleUnion(float f) + if (startIndex < 0 || startIndex > value.Length - bytesRequired) { - this.i = 0; // Just to keep the compiler happy - this.f = f; + throw new ArgumentOutOfRangeException(nameof(startIndex)); } - - /// - /// Gets the value of the instance as an integer. - /// - internal int AsInt32 => this.i; - - /// - /// Gets the value of the instance as a floating point number. - /// - internal float AsSingle => this.f; } - #endregion } -} \ No newline at end of file +} diff --git a/src/ImageSharp/IO/IFileSystem.cs b/src/ImageSharp/IO/IFileSystem.cs new file mode 100644 index 0000000000..ee1ef84d7b --- /dev/null +++ b/src/ImageSharp/IO/IFileSystem.cs @@ -0,0 +1,31 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System.IO; + + #if !NETSTANDARD1_1 + /// + /// A simple interface representing the filesystem. + /// + public interface IFileSystem + { + /// + /// Returns a readable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream OpenRead(string path); + + /// + /// Creates or opens a file and returns it as a writeable stream as defined by the path. + /// + /// Path to the file to open. + /// A stream representing the file to open. + Stream Create(string path); + } +#endif +} diff --git a/src/ImageSharp/IO/LittleEndianBitConverter.cs b/src/ImageSharp/IO/LittleEndianBitConverter.cs index 63ebe18a33..81ed8d55c2 100644 --- a/src/ImageSharp/IO/LittleEndianBitConverter.cs +++ b/src/ImageSharp/IO/LittleEndianBitConverter.cs @@ -6,42 +6,78 @@ namespace ImageSharp.IO { /// - /// Implementation of EndianBitConverter which converts to/from little-endian - /// byte arrays. - /// - /// Adapted from Miscellaneous Utility Library - /// This product includes software developed by Jon Skeet and Marc Gravell. Contact , or see - /// . - /// + /// Implementation of EndianBitConverter which converts to/from little-endian byte arrays. /// internal sealed class LittleEndianBitConverter : EndianBitConverter { /// - public override Endianness Endianness => Endianness.LittleEndian; + public override Endianness Endianness + { + get { return Endianness.LittleEndian; } + } + + /// + public override bool IsLittleEndian + { + get { return true; } + } + + /// + public override void CopyBytes(short value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 2); + + buffer[index + 1] = (byte)(value >> 8); + buffer[index] = (byte)value; + } /// - public override bool IsLittleEndian() => true; + public override void CopyBytes(int value, byte[] buffer, int index) + { + CheckByteArgument(buffer, index, 4); + + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 1] = (byte)(value >> 8); + buffer[index] = (byte)value; + } /// - protected internal override void CopyBytesImpl(long value, int bytes, byte[] buffer, int index) + public override void CopyBytes(long value, byte[] buffer, int index) { - for (int i = 0; i < bytes; i++) - { - buffer[i + index] = unchecked((byte)(value & 0xff)); - value = value >> 8; - } + CheckByteArgument(buffer, index, 8); + + buffer[index + 7] = (byte)(value >> 56); + buffer[index + 6] = (byte)(value >> 48); + buffer[index + 5] = (byte)(value >> 40); + buffer[index + 4] = (byte)(value >> 32); + buffer[index + 3] = (byte)(value >> 24); + buffer[index + 2] = (byte)(value >> 16); + buffer[index + 1] = (byte)(value >> 8); + buffer[index] = (byte)value; } /// - protected internal override long FromBytes(byte[] buffer, int startIndex, int bytesToConvert) + public unsafe override short ToInt16(byte[] value, int startIndex) { - long ret = 0; - for (int i = 0; i < bytesToConvert; i++) - { - ret = unchecked((ret << 8) | buffer[startIndex + bytesToConvert - 1 - i]); - } + CheckByteArgument(value, startIndex, 2); + return (short)((value[startIndex + 1] << 8) | value[startIndex]); + } - return ret; + /// + public unsafe override int ToInt32(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 4); + return (value[startIndex + 3] << 24) | (value[startIndex + 2] << 16) | (value[startIndex + 1] << 8) | value[startIndex]; + } + + /// + public unsafe override long ToInt64(byte[] value, int startIndex) + { + CheckByteArgument(value, startIndex, 8); + long p1 = (value[startIndex + 7] << 24) | (value[startIndex + 6] << 16) | (value[startIndex + 5] << 8) | value[startIndex + 4]; + long p2 = (value[startIndex + 3] << 24) | (value[startIndex + 2] << 16) | (value[startIndex + 1] << 8) | value[startIndex]; + return p2 | (p1 << 32); } } } \ No newline at end of file diff --git a/src/ImageSharp/IO/LocalFileSystem.cs b/src/ImageSharp/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..02a9914ead --- /dev/null +++ b/src/ImageSharp/IO/LocalFileSystem.cs @@ -0,0 +1,32 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.IO +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + + #if !NETSTANDARD1_1 + /// + /// A wrapper around the local File apis. + /// + public class LocalFileSystem : IFileSystem + { + /// + public Stream OpenRead(string path) + { + return File.OpenRead(path); + } + + /// + public Stream Create(string path) + { + return File.Create(path); + } + } +#endif +} diff --git a/src/ImageSharp/Image.Create.cs b/src/ImageSharp/Image.Create.cs new file mode 100644 index 0000000000..fcecefd7b7 --- /dev/null +++ b/src/ImageSharp/Image.Create.cs @@ -0,0 +1,66 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Diagnostics; + using System.IO; + + using Formats; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + public sealed partial class Image + { + /// + /// Create a new instance of the class + /// with the height and the width of the image. + /// + /// The pixel format. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images matadata to preload. + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// + /// A new unless is in which case it returns + /// + internal static Image Create(int width, int height, ImageMetaData metadata, Configuration configuration) + where TColor : struct, IPixel + { + if (typeof(TColor) == typeof(Color)) + { + return new Image(width, height, metadata, configuration) as Image; + } + else + { + return new Image(width, height, metadata, configuration); + } + } + + /// + /// Create a new instance of the class + /// with the height and the width of the image. + /// + /// The pixel format. + /// The width of the image in pixels. + /// The height of the image in pixels. + /// + /// The configuration providing initialization code which allows extending the library. + /// + /// + /// A new unless is in which case it returns + /// + internal static Image Create(int width, int height, Configuration configuration) + where TColor : struct, IPixel + { + return Image.Create(width, height, null, configuration); + } + } +} diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs new file mode 100644 index 0000000000..c1c1371220 --- /dev/null +++ b/src/ImageSharp/Image.Decode.cs @@ -0,0 +1,75 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Buffers; + using System.IO; + using System.Linq; + using Formats; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + public sealed partial class Image + { + /// + /// By reading the header on the provided stream this calculates the images format. + /// + /// The image stream to read the header from. + /// The configuration. + /// The image format or null if none found. + private static IImageFormat DiscoverFormat(Stream stream, Configuration config) + { + // This is probably a candidate for making into a public API in the future! + int maxHeaderSize = config.MaxHeaderSize; + if (maxHeaderSize <= 0) + { + return null; + } + + IImageFormat format; + byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); + try + { + long startPosition = stream.Position; + stream.Read(header, 0, maxHeaderSize); + stream.Position = startPosition; + format = config.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); + } + finally + { + ArrayPool.Shared.Return(header); + } + + return format; + } + + /// + /// Decodes the image stream to the current image. + /// + /// The pixel format. + /// The stream. + /// The options for the decoder. + /// the configuration. + /// + /// The decoded image + /// + private static Image Decode(Stream stream, IDecoderOptions options, Configuration config) + where TColor : struct, IPixel + { + IImageFormat format = DiscoverFormat(stream, config); + if (format == null) + { + return null; + } + + Image img = format.Decoder.Decode(config, stream, options); + img.CurrentImageFormat = format; + return img; + } + } +} diff --git a/src/ImageSharp/Image.FromBytes.cs b/src/ImageSharp/Image.FromBytes.cs new file mode 100644 index 0000000000..b2f9854f22 --- /dev/null +++ b/src/ImageSharp/Image.FromBytes.cs @@ -0,0 +1,212 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using Formats; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + public sealed partial class Image + { + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data) + { + return Load(null, data, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IDecoderOptions options) + { + return Load(null, data, options); + } + + /// + /// Loads the image from the given byte array. + /// + /// The config for the decoder. + /// The byte array containing image data. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, byte[] data) + { + return Load(config, data, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder) + { + return Load(data, decoder, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The configuration options. + /// The byte array containing image data. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, byte[] data, IDecoderOptions options) + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(config, ms, options); + } + } + + /// + /// Loads the image from the given byte array. + /// + /// The byte array containing image data. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(ms, decoder, options); + } + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data) + where TColor : struct, IPixel + { + return Load(null, data, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IDecoderOptions options) + where TColor : struct, IPixel + { + return Load(null, data, options); + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The config for the decoder. + /// The byte array containing image data. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, byte[] data) + where TColor : struct, IPixel + { + return Load(config, data, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(data, decoder, null); + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The configuration options. + /// The byte array containing image data. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, byte[] data, IDecoderOptions options) + where TColor : struct, IPixel + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(config, ms, options); + } + } + + /// + /// Loads the image from the given byte array. + /// + /// The pixel format. + /// The byte array containing image data. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(byte[] data, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + using (MemoryStream ms = new MemoryStream(data)) + { + return Load(ms, decoder, options); + } + } + } +} diff --git a/src/ImageSharp/Image.FromFile.cs b/src/ImageSharp/Image.FromFile.cs new file mode 100644 index 0000000000..40cdfe3eff --- /dev/null +++ b/src/ImageSharp/Image.FromFile.cs @@ -0,0 +1,214 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ +#if !NETSTANDARD1_1 + using System; + using System.IO; + using Formats; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + public sealed partial class Image + { + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path) + { + return Load(null, path, null); + } + + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IDecoderOptions options) + { + return Load(null, path, options); + } + + /// + /// Loads the image from the given file. + /// + /// The config for the decoder. + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, string path) + { + return Load(config, path, null); + } + + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder) + { + return Load(path, decoder, null); + } + + /// + /// Loads the image from the given file. + /// + /// The file path to the image. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) + { + return new Image(Load(path, decoder, options)); + } + + /// + /// Loads the image from the given file. + /// + /// The configuration options. + /// The file path to the image. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, string path, IDecoderOptions options) + { + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(config, s, options); + } + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path) + where TColor : struct, IPixel + { + return Load(null, path, null); + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IDecoderOptions options) + where TColor : struct, IPixel + { + return Load(null, path, options); + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The config for the decoder. + /// The file path to the image. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, string path) + where TColor : struct, IPixel + { + return Load(config, path, null); + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(path, decoder, null); + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The configuration options. + /// The file path to the image. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, string path, IDecoderOptions options) + where TColor : struct, IPixel + { + config = config ?? Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(config, s, options); + } + } + + /// + /// Loads the image from the given file. + /// + /// The pixel format. + /// The file path to the image. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(string path, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + Configuration config = Configuration.Default; + using (Stream s = config.FileSystem.OpenRead(path)) + { + return Load(s, decoder, options); + } + } + } +#endif +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs new file mode 100644 index 0000000000..41ac7757e7 --- /dev/null +++ b/src/ImageSharp/Image.FromStream.cs @@ -0,0 +1,246 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.IO; + using System.Text; + using Formats; + + /// + /// Represents an image. Each pixel is a made up four 8-bit components red, green, blue, and alpha + /// packed into a single unsigned integer value. + /// + public sealed partial class Image + { + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream) + { + return Load(null, stream, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IDecoderOptions options) + { + return Load(null, stream, options); + } + + /// + /// Loads the image from the given stream. + /// + /// The config for the decoder. + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, Stream stream) + { + return Load(config, stream, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder) + { + return Load(stream, decoder, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The configuration options. + /// The stream containing image information. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, Stream stream, IDecoderOptions options) + { + Image image = Load(config, stream, options); + + return image as Image ?? new Image(image); + } + + /// + /// Loads the image from the given stream. + /// + /// The stream containing image information. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) + { + Image image = new Image(Load(stream, decoder, options)); + + return image as Image ?? new Image(image); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream) + where TColor : struct, IPixel + { + return Load(null, stream, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { + return Load(null, stream, options); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The config for the decoder. + /// The stream containing image information. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, Stream stream) + where TColor : struct, IPixel + { + return Load(config, stream, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder) + where TColor : struct, IPixel + { + return Load(stream, decoder, null); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The stream containing image information. + /// The decoder. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Stream stream, IImageDecoder decoder, IDecoderOptions options) + where TColor : struct, IPixel + { + return WithSeekableStream(stream, s => decoder.Decode(Configuration.Default, s, options)); + } + + /// + /// Loads the image from the given stream. + /// + /// The pixel format. + /// The configuration options. + /// The stream containing image information. + /// The options for the decoder. + /// + /// Thrown if the stream is not readable nor seekable. + /// + /// The image + public static Image Load(Configuration config, Stream stream, IDecoderOptions options) + where TColor : struct, IPixel + { + config = config ?? Configuration.Default; + + Image img = WithSeekableStream(stream, s => Decode(stream, options, config)); + + if (img != null) + { + return img; + } + + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); + + foreach (IImageFormat format in config.ImageFormats) + { + stringBuilder.AppendLine("-" + format); + } + + throw new NotSupportedException(stringBuilder.ToString()); + } + + private static T WithSeekableStream(Stream stream, Func action) + { + if (!stream.CanRead) + { + throw new NotSupportedException("Cannot read from the stream."); + } + + if (stream.CanSeek) + { + return action(stream); + } + else + { + // We want to be able to load images from things like HttpContext.Request.Body + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + ms.Position = 0; + + return action(stream); + } + } + } + } +} diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 8bfd8ee1a3..00688afc96 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Diagnostics; using System.IO; @@ -15,7 +16,7 @@ namespace ImageSharp /// packed into a single unsigned integer value. /// [DebuggerDisplay("Image: {Width}x{Height}")] - public sealed class Image : Image + public sealed partial class Image : Image { /// /// Initializes a new instance of the class @@ -26,201 +27,45 @@ namespace ImageSharp /// /// The configuration providing initialization code which allows extending the library. /// - public Image(int width, int height, Configuration configuration = null) + public Image(int width, int height, Configuration configuration) : base(width, height, configuration) { } /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// Thrown if the is null. - public Image(Stream stream) - : base(stream, null, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options) - : base(stream, options, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(Stream stream, Configuration configuration) - : base(stream, null, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The options for the decoder. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options, Configuration configuration) - : base(stream, options, configuration) - { - } - -#if !NETSTANDARD1_1 - /// - /// Initializes a new instance of the class. - /// - /// - /// A file path to read image information. - /// - /// Thrown if the is null. - public Image(string filePath) - : base(filePath, null, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A file path to read image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options) - : base(filePath, options, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A file path to read image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(string filePath, Configuration configuration) - : base(filePath, null, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// A file path to read image information. - /// - /// - /// The options for the decoder. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options, Configuration configuration) - : base(filePath, options, configuration) - { - } -#endif - - /// - /// Initializes a new instance of the class. - /// - /// - /// The byte array containing image information. - /// - /// Thrown if the is null. - public Image(byte[] bytes) - : base(bytes, null, null) - { - } - - /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// with the height and the width of the image. /// - /// - /// The byte array containing image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options) - : base(bytes, options, null) + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : this(width, height, null) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// by making a copy from another image. /// - /// - /// The byte array containing image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration) - : base(bytes, null, configuration) + /// The other image, where the clone should be made from. + /// is null. + public Image(Image other) + : base(other) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// with the height and the width of the image. /// - /// - /// The byte array containing image information. - /// - /// - /// The options for the decoder. - /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The metadata. /// /// The configuration providing initialization code which allows extending the library. /// - /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) - : base(bytes, options, configuration) - { - } - - /// - /// Initializes a new instance of the class - /// by making a copy from another image. - /// - /// The other image, where the clone should be made from. - /// is null. - public Image(Image other) - : base(other) + internal Image(int width, int height, ImageMetaData metadata, Configuration configuration) + : base(width, height, metadata, configuration) { } } diff --git a/src/ImageSharp/Image/IImageBase.cs b/src/ImageSharp/Image/IImageBase.cs index 707fea235d..393d83077a 100644 --- a/src/ImageSharp/Image/IImageBase.cs +++ b/src/ImageSharp/Image/IImageBase.cs @@ -15,16 +15,6 @@ namespace ImageSharp /// Rectangle Bounds { get; } - /// - /// Gets or sets the maximum allowable width in pixels. - /// - int MaxWidth { get; set; } - - /// - /// Gets or sets the maximum allowable height in pixels. - /// - int MaxHeight { get; set; } - /// /// Gets the width in pixels. /// diff --git a/src/ImageSharp/Image/IImageBase{TColor}.cs b/src/ImageSharp/Image/IImageBase{TColor}.cs index e894fba4a8..14bdffc672 100644 --- a/src/ImageSharp/Image/IImageBase{TColor}.cs +++ b/src/ImageSharp/Image/IImageBase{TColor}.cs @@ -21,16 +21,6 @@ namespace ImageSharp /// TColor[] Pixels { get; } - /// - /// Sets the size of the pixel array of the image to the given width and height. - /// - /// The new width of the image. Must be greater than zero. - /// The new height of the image. Must be greater than zero. - /// - /// Thrown if either or are less than or equal to 0. - /// - void InitPixels(int width, int height); - /// /// Locks the image providing access to the pixels. /// diff --git a/src/ImageSharp/Image/ImageBase{TColor}.cs b/src/ImageSharp/Image/ImageBase{TColor}.cs index 878ba09b39..cfce7184b6 100644 --- a/src/ImageSharp/Image/ImageBase{TColor}.cs +++ b/src/ImageSharp/Image/ImageBase{TColor}.cs @@ -18,6 +18,16 @@ namespace ImageSharp public abstract class ImageBase : IImageBase where TColor : struct, IPixel { + /// + /// Gets or sets the maximum allowable width in pixels. + /// + public const int MaxWidth = int.MaxValue; + + /// + /// Gets or sets the maximum allowable height in pixels. + /// + public const int MaxHeight = int.MaxValue; + /// /// The image pixels /// @@ -40,7 +50,7 @@ namespace ImageSharp /// /// The configuration providing initialization code which allows extending the library. /// - protected ImageBase(Configuration configuration = null) + protected ImageBase(Configuration configuration) { this.Configuration = configuration ?? Configuration.Default; } @@ -56,10 +66,15 @@ namespace ImageSharp /// /// Thrown if either or are less than or equal to 0. /// - protected ImageBase(int width, int height, Configuration configuration = null) + protected ImageBase(int width, int height, Configuration configuration) + : this(configuration) { - this.Configuration = configuration ?? Configuration.Default; - this.InitPixels(width, height); + Guard.MustBeGreaterThan(width, 0, nameof(width)); + Guard.MustBeGreaterThan(height, 0, nameof(height)); + + this.Width = width; + this.Height = height; + this.RentPixels(); this.ClearPixels(); } @@ -73,6 +88,7 @@ namespace ImageSharp /// Thrown if the given is null. /// protected ImageBase(ImageBase other) + : this(other.Configuration) { Guard.NotNull(other, nameof(other), "Other image cannot be null."); @@ -90,12 +106,6 @@ namespace ImageSharp } } - /// - public int MaxWidth { get; set; } = int.MaxValue; - - /// - public int MaxHeight { get; set; } = int.MaxValue; - /// public TColor[] Pixels => this.pixelBuffer; @@ -139,17 +149,6 @@ namespace ImageSharp GC.SuppressFinalize(this); } - /// - public void InitPixels(int width, int height) - { - Guard.MustBeGreaterThan(width, 0, nameof(width)); - Guard.MustBeGreaterThan(height, 0, nameof(height)); - - this.Width = width; - this.Height = height; - this.RentPixels(); - } - /// public PixelAccessor Lock() { diff --git a/src/ImageSharp/Image/Image{TColor}.cs b/src/ImageSharp/Image/Image{TColor}.cs index 27dee54342..d063c3ff16 100644 --- a/src/ImageSharp/Image/Image{TColor}.cs +++ b/src/ImageSharp/Image/Image{TColor}.cs @@ -35,210 +35,20 @@ namespace ImageSharp /// /// The configuration providing initialization code which allows extending the library. /// - public Image(int width, int height, Configuration configuration = null) - : base(width, height, configuration) - { - if (!this.Configuration.ImageFormats.Any()) - { - throw new InvalidOperationException("No image formats have been configured."); - } - - this.CurrentImageFormat = this.Configuration.ImageFormats.First(); - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// Thrown if the is null. - public Image(Stream stream) - : this(stream, null, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options) - : this(stream, options, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(Stream stream, Configuration configuration) - : this(stream, null, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The stream containing image information. - /// - /// - /// The options for the decoder. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(Stream stream, IDecoderOptions options, Configuration configuration) - : base(configuration) - { - Guard.NotNull(stream, nameof(stream)); - this.Load(stream, options); - } - -#if !NETSTANDARD1_1 - /// - /// Initializes a new instance of the class. - /// - /// - /// The file containing image information. - /// - /// Thrown if the is null. - public Image(string filePath) - : this(filePath, null, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The file containing image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options) - : this(filePath, options, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The file containing image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(string filePath, Configuration configuration) - : this(filePath, null, configuration) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The file containing image information. - /// - /// - /// The options for the decoder. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(string filePath, IDecoderOptions options, Configuration configuration) - : base(configuration) - { - Guard.NotNull(filePath, nameof(filePath)); - using (var fs = File.OpenRead(filePath)) - { - this.Load(fs, options); - } - } -#endif - - /// - /// Initializes a new instance of the class. - /// - /// - /// The byte array containing image information. - /// - /// Thrown if the is null. - public Image(byte[] bytes) - : this(bytes, null, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The byte array containing image information. - /// - /// - /// The options for the decoder. - /// - /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options) - : this(bytes, options, null) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// - /// The byte array containing image information. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(byte[] bytes, Configuration configuration) - : this(bytes, null, configuration) + public Image(int width, int height, Configuration configuration) + : this(width, height, new ImageMetaData(), configuration) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// with the height and the width of the image. /// - /// - /// The byte array containing image information. - /// - /// - /// The options for the decoder. - /// - /// - /// The configuration providing initialization code which allows extending the library. - /// - /// Thrown if the is null. - public Image(byte[] bytes, IDecoderOptions options, Configuration configuration) - : base(configuration) + /// The width of the image in pixels. + /// The height of the image in pixels. + public Image(int width, int height) + : this(width, height, null) { - Guard.NotNull(bytes, nameof(bytes)); - - using (MemoryStream stream = new MemoryStream(bytes, false)) - { - this.Load(stream, options); - } } /// @@ -270,13 +80,35 @@ namespace ImageSharp public Image(ImageBase other) : base(other) { - this.CopyProperties(other); + this.MetaData = new ImageMetaData(); + } + + /// + /// Initializes a new instance of the class + /// with the height and the width of the image. + /// + /// The width of the image in pixels. + /// The height of the image in pixels. + /// The images metadata. + /// + /// The configuration providing initialization code which allows extending the library. + /// + internal Image(int width, int height, ImageMetaData metadata, Configuration configuration) + : base(width, height, configuration) + { + if (!this.Configuration.ImageFormats.Any()) + { + throw new InvalidOperationException("No image formats have been configured."); + } + + this.MetaData = metadata ?? new ImageMetaData(); + this.CurrentImageFormat = this.Configuration.ImageFormats.First(); } /// /// Gets the meta data of the image. /// - public ImageMetaData MetaData { get; private set; } = new ImageMetaData(); + public ImageMetaData MetaData { get; private set; } /// /// Gets the width of the image in inches. It is calculated as the width of the image @@ -348,9 +180,7 @@ namespace ImageSharp /// The public Image Save(Stream stream, IEncoderOptions options) { - Guard.NotNull(stream, nameof(stream)); - this.CurrentImageFormat.Encoder.Encode(this, stream, options); - return this; + return this.Save(stream, this.CurrentImageFormat?.Encoder, options); } /// @@ -373,10 +203,9 @@ namespace ImageSharp /// The public Image Save(Stream stream, IImageFormat format, IEncoderOptions options) { - Guard.NotNull(stream, nameof(stream)); Guard.NotNull(format, nameof(format)); - format.Encoder.Encode(this, stream, options); - return this; + + return this.Save(stream, format.Encoder, options); } /// @@ -407,13 +236,8 @@ namespace ImageSharp { Guard.NotNull(stream, nameof(stream)); Guard.NotNull(encoder, nameof(encoder)); - encoder.Encode(this, stream, options); - // Reset to the start of the stream. - if (stream.CanSeek) - { - stream.Position = 0; - } + encoder.Encode(this, stream, options); return this; } @@ -446,7 +270,7 @@ namespace ImageSharp throw new InvalidOperationException($"No image formats have been registered for the file extension '{ext}'."); } - return this.Save(filePath, format); + return this.Save(filePath, format, options); } /// @@ -472,10 +296,7 @@ namespace ImageSharp public Image Save(string filePath, IImageFormat format, IEncoderOptions options) { Guard.NotNull(format, nameof(format)); - using (FileStream fs = File.Create(filePath)) - { - return this.Save(fs, format); - } + return this.Save(filePath, format.Encoder, options); } /// @@ -501,9 +322,9 @@ namespace ImageSharp public Image Save(string filePath, IImageEncoder encoder, IEncoderOptions options) { Guard.NotNull(encoder, nameof(encoder)); - using (FileStream fs = File.Create(filePath)) + using (Stream fs = this.Configuration.FileSystem.Create(filePath)) { - return this.Save(fs, encoder); + return this.Save(fs, encoder, options); } } #endif @@ -598,103 +419,8 @@ namespace ImageSharp /// private void CopyProperties(IImage other) { - base.CopyProperties(other); - this.CurrentImageFormat = other.CurrentImageFormat; this.MetaData = new ImageMetaData(other.MetaData); } - - /// - /// Loads the image from the given stream. - /// - /// The stream containing image information. - /// The options for the decoder. - /// - /// Thrown if the stream is not readable nor seekable. - /// - private void Load(Stream stream, IDecoderOptions options) - { - if (!this.Configuration.ImageFormats.Any()) - { - throw new InvalidOperationException("No image formats have been configured."); - } - - if (!stream.CanRead) - { - throw new NotSupportedException("Cannot read from the stream."); - } - - if (stream.CanSeek) - { - if (this.Decode(stream, options)) - { - return; - } - } - else - { - // We want to be able to load images from things like HttpContext.Request.Body - using (MemoryStream ms = new MemoryStream()) - { - stream.CopyTo(ms); - ms.Position = 0; - - if (this.Decode(ms, options)) - { - return; - } - } - } - - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.AppendLine("Image cannot be loaded. Available formats:"); - - foreach (IImageFormat format in this.Configuration.ImageFormats) - { - stringBuilder.AppendLine("-" + format); - } - - throw new NotSupportedException(stringBuilder.ToString()); - } - - /// - /// Decodes the image stream to the current image. - /// - /// The stream. - /// The options for the decoder. - /// - /// The . - /// - private bool Decode(Stream stream, IDecoderOptions options) - { - int maxHeaderSize = this.Configuration.MaxHeaderSize; - if (maxHeaderSize <= 0) - { - return false; - } - - IImageFormat format; - byte[] header = ArrayPool.Shared.Rent(maxHeaderSize); - try - { - long startPosition = stream.Position; - stream.Read(header, 0, maxHeaderSize); - stream.Position = startPosition; - format = this.Configuration.ImageFormats.FirstOrDefault(x => x.IsSupportedFileFormat(header)); - } - finally - { - ArrayPool.Shared.Return(header); - } - - if (format == null) - { - return false; - } - - format.Decoder.Decode(this, stream, options); - this.CurrentImageFormat = format; - return true; - } } } \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TColor}.cs b/src/ImageSharp/Image/PixelAccessor{TColor}.cs index a106765652..f5393cfb38 100644 --- a/src/ImageSharp/Image/PixelAccessor{TColor}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TColor}.cs @@ -15,7 +15,7 @@ namespace ImageSharp /// Provides per-pixel access to generic pixels. /// /// The pixel format. - public sealed unsafe class PixelAccessor : IDisposable + public sealed unsafe class PixelAccessor : IDisposable, IPinnedImageBuffer where TColor : struct, IPixel { /// @@ -37,7 +37,7 @@ namespace ImageSharp /// /// The containing the pixel data. /// - private PinnedBuffer pixelBuffer; + private PinnedImageBuffer pixelBuffer; /// /// Initializes a new instance of the class. @@ -59,7 +59,7 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. public PixelAccessor(int width, int height) - : this(width, height, new PinnedBuffer(width * height)) + : this(width, height, PinnedImageBuffer.CreateClean(width, height)) { } @@ -69,7 +69,7 @@ namespace ImageSharp /// The width of the image represented by the pixel buffer. /// The height of the image represented by the pixel buffer. /// The pixel buffer. - private PixelAccessor(int width, int height, PinnedBuffer pixels) + private PixelAccessor(int width, int height, PinnedImageBuffer pixels) { Guard.NotNull(pixels, nameof(pixels)); Guard.MustBeGreaterThan(width, 0, nameof(width)); @@ -123,6 +123,9 @@ namespace ImageSharp /// public ParallelOptions ParallelOptions { get; } + /// + BufferSpan IPinnedImageBuffer.Span => this.pixelBuffer; + private static BulkPixelOperations Operations => BulkPixelOperations.Instance; /// @@ -148,6 +151,37 @@ namespace ImageSharp } } + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + if (this.isDisposed) + { + return; + } + + // Note disposing is done. + this.isDisposed = true; + + this.pixelBuffer.Dispose(); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SuppressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Resets all the pixels to it's initial value. + /// + public void Reset() + { + Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); + } + /// /// Copy an area of pixels to the image. /// @@ -157,7 +191,7 @@ namespace ImageSharp /// /// Thrown when an unsupported component order value is passed. /// - public void CopyFrom(PixelArea area, int targetY, int targetX = 0) + internal void CopyFrom(PixelArea area, int targetY, int targetX = 0) { this.CheckCoordinates(area, targetX, targetY); @@ -173,7 +207,7 @@ namespace ImageSharp /// /// Thrown when an unsupported component order value is passed. /// - public void CopyTo(PixelArea area, int sourceY, int sourceX = 0) + internal void CopyTo(PixelArea area, int sourceY, int sourceX = 0) { this.CheckCoordinates(area, sourceX, sourceY); @@ -190,7 +224,7 @@ namespace ImageSharp /// /// Thrown when an unsupported component order value is passed. /// - public void SafeCopyTo(PixelArea area, int sourceY, int sourceX = 0) + internal void SafeCopyTo(PixelArea area, int sourceY, int sourceX = 0) { int width = Math.Min(area.Width, this.Width - sourceX); if (width < 1) @@ -207,48 +241,6 @@ namespace ImageSharp this.CopyTo(area, sourceX, sourceY, width, height); } - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - if (this.isDisposed) - { - return; - } - - // Note disposing is done. - this.isDisposed = true; - - this.pixelBuffer.Dispose(); - - // This object will be cleaned up by the Dispose method. - // Therefore, you should call GC.SuppressFinalize to - // take this object off the finalization queue - // and prevent finalization code for this object - // from executing a second time. - GC.SuppressFinalize(this); - } - - /// - /// Resets all the pixels to it's initial value. - /// - public void Reset() - { - Unsafe.InitBlock(this.pixelsBase, 0, (uint)(this.RowStride * this.Height)); - } - - /// - /// Gets a to the row 'y' beginning from the pixel at 'x'. - /// - /// The x coordinate - /// The y coordinate - /// The - internal BufferPointer GetRowPointer(int x, int y) - { - return this.pixelBuffer.Slice((y * this.Width) + x); - } - /// /// Sets the pixel buffer in an unsafe manner. This should not be used unless you know what its doing!!! /// @@ -288,8 +280,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + BufferSpan source = area.GetRowSpan(y); + BufferSpan destination = this.GetRowSpan(targetX, targetY + y); Operations.PackFromZyxBytes(source, destination, width); } @@ -308,8 +300,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + BufferSpan source = area.GetRowSpan(y); + BufferSpan destination = this.GetRowSpan(targetX, targetY + y); Operations.PackFromZyxwBytes(source, destination, width); } @@ -328,8 +320,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + BufferSpan source = area.GetRowSpan(y); + BufferSpan destination = this.GetRowSpan(targetX, targetY + y); Operations.PackFromXyzBytes(source, destination, width); } @@ -348,8 +340,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = area.GetRowPointer(y); - BufferPointer destination = this.GetRowPointer(targetX, targetY + y); + BufferSpan source = area.GetRowSpan(y); + BufferSpan destination = this.GetRowSpan(targetX, targetY + y); Operations.PackFromXyzwBytes(source, destination, width); } } @@ -367,8 +359,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxBytes(source, destination, width); } } @@ -386,8 +378,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToZyxwBytes(source, destination, width); } } @@ -405,8 +397,8 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToXyzBytes(source, destination, width); } } @@ -424,15 +416,15 @@ namespace ImageSharp { for (int y = 0; y < height; y++) { - BufferPointer source = this.GetRowPointer(sourceX, sourceY + y); - BufferPointer destination = area.GetRowPointer(y); + BufferSpan source = this.GetRowSpan(sourceX, sourceY + y); + BufferSpan destination = area.GetRowSpan(y); Operations.ToXyzwBytes(source, destination, width); } } private void SetPixelBufferUnsafe(int width, int height, TColor[] pixels) { - this.SetPixelBufferUnsafe(width, height, new PinnedBuffer(width * height, pixels)); + this.SetPixelBufferUnsafe(width, height, new PinnedImageBuffer(pixels, width, height)); } /// @@ -441,7 +433,7 @@ namespace ImageSharp /// The width. /// The height. /// The pixel buffer - private void SetPixelBufferUnsafe(int width, int height, PinnedBuffer pixels) + private void SetPixelBufferUnsafe(int width, int height, PinnedImageBuffer pixels) { this.pixelBuffer = pixels; this.pixelsBase = (byte*)pixels.Pointer; diff --git a/src/ImageSharp/Image/PixelArea{TColor}.cs b/src/ImageSharp/Image/PixelArea{TColor}.cs index be6debba2f..bd10c9b6b0 100644 --- a/src/ImageSharp/Image/PixelArea{TColor}.cs +++ b/src/ImageSharp/Image/PixelArea{TColor}.cs @@ -5,17 +5,15 @@ namespace ImageSharp { using System; - using System.Buffers; using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; - using System.Runtime.InteropServices; /// /// Represents an area of generic pixels. /// /// The pixel format. - public sealed unsafe class PixelArea : IDisposable + internal sealed unsafe class PixelArea : IDisposable where TColor : struct, IPixel { /// @@ -204,11 +202,11 @@ namespace ImageSharp } /// - /// Gets a to the row y. + /// Gets a to the row y. /// /// The y coordinate - /// The - internal BufferPointer GetRowPointer(int y) + /// The + internal BufferSpan GetRowSpan(int y) { return this.byteBuffer.Slice(y * this.RowStride); } diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index a0766d1edf..79525a8e83 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing /// Allows the application of processors to images. /// /// The pixel format. - public abstract class ImageProcessor : IImageProcessor + internal abstract class ImageProcessor : IImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index de3e764734..c51c0833a0 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -2,7 +2,7 @@ A cross-platform library for the processing of image files; written in C# ImageSharp - 1.0.0-alpha3 + 1.0.0-alpha5 James Jackson-South and contributors netstandard1.3;netstandard1.1 true diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index de1e424769..aed6efa2cb 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -5,6 +5,7 @@ namespace ImageSharp { + using System; using System.Collections.Generic; /// @@ -55,12 +56,16 @@ namespace ImageSharp foreach (ImageProperty property in other.Properties) { - this.Properties.Add(new ImageProperty(property)); + this.Properties.Add(new ImageProperty(property)); } if (other.ExifProfile != null) { - this.ExifProfile = new ExifProfile(other.ExifProfile); + this.ExifProfile = new ExifProfile(other.ExifProfile); + } + else + { + this.ExifProfile = null; } } diff --git a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs index b363286b00..c4a94c5ff1 100644 --- a/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs +++ b/src/ImageSharp/MetaData/Profiles/Exif/ExifProfile.cs @@ -137,7 +137,7 @@ namespace ImageSharp using (MemoryStream memStream = new MemoryStream(this.data, this.thumbnailOffset, this.thumbnailLength)) { - return new Image(memStream); + return Image.Load(memStream); } } diff --git a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs index 2d57957d3f..5555463418 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/BinaryThresholdProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// . The image will be converted to grayscale before thresholding occurs. /// /// The pixel format. - public class BinaryThresholdProcessor : ImageProcessor + internal class BinaryThresholdProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs index ce03c58a8a..50f042bd69 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/ErrorDiffusionDitherProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An that dithers an image using error diffusion. /// /// The pixel format. - public class ErrorDiffusionDitherProcessor : ImageProcessor + internal class ErrorDiffusionDitherProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs index 4126419f25..c7f4d20ace 100644 --- a/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Binarization/OrderedDitherProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// An that dithers an image using error diffusion. /// /// The pixel format. - public class OrderedDitherProcessor : ImageProcessor + internal class OrderedDitherProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs index 0ea821bef6..0214af72de 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/BlackWhiteProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image to their black and white equivalent. /// /// The pixel format. - public class BlackWhiteProcessor : ColorMatrixFilter + internal class BlackWhiteProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs index 15e7c78da1..d1e986a9df 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatomalyProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Achromatomaly (Color desensitivity) color blindness. /// /// The pixel format. - public class AchromatomalyProcessor : ColorMatrixFilter + internal class AchromatomalyProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs index adca0fe985..d17e28dcaf 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/AchromatopsiaProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Achromatopsia (Monochrome) color blindness. /// /// The pixel format. - public class AchromatopsiaProcessor : ColorMatrixFilter + internal class AchromatopsiaProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs index 6de54beeae..7f4529ba47 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranomalyProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Deuteranomaly (Green-Weak) color blindness. /// /// The pixel format. - public class DeuteranomalyProcessor : ColorMatrixFilter + internal class DeuteranomalyProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs index 4729ccc616..493ed2caed 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/DeuteranopiaProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Deuteranopia (Green-Blind) color blindness. /// /// The pixel format. - public class DeuteranopiaProcessor : ColorMatrixFilter + internal class DeuteranopiaProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs index 200fff24d8..ddea24be01 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanomalyProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Protanopia (Red-Weak) color blindness. /// /// The pixel format. - public class ProtanomalyProcessor : ColorMatrixFilter + internal class ProtanomalyProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs index 7c0f03543f..c5446dbe1a 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/ProtanopiaProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Protanopia (Red-Blind) color blindness. /// /// The pixel format. - public class ProtanopiaProcessor : ColorMatrixFilter + internal class ProtanopiaProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs index 63f1fd9eb1..846e9c61a7 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanomalyProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Tritanomaly (Blue-Weak) color blindness. /// /// The pixel format. - public class TritanomalyProcessor : ColorMatrixFilter + internal class TritanomalyProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs index 2200414fee..a0094f71f0 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorBlindness/TritanopiaProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating Tritanopia (Blue-Blind) color blindness. /// /// The pixel format. - public class TritanopiaProcessor : ColorMatrixFilter + internal class TritanopiaProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixFilter.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs similarity index 96% rename from src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixFilter.cs rename to src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs index a37228a9ba..c75da00037 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixFilter.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/ColorMatrixProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// The color matrix filter. Inherit from this class to perform operation involving color matrices. /// /// The pixel format. - public abstract class ColorMatrixFilter : ImageProcessor, IColorMatrixFilter + internal abstract class ColorMatrixProcessor : ImageProcessor, IColorMatrixFilter where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs index bd14da59e5..1f5a0fa7e9 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt601Processor.cs @@ -9,11 +9,11 @@ namespace ImageSharp.Processing.Processors using System.Numerics; /// - /// Converts the colors of the image to Grayscale applying the formula as specified by - /// ITU-R Recommendation BT.601 . + /// Converts the colors of the image to Grayscale applying the formula as specified by ITU-R Recommendation BT.601 + /// . /// /// The pixel format. - public class GrayscaleBt601Processor : ColorMatrixFilter + internal class GrayscaleBt601Processor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs index 925a36c754..048462696a 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/GrayscaleBt709Processor.cs @@ -9,11 +9,11 @@ namespace ImageSharp.Processing.Processors using System.Numerics; /// - /// Converts the colors of the image to Grayscale applying the formula as specified by - /// ITU-R Recommendation BT.709 . + /// Converts the colors of the image to Grayscale applying the formula as specified by ITU-R Recommendation BT.709 + /// . /// /// The pixel format. - public class GrayscaleBt709Processor : ColorMatrixFilter + internal class GrayscaleBt709Processor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs index fdf5ffdb44..0d06c58682 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/HueProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// An to change the hue of an . /// /// The pixel format. - public class HueProcessor : ColorMatrixFilter + internal class HueProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/IColorMatrixFilter.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/IColorMatrixFilter.cs index faee890eb6..57296a0c3b 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/IColorMatrixFilter.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/IColorMatrixFilter.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// alter the image pixels. /// /// The pixel format. - public interface IColorMatrixFilter : IImageProcessor + internal interface IColorMatrixFilter : IImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/KodachromeProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/KodachromeProcessor.cs index fee1684985..8df8efcd19 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/KodachromeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/KodachromeProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating an old Kodachrome camera effect. /// /// The pixel format. - public class KodachromeProcessor : ColorMatrixFilter + internal class KodachromeProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs index 0e614afe8a..b89caec863 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating an old Lomograph effect. /// /// The pixel format. - public class LomographProcessor : ColorMatrixFilter + internal class LomographProcessor : ColorMatrixProcessor where TColor : struct, IPixel { private static readonly TColor VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index 666fe5bc0c..b5a23f8557 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Converts the colors of the image recreating an old Polaroid effect. /// /// The pixel format. - public class PolaroidProcessor : ColorMatrixFilter + internal class PolaroidProcessor : ColorMatrixProcessor where TColor : struct, IPixel { private static TColor veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/SaturationProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/SaturationProcessor.cs index d63326385c..371294dd56 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/SaturationProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/SaturationProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// An to change the saturation of an . /// /// The pixel format. - public class SaturationProcessor : ColorMatrixFilter + internal class SaturationProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/SepiaProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/SepiaProcessor.cs index d8fdc6cd1a..49a071bd98 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/SepiaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/SepiaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// The formula used matches the svg specification. /// /// The pixel format. - public class SepiaProcessor : ColorMatrixFilter + internal class SepiaProcessor : ColorMatrixProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs index 3597ba7de7..7ffca534cd 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Processing.Processors /// Applies a Box blur sampler to the image. /// /// The pixel format. - public class BoxBlurProcessor : ImageProcessor + internal class BoxBlurProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs index d104f5d351..fa06a863ec 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2DProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that uses two one-dimensional matrices to perform convolution against an image. /// /// The pixel format. - public class Convolution2DProcessor : ImageProcessor + internal class Convolution2DProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -108,9 +108,9 @@ namespace ImageSharp.Processing.Processors } } - float red = (float)Math.Sqrt((rX * rX) + (rY * rY)); - float green = (float)Math.Sqrt((gX * gX) + (gY * gY)); - float blue = (float)Math.Sqrt((bX * bX) + (bY * bY)); + float red = MathF.Sqrt((rX * rX) + (rY * rY)); + float green = MathF.Sqrt((gX * gX) + (gY * gY)); + float blue = MathF.Sqrt((bX * bX) + (bY * bY)); TColor packed = default(TColor); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs index 1d118443c3..45906a46fc 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that uses two one-dimensional matrices to perform two-pass convolution against an image. /// /// The pixel format. - public class Convolution2PassProcessor : ImageProcessor + internal class Convolution2PassProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs index 6b5b6d3fe4..3ab95c4ce9 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/ConvolutionProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that uses a 2 dimensional matrix to perform convolution against an image. /// /// The pixel format. - public class ConvolutionProcessor : ImageProcessor + internal class ConvolutionProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs index a8c786f712..b5c6816569 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetector2DProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that detects edges within an image using two one-dimensional matrices. /// /// The pixel format. - public abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor + internal abstract class EdgeDetector2DProcessor : ImageProcessor, IEdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs index eb8491d4cd..e92c2d1093 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorCompassProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that detects edges within an image using a eight two dimensional matrices. /// /// The pixel format. - public abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor + internal abstract class EdgeDetectorCompassProcessor : ImageProcessor, IEdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs index a963bb5780..d8b491faf5 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/EdgeDetectorProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Processing.Processors /// Defines a sampler that detects edges within an image using a single two dimensional matrix. /// /// The pixel format. - public abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor + internal abstract class EdgeDetectorProcessor : ImageProcessor, IEdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs index 6456cc073c..20e7b1b176 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KayyaliProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class KayyaliProcessor : EdgeDetector2DProcessor + internal class KayyaliProcessor : EdgeDetector2DProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs index 90b3fc4b4c..1b88a2200e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/KirschProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class KirschProcessor : EdgeDetectorCompassProcessor + internal class KirschProcessor : EdgeDetectorCompassProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs index c8823efee8..ec6963b1ea 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian3X3Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class Laplacian3X3Processor : EdgeDetectorProcessor + internal class Laplacian3X3Processor : EdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs index 3aad6d1ef0..cc68c4fb71 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/Laplacian5X5Processor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class Laplacian5X5Processor : EdgeDetectorProcessor + internal class Laplacian5X5Processor : EdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs index a7da76e136..f0944e6818 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/LaplacianOfGaussianProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class LaplacianOfGaussianProcessor : EdgeDetectorProcessor + internal class LaplacianOfGaussianProcessor : EdgeDetectorProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs index fea9844183..fdb63d837e 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/PrewittProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class PrewittProcessor : EdgeDetector2DProcessor + internal class PrewittProcessor : EdgeDetector2DProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs index 329a995c09..d9c5f5d213 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobertsCrossProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class RobertsCrossProcessor : EdgeDetector2DProcessor + internal class RobertsCrossProcessor : EdgeDetector2DProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs index 60726deab5..681d983c45 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/RobinsonProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class RobinsonProcessor : EdgeDetectorCompassProcessor + internal class RobinsonProcessor : EdgeDetectorCompassProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs index ed45ba0acf..c1e83b7f97 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/ScharrProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class ScharrProcessor : EdgeDetector2DProcessor + internal class ScharrProcessor : EdgeDetector2DProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs index 3d2c583a7a..0c13fa3d24 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/EdgeDetection/SobelProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// The pixel format. [SuppressMessage("ReSharper", "StaticMemberInGenericType", Justification = "We want to use only one instance of each array field for each generic type.")] - public class SobelProcessor : EdgeDetector2DProcessor + internal class SobelProcessor : EdgeDetector2DProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs index ddaffc9b48..65a137e359 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Processing.Processors /// Applies a Gaussian blur sampler to the image. /// /// The pixel format. - public class GaussianBlurProcessor : ImageProcessor + internal class GaussianBlurProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs index 6541b73800..bb3dc6f999 100644 --- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs @@ -11,7 +11,7 @@ namespace ImageSharp.Processing.Processors /// Applies a Gaussian sharpening sampler to the image. /// /// The pixel format. - public class GaussianSharpenProcessor : ImageProcessor + internal class GaussianSharpenProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index 11af92ea75..ce48aea1ad 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An to change the alpha component of an . /// /// The pixel format. - public class AlphaProcessor : ImageProcessor + internal class AlphaProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index d6d209dc7e..d928eb1a47 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Sets the background color of the image. /// /// The pixel format. - public class BackgroundColorProcessor : ImageProcessor + internal class BackgroundColorProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -77,7 +77,7 @@ namespace ImageSharp.Processing.Processors color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F); } - if (Math.Abs(a) < Constants.Epsilon) + if (MathF.Abs(a) < Constants.Epsilon) { color = backgroundColor; } diff --git a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs index 566f2c6d74..84df5e89e8 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BrightnessProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An to change the brightness of an . /// /// The pixel format. - public class BrightnessProcessor : ImageProcessor + internal class BrightnessProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs index f4acc42bfc..042e396996 100644 --- a/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/ContrastProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An to change the contrast of an . /// /// The pixel format. - public class ContrastProcessor : ImageProcessor + internal class ContrastProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs index 641cd1b47d..4358e89460 100644 --- a/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/InvertProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An to invert the colors of an . /// /// The pixel format. - public class InvertProcessor : ImageProcessor + internal class InvertProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs index 6b9558ad2c..957955c6c4 100644 --- a/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/OilPaintingProcessor.cs @@ -14,7 +14,7 @@ namespace ImageSharp.Processing.Processors /// /// Adapted from by Dewald Esterhuizen. /// The pixel format. - public class OilPaintingProcessor : ImageProcessor + internal class OilPaintingProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -139,9 +139,9 @@ namespace ImageSharp.Processing.Processors } } - float red = Math.Abs(redBin[maxIndex] / maxIntensity); - float green = Math.Abs(greenBin[maxIndex] / maxIntensity); - float blue = Math.Abs(blueBin[maxIndex] / maxIntensity); + float red = MathF.Abs(redBin[maxIndex] / maxIntensity); + float green = MathF.Abs(greenBin[maxIndex] / maxIntensity); + float blue = MathF.Abs(blueBin[maxIndex] / maxIntensity); TColor packed = default(TColor); packed.PackFromVector4(new Vector4(red, green, blue, sourcePixels[x, y].ToVector4().W)); diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs index d9d78dfa8c..818b1f5137 100644 --- a/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/PixelateProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An to pixelate the colors of an . /// /// The pixel format. - public class PixelateProcessor : ImageProcessor + internal class PixelateProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 9043b66cb8..6eeb7398aa 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An that applies a radial glow effect an . /// /// The pixel format. - public class GlowProcessor : ImageProcessor + internal class GlowProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -44,7 +44,7 @@ namespace ImageSharp.Processing.Processors int endX = sourceRectangle.Right; TColor glowColor = this.GlowColor; Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); - float maxDistance = this.Radius > 0 ? Math.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; // Align start/end positions. int minX = Math.Max(0, startX); diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index f1872d0b81..40d6d94ac9 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// An that applies a radial vignette effect to an . /// /// The pixel format. - public class VignetteProcessor : ImageProcessor + internal class VignetteProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -49,9 +49,9 @@ namespace ImageSharp.Processing.Processors int endX = sourceRectangle.Right; TColor vignetteColor = this.VignetteColor; Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); - float rX = this.RadiusX > 0 ? Math.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; - float rY = this.RadiusY > 0 ? Math.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; - float maxDistance = (float)Math.Sqrt((rX * rX) + (rY * rY)); + 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)); // Align start/end positions. int minX = Math.Max(0, startX); diff --git a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs deleted file mode 100644 index 2190254f0d..0000000000 --- a/src/ImageSharp/Processing/Processors/Transforms/CompandingResizeProcessor.cs +++ /dev/null @@ -1,171 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - /// - /// Provides methods that allow the resizing of images using various algorithms. - /// This version will expand and compress the image to and from a linear color space during processing. - /// - /// The pixel format. - public class CompandingResizeProcessor : ResamplingWeightedProcessor - where TColor : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The sampler to perform the resize operation. - /// The target width. - /// The target height. - public CompandingResizeProcessor(IResampler sampler, int width, int height) - : base(sampler, width, height, new Rectangle(0, 0, width, height)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The sampler to perform the resize operation. - /// The target width. - /// The target height. - /// - /// The structure that specifies the portion of the target image object to draw to. - /// - public CompandingResizeProcessor(IResampler sampler, int width, int height, Rectangle resizeRectangle) - : base(sampler, width, height, resizeRectangle) - { - } - - /// - public override bool Compand { get; set; } = true; - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - // Jump out, we'll deal with that later. - if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) - { - return; - } - - int width = this.Width; - int height = this.Height; - int sourceX = sourceRectangle.X; - int sourceY = sourceRectangle.Y; - int startY = this.ResizeRectangle.Y; - int endY = this.ResizeRectangle.Bottom; - int startX = this.ResizeRectangle.X; - int endX = this.ResizeRectangle.Right; - - int minX = Math.Max(0, startX); - int maxX = Math.Min(width, endX); - int minY = Math.Max(0, startY); - int maxY = Math.Min(height, endY); - - if (this.Sampler is NearestNeighborResampler) - { - // Scaling factors - float widthFactor = sourceRectangle.Width / (float)this.ResizeRectangle.Width; - float heightFactor = sourceRectangle.Height / (float)this.ResizeRectangle.Height; - - using (PixelAccessor 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); - - for (int x = minX; x < maxX; x++) - { - // X coordinates of source points - targetPixels[x, y] = sourcePixels[(int)(((x - startX) * widthFactor) + sourceX), originY]; - } - }); - } - - // Break out now. - source.SwapPixelsBuffers(targetPixels); - return; - } - } - - // Interpolate the image using the calculated weights. - // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm - // First process the columns. Since we are not using multiple threads startY and endY - // are the upper and lower bounds of the source rectangle. - using (PixelAccessor targetPixels = new PixelAccessor(width, height)) - { - using (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) - { - Parallel.For( - 0, - sourceRectangle.Bottom, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) - { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4().Expand() * xw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - firstPassPixels[x, y] = d; - } - }); - - // Now process the rows. - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; - - for (int x = 0; x < width; x++) - { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) - { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4().Expand() * yw.Value; - } - - TColor d = default(TColor); - d.PackFromVector4(destination.Compress()); - targetPixels[x, y] = d; - } - }); - } - - source.SwapPixelsBuffers(targetPixels); - } - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs index 88f23a7fd6..7d473c55ed 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/CropProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Provides methods to allow the cropping of an image. /// /// The pixel format. - public class CropProcessor : ImageProcessor + internal class CropProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs index d150c59d49..049fbf2de0 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/EntropyCropProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// entropy. /// /// The pixel format. - public class EntropyCropProcessor : ImageProcessor + internal class EntropyCropProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs index da4f2b3a80..290d81799a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/FlipProcessor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Provides methods that allow the flipping of an image around its center point. /// /// The pixel format. - public class FlipProcessor : ImageProcessor + internal class FlipProcessor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs index 7e87576870..0c290a9b62 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Matrix3x2Processor.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Processing.Processors /// Provides methods to transform an image using a . /// /// The pixel format. - public abstract class Matrix3x2Processor : ImageProcessor + internal abstract class Matrix3x2Processor : ImageProcessor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs new file mode 100644 index 0000000000..99b143de67 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.Weights.cs @@ -0,0 +1,176 @@ +namespace ImageSharp.Processing.Processors +{ + using System; + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Conains the definition of and . + /// + internal abstract partial class ResamplingWeightedProcessor + { + /// + /// Points to a collection of of weights allocated in . + /// + internal unsafe struct WeightsWindow + { + /// + /// The local left index position + /// + public int Left; + + /// + /// The span of weights pointing to . + /// + // TODO: In the case of switching to official System.Memory and System.Buffers.Primitives this should be System.Buffers.Buffer (formerly Memory), because Span is stack-only! + // see: https://github.com/dotnet/corefxlab/blob/873d35ebed7264e2f9adb556f3b61bebc12395d6/docs/specs/memory.md + public BufferSpan Span; + + /// + /// Initializes a new instance of the struct. + /// + /// The local left index + /// The span + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal WeightsWindow(int left, BufferSpan span) + { + this.Left = left; + this.Span = span; + } + + /// + /// Gets an unsafe float* pointer to the beginning of . + /// + public float* Ptr => (float*)this.Span.PointerAtOffset; + + /// + /// Gets the lenghth of the weights window + /// + public int Length => this.Span.Length; + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// + /// The input span of vectors + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; + vecPtr += left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float weight = horizontalValues[i]; + result += (*vecPtr) * weight; + vecPtr++; + } + + return result; + } + + /// + /// Computes the sum of vectors in 'rowSpan' weighted by weight values, pointed by this instance. + /// Applies to all input vectors. + /// + /// The input span of vectors + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeExpandedWeightedRowSum(BufferSpan rowSpan) + { + float* horizontalValues = this.Ptr; + int left = this.Left; + Vector4* vecPtr = (Vector4*)rowSpan.PointerAtOffset; + vecPtr += left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float weight = horizontalValues[i]; + result += (*vecPtr).Expand() * weight; + vecPtr++; + } + + return result; + } + + /// + /// Computes the sum of vectors in 'firstPassPixels' at a column pointed by 'x', + /// weighted by weight values, pointed by this instance. + /// + /// The buffer of input vectors in row first order + /// The column position + /// The weighted sum + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Vector4 ComputeWeightedColumnSum(PinnedImageBuffer firstPassPixels, int x) + { + float* verticalValues = this.Ptr; + int left = this.Left; + + // Destination color components + Vector4 result = Vector4.Zero; + + for (int i = 0; i < this.Length; i++) + { + float yw = verticalValues[i]; + int index = left + i; + result += firstPassPixels[x, index] * yw; + } + + return result; + } + } + + /// + /// Holds the values in an optimized contigous memory region. + /// + internal class WeightsBuffer : IDisposable + { + private PinnedImageBuffer dataBuffer; + + /// + /// Initializes a new instance of the class. + /// + /// The size of the source window + /// The size of the destination window + public WeightsBuffer(int sourceSize, int destinationSize) + { + this.dataBuffer = PinnedImageBuffer.CreateClean(sourceSize, destinationSize); + this.Weights = new WeightsWindow[destinationSize]; + } + + /// + /// Gets the calculated values. + /// + public WeightsWindow[] Weights { get; } + + /// + /// Disposes instance releasing it's backing buffer. + /// + public void Dispose() + { + this.dataBuffer.Dispose(); + } + + /// + /// Slices a weights value at the given positions. + /// + /// The index in destination buffer + /// The local left index value + /// The local right index value + /// The weights + public WeightsWindow GetWeightsWindow(int destIdx, int leftIdx, int rightIdx) + { + BufferSpan span = this.dataBuffer.GetRowSpan(destIdx).Slice(leftIdx, rightIdx - leftIdx + 1); + return new WeightsWindow(leftIdx, span); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs index 25a7a4efd6..1374e58156 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResamplingWeightedProcessor.cs @@ -6,13 +6,15 @@ namespace ImageSharp.Processing.Processors { using System; + using System.Buffers; + using System.Runtime.InteropServices; /// /// Provides methods that allow the resizing of images using various algorithms. /// Adapted from /// /// The pixel format. - public abstract class ResamplingWeightedProcessor : ImageProcessor + internal abstract partial class ResamplingWeightedProcessor : ImageProcessor where TColor : struct, IPixel { /// @@ -59,32 +61,21 @@ namespace ImageSharp.Processing.Processors /// /// Gets or sets the horizontal weights. /// - protected Weights[] HorizontalWeights { get; set; } + protected WeightsBuffer HorizontalWeights { get; set; } /// /// Gets or sets the vertical weights. /// - protected Weights[] VerticalWeights { get; set; } - - /// - protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) - { - if (!(this.Sampler is NearestNeighborResampler)) - { - this.HorizontalWeights = this.PrecomputeWeights(this.ResizeRectangle.Width, sourceRectangle.Width); - this.VerticalWeights = this.PrecomputeWeights(this.ResizeRectangle.Height, sourceRectangle.Height); - } - } + protected WeightsBuffer VerticalWeights { get; set; } /// /// Computes the weights to apply at each pixel when resizing. /// - /// The destination section size. - /// The source section size. - /// - /// The . - /// - protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize) + /// The destination size + /// The source size + /// The + // TODO: Made internal to simplify experimenting with weights data. Make it protected again when finished figuring out how to optimize all the stuff! + internal unsafe WeightsBuffer PrecomputeWeights(int destinationSize, int sourceSize) { float ratio = (float)sourceSize / destinationSize; float scale = ratio; @@ -95,8 +86,8 @@ namespace ImageSharp.Processing.Processors } IResampler sampler = this.Sampler; - float radius = (float)Math.Ceiling(scale * sampler.Radius); - Weights[] result = new Weights[destinationSize]; + float radius = MathF.Ceiling(scale * sampler.Radius); + WeightsBuffer result = new WeightsBuffer(sourceSize, destinationSize); for (int i = 0; i < destinationSize; i++) { @@ -116,67 +107,55 @@ namespace ImageSharp.Processing.Processors } float sum = 0; - result[i] = new Weights(); - Weight[] weights = new Weight[right - left + 1]; + + WeightsWindow ws = result.GetWeightsWindow(i, left, right); + result.Weights[i] = ws; + + float* weights = ws.Ptr; for (int j = left; j <= right; j++) { float weight = sampler.GetValue((j - center) / scale); sum += weight; - weights[j - left] = new Weight(j, weight); + weights[j - left] = weight; } // Normalise, best to do it here rather than in the pixel loop later on. if (sum > 0) { - for (int w = 0; w < weights.Length; w++) + for (int w = 0; w < ws.Length; w++) { - weights[w].Value = weights[w].Value / sum; + weights[w] = weights[w] / sum; } } - - result[i].Values = weights; } return result; } - /// - /// Represents the weight to be added to a scaled pixel. - /// - protected struct Weight + /// + protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) { - /// - /// Initializes a new instance of the struct. - /// - /// The index. - /// The value. - public Weight(int index, float value) + if (!(this.Sampler is NearestNeighborResampler)) { - this.Index = index; - this.Value = value; - } - - /// - /// Gets the pixel index. - /// - public int Index { get; } + this.HorizontalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Width, + sourceRectangle.Width); - /// - /// Gets or sets the result of the interpolation algorithm. - /// - public float Value { get; set; } + this.VerticalWeights = this.PrecomputeWeights( + this.ResizeRectangle.Height, + sourceRectangle.Height); + } } - /// - /// Represents a collection of weights and their sum. - /// - protected class Weights + /// + protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - /// - /// Gets or sets the values. - /// - public Weight[] Values { get; set; } + base.AfterApply(source, sourceRectangle); + this.HorizontalWeights?.Dispose(); + this.HorizontalWeights = null; + this.VerticalWeights?.Dispose(); + this.VerticalWeights = null; } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 9ec804aa4f..944e245ac8 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -12,11 +12,8 @@ namespace ImageSharp.Processing.Processors /// /// Provides methods that allow the resizing of images using various algorithms. /// - /// - /// This version and the have been separated out to improve performance. - /// /// The pixel format. - public class ResizeProcessor : ResamplingWeightedProcessor + internal class ResizeProcessor : ResamplingWeightedProcessor where TColor : struct, IPixel { /// @@ -45,7 +42,7 @@ namespace ImageSharp.Processing.Processors } /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + protected override unsafe void OnApply(ImageBase source, Rectangle sourceRectangle) { // Jump out, we'll deal with that later. if (source.Width == this.Width && source.Height == this.Height && sourceRectangle == this.ResizeRectangle) @@ -104,36 +101,49 @@ namespace ImageSharp.Processing.Processors // A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm // First process the columns. Since we are not using multiple threads startY and endY // 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 (PixelAccessor sourcePixels = source.Lock()) - using (PixelAccessor firstPassPixels = new PixelAccessor(width, source.Height)) + using (PinnedImageBuffer firstPassPixels = new PinnedImageBuffer(width, source.Height)) { + firstPassPixels.Clear(); + Parallel.For( 0, sourceRectangle.Bottom, this.ParallelOptions, y => - { - for (int x = minX; x < maxX; x++) { - // Ensure offsets are normalised for cropping and padding. - Weight[] horizontalValues = this.HorizontalWeights[x - startX].Values; - - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < horizontalValues.Length; i++) + // TODO: Without Parallel.For() this buffer object could be reused: + using (PinnedBuffer tempRowBuffer = new PinnedBuffer(sourcePixels.Width)) { - Weight xw = horizontalValues[i]; - destination += sourcePixels[xw.Index + sourceX, y].ToVector4() * xw.Value; + BufferSpan sourceRow = sourcePixels.GetRowSpan(y); + + BulkPixelOperations.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); + } + } + else + { + for (int x = minX; x < maxX; x++) + { + WeightsWindow window = this.HorizontalWeights.Weights[x - startX]; + firstPassPixels[x, y] = window.ComputeWeightedRowSum(tempRowBuffer); + } + } } - - TColor d = default(TColor); - d.PackFromVector4(destination); - firstPassPixels[x, y] = d; - } - }); + }); // Now process the rows. Parallel.For( @@ -143,22 +153,31 @@ namespace ImageSharp.Processing.Processors y => { // Ensure offsets are normalised for cropping and padding. - Weight[] verticalValues = this.VerticalWeights[y - startY].Values; + WeightsWindow window = this.VerticalWeights.Weights[y - startY]; - for (int x = 0; x < width; x++) + if (this.Compand) { - // Destination color components - Vector4 destination = Vector4.Zero; - - for (int i = 0; i < verticalValues.Length; i++) + for (int x = 0; x < width; x++) { - Weight yw = verticalValues[i]; - destination += firstPassPixels[x, yw.Index + sourceY].ToVector4() * yw.Value; + // Destination color components + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); + destination = destination.Compress(); + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; } + } + else + { + for (int x = 0; x < width; x++) + { + // Destination color components + Vector4 destination = window.ComputeWeightedColumnSum(firstPassPixels, x); - TColor d = default(TColor); - d.PackFromVector4(destination); - targetPixels[x, y] = d; + TColor d = default(TColor); + d.PackFromVector4(destination); + targetPixels[x, y] = d; + } } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index 22fbb895c1..16e0b6635f 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Provides methods that allow the rotating of images. /// /// The pixel format. - public class RotateProcessor : Matrix3x2Processor + internal class RotateProcessor : Matrix3x2Processor where TColor : struct, IPixel { /// @@ -71,7 +71,7 @@ namespace ImageSharp.Processing.Processors /// protected override void BeforeApply(ImageBase source, Rectangle sourceRectangle) { - if (Math.Abs(this.Angle) < Constants.Epsilon || Math.Abs(this.Angle - 90) < Constants.Epsilon || Math.Abs(this.Angle - 180) < Constants.Epsilon || Math.Abs(this.Angle - 270) < Constants.Epsilon) + if (MathF.Abs(this.Angle) < Constants.Epsilon || MathF.Abs(this.Angle - 90) < Constants.Epsilon || MathF.Abs(this.Angle - 180) < Constants.Epsilon || MathF.Abs(this.Angle - 270) < Constants.Epsilon) { return; } @@ -90,25 +90,25 @@ namespace ImageSharp.Processing.Processors /// The private bool OptimizedApply(ImageBase source) { - if (Math.Abs(this.Angle) < Constants.Epsilon) + if (MathF.Abs(this.Angle) < Constants.Epsilon) { // No need to do anything so return. return true; } - if (Math.Abs(this.Angle - 90) < Constants.Epsilon) + if (MathF.Abs(this.Angle - 90) < Constants.Epsilon) { this.Rotate90(source); return true; } - if (Math.Abs(this.Angle - 180) < Constants.Epsilon) + if (MathF.Abs(this.Angle - 180) < Constants.Epsilon) { this.Rotate180(source); return true; } - if (Math.Abs(this.Angle - 270) < Constants.Epsilon) + if (MathF.Abs(this.Angle - 270) < Constants.Epsilon) { this.Rotate270(source); return true; diff --git a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs index c6afbedcce..5fe3f7d958 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/SkewProcessor.cs @@ -13,7 +13,7 @@ namespace ImageSharp.Processing.Processors /// Provides methods that allow the skewing of images. /// /// The pixel format. - public class SkewProcessor : Matrix3x2Processor + internal class SkewProcessor : Matrix3x2Processor where TColor : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs index 3dc37e52d7..4be938c399 100644 --- a/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs +++ b/src/ImageSharp/Processing/Transforms/Options/ResizeHelper.cs @@ -65,7 +65,7 @@ namespace ImageSharp.Processing return new Rectangle(0, 0, source.Width, source.Height); } - double ratio; + float ratio; int sourceWidth = source.Width; int sourceHeight = source.Height; @@ -75,8 +75,8 @@ namespace ImageSharp.Processing int destinationHeight = height; // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); if (percentHeight < percentWidth) { @@ -84,7 +84,7 @@ namespace ImageSharp.Processing if (options.CenterCoordinates.Any()) { - double center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); + float center = -(ratio * sourceHeight) * options.CenterCoordinates.First(); destinationY = (int)center + (height / 2); if (destinationY > 0) @@ -117,7 +117,7 @@ namespace ImageSharp.Processing } } - destinationHeight = (int)Math.Ceiling(sourceHeight * percentWidth); + destinationHeight = (int)MathF.Ceiling(sourceHeight * percentWidth); } else { @@ -125,7 +125,7 @@ namespace ImageSharp.Processing if (options.CenterCoordinates.Any()) { - double center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; + float center = -(ratio * sourceWidth) * options.CenterCoordinates.ToArray()[1]; destinationX = (int)center + (width / 2); if (destinationX > 0) @@ -158,7 +158,7 @@ namespace ImageSharp.Processing } } - destinationWidth = (int)Math.Ceiling(sourceWidth * percentHeight); + destinationWidth = (int)MathF.Ceiling(sourceWidth * percentHeight); } return new Rectangle(destinationX, destinationY, destinationWidth, destinationHeight); @@ -184,7 +184,7 @@ namespace ImageSharp.Processing return new Rectangle(0, 0, source.Width, source.Height); } - double ratio; + float ratio; int sourceWidth = source.Width; int sourceHeight = source.Height; @@ -194,8 +194,8 @@ namespace ImageSharp.Processing int destinationHeight = height; // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); + float percentHeight = MathF.Abs(height / (float)sourceHeight); + float percentWidth = MathF.Abs(width / (float)sourceWidth); if (percentHeight < percentWidth) { @@ -269,8 +269,8 @@ namespace ImageSharp.Processing int sourceHeight = source.Height; // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)sourceHeight); - double percentWidth = Math.Abs(width / (double)sourceWidth); + 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); @@ -350,12 +350,12 @@ namespace ImageSharp.Processing int destinationHeight = height; // Fractional variants for preserving aspect ratio. - double percentHeight = Math.Abs(height / (double)source.Height); - double percentWidth = Math.Abs(width / (double)source.Width); + float percentHeight = MathF.Abs(height / (float)source.Height); + float percentWidth = MathF.Abs(width / (float)source.Width); - // Integers must be cast to doubles to get needed precision - double ratio = (double)options.Size.Height / options.Size.Width; - double sourceRatio = (double)source.Height / 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; if (sourceRatio < ratio) { @@ -397,7 +397,7 @@ namespace ImageSharp.Processing return new Rectangle(0, 0, source.Width, source.Height); } - double sourceRatio = (double)source.Height / source.Width; + float sourceRatio = (float)source.Height / source.Width; // Find the shortest distance to go. int widthDiff = source.Width - width; diff --git a/src/ImageSharp/Processing/Transforms/Resize.cs b/src/ImageSharp/Processing/Transforms/Resize.cs index ab256c4ae2..1952aa1a7a 100644 --- a/src/ImageSharp/Processing/Transforms/Resize.cs +++ b/src/ImageSharp/Processing/Transforms/Resize.cs @@ -156,16 +156,8 @@ namespace ImageSharp Guard.MustBeGreaterThan(width, 0, nameof(width)); Guard.MustBeGreaterThan(height, 0, nameof(height)); - ResamplingWeightedProcessor processor; - - if (compand) - { - processor = new CompandingResizeProcessor(sampler, width, height, targetRectangle); - } - else - { - processor = new ResizeProcessor(sampler, width, height, targetRectangle); - } + ResizeProcessor processor = + new ResizeProcessor(sampler, width, height, targetRectangle) { Compand = compand }; source.ApplyProcessor(processor, sourceRectangle); return source; diff --git a/src/ImageSharp/Quantizers/IQuantizer.cs b/src/ImageSharp/Quantizers/IQuantizer.cs index 9ee3072666..88f273e9b6 100644 --- a/src/ImageSharp/Quantizers/IQuantizer.cs +++ b/src/ImageSharp/Quantizers/IQuantizer.cs @@ -5,8 +5,6 @@ namespace ImageSharp.Quantizers { - using System; - using ImageSharp.Dithering; /// diff --git a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs index c047e1af48..2590f297eb 100644 --- a/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/OctreeQuantizer.cs @@ -7,6 +7,7 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; + using System.Runtime.CompilerServices; /// /// Encapsulates methods to calculate the color palette if an image using an Octree pattern. @@ -16,6 +17,11 @@ namespace ImageSharp.Quantizers public sealed class OctreeQuantizer : Quantizer where TColor : struct, IPixel { + /// + /// A lookup table for colors + /// + private readonly Dictionary colorMap = new Dictionary(); + /// /// The pixel buffer, used to reduce allocations. /// @@ -31,6 +37,11 @@ namespace ImageSharp.Quantizers /// private int colors; + /// + /// The reduced image palette + /// + private TColor[] palette; + /// /// Initializes a new instance of the class. /// @@ -52,6 +63,58 @@ namespace ImageSharp.Quantizers return base.Quantize(image, maxColors); } + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + protected override void SecondPass(PixelAccessor 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. + TColor sourcePixel = source[0, 0]; + TColor previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(sourcePixel); + TColor[] colorPalette = this.GetPalette(); + TColor transformedPixel = colorPalette[pixelValue]; + + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = source[x, y]; + + // 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. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = colorPalette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } + } + /// /// Process the pixel in the first pass of the algorithm /// @@ -69,26 +132,39 @@ namespace ImageSharp.Quantizers } /// - /// Override this to process the pixel in the second pass of the algorithm + /// Retrieve the palette for the quantized image. /// - /// The pixel to quantize /// - /// The quantized value + /// The new color palette /// - protected override byte QuantizePixel(TColor pixel) + protected override TColor[] GetPalette() { - return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); + if (this.palette == null) + { + this.palette = this.octree.Palletize(Math.Max(this.colors, 1)); + } + + return this.palette; } /// - /// Retrieve the palette for the quantized image. + /// Process the pixel in the second pass of the algorithm /// + /// The pixel to quantize /// - /// The new color palette + /// The quantized value /// - protected override TColor[] GetPalette() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(TColor pixel) { - return this.octree.Palletize(Math.Max(this.colors, 1)); + if (this.Dither) + { + // The colors have changed so we need to use Euclidean distance caclulation to find the closest value. + // This palette can never be null here. + return this.GetClosestColor(pixel, this.palette, this.colorMap); + } + + return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); } /// diff --git a/src/ImageSharp/Quantizers/Octree/Quantizer.cs b/src/ImageSharp/Quantizers/Octree/Quantizer.cs index 8af638de35..ccf8da3df6 100644 --- a/src/ImageSharp/Quantizers/Octree/Quantizer.cs +++ b/src/ImageSharp/Quantizers/Octree/Quantizer.cs @@ -9,7 +9,6 @@ namespace ImageSharp.Quantizers using System.Collections.Generic; using System.Numerics; using System.Runtime.CompilerServices; - using ImageSharp.Dithering; /// @@ -19,21 +18,11 @@ namespace ImageSharp.Quantizers public abstract class Quantizer : IDitheredQuantizer where TColor : struct, IPixel { - /// - /// A lookup table for colors - /// - private readonly Dictionary colorMap = new Dictionary(); - /// /// Flag used to indicate whether a single pass or two passes are needed for quantization. /// private readonly bool singlePass; - /// - /// The reduced image palette - /// - private TColor[] palette; - /// /// Initializes a new instance of the class. /// @@ -65,6 +54,7 @@ namespace ImageSharp.Quantizers int height = image.Height; int width = image.Width; byte[] quantizedPixels = new byte[width * height]; + TColor[] colorPalette; using (PixelAccessor pixels = image.Lock()) { @@ -76,8 +66,8 @@ namespace ImageSharp.Quantizers this.FirstPass(pixels, width, height); } - // Get the palette - this.palette = this.GetPalette(); + // Collect the palette. Octree requires this to be done before the second pass runs. + colorPalette = this.GetPalette(); if (this.Dither) { @@ -94,7 +84,7 @@ namespace ImageSharp.Quantizers } } - return new QuantizedImage(width, height, this.palette, quantizedPixels); + return new QuantizedImage(width, height, colorPalette, quantizedPixels); } /// @@ -124,25 +114,7 @@ namespace ImageSharp.Quantizers /// The output pixel array /// The width in pixels of the image /// The height in pixels of the image - protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height) - { - for (int y = 0; y < height; y++) - { - // And loop through each column - for (int x = 0; x < width; x++) - { - if (this.Dither) - { - // Apply the dithering matrix - TColor sourcePixel = source[x, y]; - TColor transformedPixel = this.palette[this.GetClosestColor(sourcePixel, this.palette, this.colorMap)]; - this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); - } - - output[(y * source.Width) + x] = this.QuantizePixel(source[x, y]); - } - } - } + protected abstract void SecondPass(PixelAccessor source, byte[] output, int width, int height); /// /// Override this to process the pixel in the first pass of the algorithm @@ -157,16 +129,7 @@ namespace ImageSharp.Quantizers } /// - /// Override this to process the pixel in the second pass of the algorithm - /// - /// The pixel to quantize - /// - /// The quantized value - /// - protected abstract byte QuantizePixel(TColor pixel); - - /// - /// Retrieve the palette for the quantized image + /// Retrieve the palette for the quantized image. Can be called more than once so make sure calls are cached. /// /// /// @@ -184,9 +147,9 @@ namespace ImageSharp.Quantizers protected byte GetClosestColor(TColor pixel, TColor[] colorPalette, Dictionary cache) { // Check if the color is in the lookup table - if (this.colorMap.ContainsKey(pixel)) + if (cache.ContainsKey(pixel)) { - return this.colorMap[pixel]; + return cache[pixel]; } // Not found - loop through the palette and find the nearest match. @@ -208,14 +171,14 @@ namespace ImageSharp.Quantizers leastDistance = distance; // And if it's an exact match, exit the loop - if (Math.Abs(distance) < Constants.Epsilon) + if (MathF.Abs(distance) < Constants.Epsilon) { break; } } // Now I have the index, pop it into the cache for next time - this.colorMap.Add(pixel, colorIndex); + cache.Add(pixel, colorIndex); return colorIndex; } diff --git a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs index 19f10aabb0..dedb5ca238 100644 --- a/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs +++ b/src/ImageSharp/Quantizers/Palette/PaletteQuantizer.cs @@ -7,6 +7,8 @@ namespace ImageSharp.Quantizers { using System; using System.Collections.Generic; + using System.Numerics; + using System.Runtime.CompilerServices; /// /// Encapsulates methods to create a quantized image based upon the given palette. @@ -69,10 +71,56 @@ namespace ImageSharp.Quantizers return base.Quantize(image, maxColors); } - /// - protected override byte QuantizePixel(TColor pixel) + /// + /// Execute a second pass through the bitmap + /// + /// The source image. + /// The output pixel array + /// The width in pixels of the image + /// The height in pixels of the image + protected override void SecondPass(PixelAccessor source, byte[] output, int width, int height) { - return this.GetClosestColor(pixel, this.colors, this.colorMap); + // 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. + TColor sourcePixel = source[0, 0]; + TColor previousPixel = sourcePixel; + byte pixelValue = this.QuantizePixel(sourcePixel); + TColor[] colorPalette = this.GetPalette(); + TColor transformedPixel = colorPalette[pixelValue]; + + for (int y = 0; y < height; y++) + { + // And loop through each column + for (int x = 0; x < width; x++) + { + // Get the pixel. + sourcePixel = source[x, y]; + + // 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. + if (!previousPixel.Equals(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(sourcePixel); + + // And setup the previous pointer + previousPixel = sourcePixel; + + if (this.Dither) + { + transformedPixel = colorPalette[pixelValue]; + } + } + + if (this.Dither) + { + // Apply the dithering matrix. We have to reapply the value now as the original has changed. + this.DitherType.Dither(source, sourcePixel, transformedPixel, x, y, width, height); + } + + output[(y * source.Width) + x] = pixelValue; + } + } } /// @@ -80,5 +128,18 @@ namespace ImageSharp.Quantizers { return this.colors; } + + /// + /// Process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// + /// The quantized value + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private byte QuantizePixel(TColor pixel) + { + return this.GetClosestColor(pixel, this.GetPalette(), this.colorMap); + } } } \ No newline at end of file diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000000..3764e03cb8 --- /dev/null +++ b/src/README.md @@ -0,0 +1,7 @@ +# ImageSharp core libraries + +## Coding conventions + +### Argument ordering + +- When passing a `Configuration` object it should be the first argument \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs new file mode 100644 index 0000000000..e912ea29f6 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromVector4ReferenceVsPointer.cs @@ -0,0 +1,74 @@ +namespace ImageSharp.Benchmarks +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + using ImageSharp; + + /// + /// Compares two implementation candidates for general BulkPixelOperations.ToVector4(): + /// - One iterating with pointers + /// - One iterating with ref locals + /// + public unsafe class PackFromVector4ReferenceVsPointer + { + private PinnedBuffer destination; + + private PinnedBuffer source; + + [Params(16, 128, 1024)] + public int Count { get; set; } + + [Setup] + public void Setup() + { + this.destination = new PinnedBuffer(this.Count); + this.source = new PinnedBuffer(this.Count * 4); + } + + [Cleanup] + public void Cleanup() + { + this.source.Dispose(); + this.destination.Dispose(); + } + + [Benchmark(Baseline = true)] + public void PackUsingPointers() + { + Vector4* sp = (Vector4*)this.source.Pointer; + byte* dp = (byte*)this.destination.Pointer; + int count = this.Count; + int size = sizeof(ImageSharp.Color); + + for (int i = 0; i < count; i++) + { + Vector4 v = Unsafe.Read(sp); + ImageSharp.Color c = default(ImageSharp.Color); + c.PackFromVector4(v); + Unsafe.Write(dp, c); + + sp++; + dp += size; + } + } + + [Benchmark] + public void PackUsingReferences() + { + ref Vector4 sp = ref this.source.Array[0]; + ref ImageSharp.Color dp = ref this.destination.Array[0]; + int count = this.Count; + + for (int i = 0; i < count; i++) + { + dp.PackFromVector4(sp); + + sp = Unsafe.Add(ref sp, 1); + dp = Unsafe.Add(ref dp, 1); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs index a10417b90b..3e60cae4dc 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawBeziers.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(System.Drawing.Color.HotPink, 10); + Pen pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawBeziers(pen, new[] { new PointF(10, 500), new PointF(30, 10), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs index 146def3637..ee97866e24 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawLines.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(System.Drawing.Color.HotPink, 10); + Pen pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawLines(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs index e6c1ac0d65..047cacb421 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/DrawPolygon.cs @@ -27,7 +27,7 @@ namespace ImageSharp.Benchmarks { graphics.InterpolationMode = InterpolationMode.Default; graphics.SmoothingMode = SmoothingMode.AntiAlias; - var pen = new Pen(System.Drawing.Color.HotPink, 10); + Pen pen = new Pen(System.Drawing.Color.HotPink, 10); graphics.DrawPolygon(pen, new[] { new PointF(10, 10), new PointF(550, 50), diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs index 1eafbe077f..782306deb7 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillPolygon.cs @@ -9,6 +9,7 @@ namespace ImageSharp.Benchmarks using System.Drawing.Drawing2D; using System.IO; using System.Numerics; + using SixLabors.Shapes; using BenchmarkDotNet.Attributes; @@ -17,6 +18,15 @@ namespace ImageSharp.Benchmarks public class FillPolygon : BenchmarkBase { + private readonly Polygon shape; + + public FillPolygon() + { + this.shape = new SixLabors.Shapes.Polygon(new LinearLineSegment(new Vector2(10, 10), + new Vector2(550, 50), + new Vector2(200, 400))); + } + [Benchmark(Baseline = true, Description = "System.Drawing Fill Polygon")] public void DrawSolidPolygonSystemDrawing() { @@ -60,5 +70,21 @@ namespace ImageSharp.Benchmarks } } } + + [Benchmark(Description = "ImageSharp Fill Polygon - cached shape")] + public void DrawSolidPolygonCoreCahced() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.Fill( + CoreColor.HotPink, + this.shape); + + using (MemoryStream ms = new MemoryStream()) + { + image.SaveAsBmp(ms); + } + } + } } } \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs index 589ac0cd43..dcd22d6fb4 100644 --- a/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs +++ b/tests/ImageSharp.Benchmarks/Drawing/FillWithPattern.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks using (Graphics graphics = Graphics.FromImage(destination)) { graphics.SmoothingMode = SmoothingMode.AntiAlias; - var brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink); + HatchBrush brush = new HatchBrush(HatchStyle.BackwardDiagonal, System.Drawing.Color.HotPink); graphics.FillRectangle(brush, new Rectangle(0,0, 800,800)); // can't find a way to flood fill with a brush } using (MemoryStream ms = new MemoryStream()) diff --git a/tests/ImageSharp.Benchmarks/General/IterateArray.cs b/tests/ImageSharp.Benchmarks/General/IterateArray.cs new file mode 100644 index 0000000000..48f2316a27 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/General/IterateArray.cs @@ -0,0 +1,70 @@ +namespace ImageSharp.Benchmarks.General +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using BenchmarkDotNet.Attributes; + + public class IterateArray + { + // Usual pinned stuff + private PinnedBuffer buffer; + + // An array that's not pinned by intent! + private Vector4[] array; + + [Params(64, 1024)] + public int Length { get; set; } + + [Setup] + public void Setup() + { + this.buffer = new PinnedBuffer(this.Length); + this.array = new Vector4[this.Length]; + } + + [Benchmark(Baseline = true)] + public Vector4 IterateIndexed() + { + Vector4 sum = new Vector4(); + Vector4[] a = this.array; + + for (int i = 0; i < a.Length; i++) + { + sum += a[i]; + } + return sum; + } + + [Benchmark] + public unsafe Vector4 IterateUsingPointers() + { + Vector4 sum = new Vector4(); + + Vector4* ptr = (Vector4*) this.buffer.Pointer; + Vector4* end = ptr + this.Length; + + for (; ptr < end; ptr++) + { + sum += *ptr; + } + + return sum; + } + + [Benchmark] + public Vector4 IterateUsingReferences() + { + Vector4 sum = new Vector4(); + + ref Vector4 start = ref this.array[0]; + + for (int i = 0; i < this.Length; i++) + { + sum += Unsafe.Add(ref start, i); + } + + return sum; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs index 431bbeb079..acde8e0dbe 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeBmp.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.bmpBytes)) { - using (CoreImage image = new CoreImage(memoryStream)) + using (CoreImage image = CoreImage.Load(memoryStream)) { return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs index 517915bacb..6786cfdc0f 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeFilteredPng.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Benchmarks.Image private Size LoadPng(MemoryStream stream) { - using (Image image = new Image(stream)) + using (Image image = Image.Load(stream)) { return new Size(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs index cb70213dac..a9bb4c7b3a 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeGif.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.gifBytes)) { - using (CoreImage image = new CoreImage(memoryStream)) + using (CoreImage image = CoreImage.Load(memoryStream)) { return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs index cbbe9c9f23..6ce2303703 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpeg.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.jpegBytes)) { - using (CoreImage image = new CoreImage(memoryStream)) + using (CoreImage image = CoreImage.Load(memoryStream)) { return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs index c79d615384..5c3c1e115b 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodeJpegMultiple.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks.Image public void DecodeJpegImageSharp() { this.ForEachStream( - ms => new ImageSharp.Image(ms) + ms => ImageSharp.Image.Load(ms) ); } diff --git a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs index 79c8dbc23e..620a48a3b1 100644 --- a/tests/ImageSharp.Benchmarks/Image/DecodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/DecodePng.cs @@ -43,7 +43,7 @@ namespace ImageSharp.Benchmarks.Image { using (MemoryStream memoryStream = new MemoryStream(this.pngBytes)) { - using (CoreImage image = new CoreImage(memoryStream)) + using (CoreImage image = CoreImage.Load(memoryStream)) { return new CoreSize(image.Width, image.Height); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs index b0a3b44999..6ed5773388 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeBmp.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks.Image if (this.bmpStream == null) { this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); - this.bmpCore = new CoreImage(this.bmpStream); + this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs index 0810f3fe17..fabeba1bde 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeGif.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks.Image if (this.bmpStream == null) { this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); - this.bmpCore = new CoreImage(this.bmpStream); + this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs new file mode 100644 index 0000000000..1318c1674a --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Image/EncodeIndexedPng.cs @@ -0,0 +1,104 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks.Image +{ + using System.IO; + + using BenchmarkDotNet.Attributes; + + using ImageSharp; + using ImageSharp.Formats; + using ImageSharp.Quantizers; + + /// + /// Benchmarks saving png files using different quantizers. System.Drawing cannot save indexed png files so we cannot compare. + /// + public class EncodeIndexedPng : BenchmarkBase + { + // System.Drawing needs this. + private Stream bmpStream; + private Image bmpCore; + + [Params(false)] + public bool LargeImage { get; set; } + + [Setup] + public void ReadImages() + { + if (this.bmpStream == null) + { + string path = this.LargeImage + ? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg" + : "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"; + this.bmpStream = File.OpenRead(path); + this.bmpCore = Image.Load(this.bmpStream); + this.bmpStream.Position = 0; + } + } + + [Cleanup] + public void Cleanup() + { + this.bmpStream.Dispose(); + this.bmpCore.Dispose(); + } + + [Benchmark(Baseline = true, Description = "ImageSharp Octree Png")] + public void PngCoreOctree() + { + using (MemoryStream memoryStream = new MemoryStream()) + { + PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new OctreeQuantizer(), Quality = 256 }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Octree NoDither Png")] + public void PngCoreOctreeNoDIther() + { + using (MemoryStream memoryStream = new MemoryStream()) + { + PngEncoderOptions options = new PngEncoderOptions { Quantizer = new OctreeQuantizer { Dither = false }, Quality = 256 }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Palette Png")] + public void PngCorePalette() + { + using (MemoryStream memoryStream = new MemoryStream()) + { + PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer(), Quality = 256 }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Palette NoDither Png")] + public void PngCorePaletteNoDither() + { + using (MemoryStream memoryStream = new MemoryStream()) + { + PngEncoderOptions options = new PngEncoderOptions { Quantizer = new PaletteQuantizer { Dither = false }, Quality = 256 }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + + [Benchmark(Description = "ImageSharp Wu Png")] + public void PngCoreWu() + { + using (MemoryStream memoryStream = new MemoryStream()) + { + PngEncoderOptions options = new PngEncoderOptions() { Quantizer = new WuQuantizer(), Quality = 256 }; + + this.bmpCore.SaveAsPng(memoryStream, options); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs index f835f9666f..7649812ecc 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodeJpeg.cs @@ -25,7 +25,7 @@ namespace ImageSharp.Benchmarks.Image if (this.bmpStream == null) { this.bmpStream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"); - this.bmpCore = new CoreImage(this.bmpStream); + this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); } diff --git a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs index 0bb0e922c5..4c1feb6c2a 100644 --- a/tests/ImageSharp.Benchmarks/Image/EncodePng.cs +++ b/tests/ImageSharp.Benchmarks/Image/EncodePng.cs @@ -38,7 +38,7 @@ namespace ImageSharp.Benchmarks.Image ? "../ImageSharp.Tests/TestImages/Formats/Jpg/baseline/jpeg420exif.jpg" : "../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp"; this.bmpStream = File.OpenRead(path); - this.bmpCore = new CoreImage(this.bmpStream); + this.bmpCore = CoreImage.Load(this.bmpStream); this.bmpStream.Position = 0; this.bmpDrawing = Image.FromStream(this.bmpStream); } diff --git a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs index 26aad07b8d..a084ca025c 100644 --- a/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs +++ b/tests/ImageSharp.Benchmarks/Image/MultiImageBenchmarkBase.cs @@ -104,13 +104,13 @@ namespace ImageSharp.Benchmarks.Image continue; } - var allFiles = + string[] allFiles = this.SearchPatterns.SelectMany( f => Directory.EnumerateFiles(path, f, SearchOption.AllDirectories) .Where(fn => !this.ExcludeSubstringsInFileNames.Any(w => fn.ToLower().Contains(w)))).ToArray(); - foreach (var fn in allFiles) + foreach (string fn in allFiles) { this.FileNamesToBytes[fn] = File.ReadAllBytes(fn); } @@ -123,13 +123,13 @@ namespace ImageSharp.Benchmarks.Image /// The operation to execute. If the returned object is <see cref="IDisposable"/> it will be disposed protected void ForEachStream(Func operation) { - foreach (var kv in this.FileNames2Bytes) + foreach (KeyValuePair kv in this.FileNames2Bytes) { using (MemoryStream memoryStream = new MemoryStream(kv.Value)) { try { - var obj = operation(memoryStream); + object obj = operation(memoryStream); (obj as IDisposable)?.Dispose(); } @@ -147,14 +147,14 @@ namespace ImageSharp.Benchmarks.Image { base.ReadFilesImpl(); - foreach (var kv in this.FileNamesToBytes) + foreach (KeyValuePair kv in this.FileNamesToBytes) { byte[] bytes = kv.Value; string fn = kv.Key; - using (var ms1 = new MemoryStream(bytes)) + using (MemoryStream ms1 = new MemoryStream(bytes)) { - this.FileNamesToImageSharpImages[fn] = new Image(ms1); + this.FileNamesToImageSharpImages[fn] = Image.Load(ms1); } @@ -178,11 +178,11 @@ namespace ImageSharp.Benchmarks.Image protected void ForEachImageSharpImage(Func operation) { - foreach (var kv in this.FileNames2ImageSharpImages) + foreach (KeyValuePair kv in this.FileNames2ImageSharpImages) { try { - var obj = operation(kv.Value); + object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } @@ -213,11 +213,11 @@ namespace ImageSharp.Benchmarks.Image protected void ForEachSystemDrawingImage(Func operation) { - foreach (var kv in this.FileNames2SystemDrawingImages) + foreach (KeyValuePair kv in this.FileNames2SystemDrawingImages) { try { - var obj = operation(kv.Value); + object obj = operation(kv.Value); (obj as IDisposable)?.Dispose(); } catch (Exception ex) diff --git a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs index 28bec5124d..28661b9d63 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/DetectEdges.cs @@ -23,7 +23,7 @@ namespace ImageSharp.Benchmarks { using (FileStream stream = File.OpenRead("../ImageSharp.Tests/TestImages/Formats/Bmp/Car.bmp")) { - this.image = new CoreImage(stream); + this.image = CoreImage.Load(stream); } } } diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 467663a539..dad603523c 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -38,12 +38,19 @@ namespace ImageSharp.Sandbox46 public static void Main(string[] args) { // RunDecodeJpegProfilingTests(); + // RunToVector4ProfilingTest(); - RunToVector4ProfilingTest(); + RunResizeProfilingTest(); Console.ReadLine(); } + private static void RunResizeProfilingTest() + { + ResizeProfilingBenchmarks test = new ResizeProfilingBenchmarks(new ConsoleOutput()); + test.ResizeBicubic(2000, 2000); + } + private static void RunToVector4ProfilingTest() { BulkPixelOperationsTests.Color tests = new BulkPixelOperationsTests.Color(new ConsoleOutput()); diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs index 6621292ec2..60e25aa043 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs @@ -314,8 +314,8 @@ namespace ImageSharp.Tests.Colors public PinnedBuffer ActualDestBuffer { get; } public PinnedBuffer ExpectedDestBuffer { get; } - public BufferPointer Source => this.SourceBuffer.Slice(); - public BufferPointer ActualDest => this.ActualDestBuffer.Slice(); + public BufferSpan Source => this.SourceBuffer; + public BufferSpan ActualDest => this.ActualDestBuffer; public TestBuffers(TSource[] source, TDest[] expectedDest) { @@ -335,7 +335,7 @@ namespace ImageSharp.Tests.Colors public void Verify() { - int count = this.ExpectedDestBuffer.Count; + int count = this.ExpectedDestBuffer.Length; if (typeof(TDest) == typeof(Vector4)) { @@ -364,11 +364,11 @@ namespace ImageSharp.Tests.Colors internal static void TestOperation( TSource[] source, TDest[] expected, - Action, BufferPointer> action) + Action, BufferSpan> action) where TSource : struct where TDest : struct { - using (var buffers = new TestBuffers(source, expected)) + using (TestBuffers buffers = new TestBuffers(source, expected)) { action(buffers.Source, buffers.ActualDest); buffers.Verify(); diff --git a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs index 08b9375f82..83c02635a2 100644 --- a/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorConstructorTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector4Values = new Vector4[] + Vector4[] vector4Values = new Vector4[] { Vector4.Zero, Vector4.One, @@ -25,10 +25,10 @@ namespace ImageSharp.Tests.Colors Vector4.UnitW, }; - foreach (var vector4 in vector4Values) + foreach (Vector4 vector4 in vector4Values) { // using float array to work around a bug in xunit corruptint the state of any Vector4 passed as MemberData - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(vector4), vector4Components }; yield return new object[] { new Bgra4444(vector4), vector4Components }; @@ -48,7 +48,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector3Values = new Dictionary() + Dictionary vector3Values = new Dictionary() { { Vector3.One, Vector4.One }, { Vector3.Zero, new Vector4(0, 0, 0, 1) }, @@ -57,11 +57,11 @@ namespace ImageSharp.Tests.Colors { Vector3.UnitZ, new Vector4(0, 0, 1, 1) }, }; - foreach (var vector3 in vector3Values.Keys) + foreach (Vector3 vector3 in vector3Values.Keys) { - var vector4 = vector3Values[vector3]; + Vector4 vector4 = vector3Values[vector3]; // using float array to work around a bug in xunit corruptint the state of any Vector4 passed as MemberData - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(vector3), vector4Components }; yield return new object[] { new Bgr565(vector3), vector4Components }; @@ -73,7 +73,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector4Values = new Vector4[] + Vector4[] vector4Values = new Vector4[] { Vector4.Zero, Vector4.One, @@ -83,10 +83,10 @@ namespace ImageSharp.Tests.Colors Vector4.UnitW, }; - foreach (var vector4 in vector4Values) + foreach (Vector4 vector4 in vector4Values) { // using float array to work around a bug in xunit corruptint the state of any Vector4 passed as MemberData - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(vector4.X, vector4.Y, vector4.Z, vector4.W), vector4Components }; yield return new object[] { new Bgra4444(vector4.X, vector4.Y, vector4.Z, vector4.W), vector4Components }; @@ -106,7 +106,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector3Values = new Dictionary() + Dictionary vector3Values = new Dictionary() { { Vector3.One, Vector4.One }, { Vector3.Zero, new Vector4(0, 0, 0, 1) }, @@ -115,11 +115,11 @@ namespace ImageSharp.Tests.Colors { Vector3.UnitZ, new Vector4(0, 0, 1, 1) }, }; - foreach (var vector3 in vector3Values.Keys) + foreach (Vector3 vector3 in vector3Values.Keys) { - var vector4 = vector3Values[vector3]; + Vector4 vector4 = vector3Values[vector3]; // using float array to work around a bug in xunit corruptint the state of any Vector4 passed as MemberData - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(vector3.X, vector3.Y, vector3.Z), vector4Components }; yield return new object[] { new Bgr565(vector3.X, vector3.Y, vector3.Z), vector4Components }; @@ -135,12 +135,12 @@ namespace ImageSharp.Tests.Colors public void ConstructorToVector4(IPixel packedVector, float[] expectedVector4Components) { // Arrange - var precision = 2; + int precision = 2; // using float array to work around a bug in xunit corruptint the state of any Vector4 passed as MemberData - var expectedVector4 = new Vector4(expectedVector4Components[0], expectedVector4Components[1], expectedVector4Components[2], expectedVector4Components[3]); + Vector4 expectedVector4 = new Vector4(expectedVector4Components[0], expectedVector4Components[1], expectedVector4Components[2], expectedVector4Components[3]); // Act - var vector4 = packedVector.ToVector4(); + Vector4 vector4 = packedVector.ToVector4(); // Assert Assert.Equal(expectedVector4.X, vector4.X, precision); diff --git a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs index c241e8d6fc..b5b09c8288 100644 --- a/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorEqualityTests.cs @@ -238,7 +238,7 @@ namespace ImageSharp.Tests.Colors public void Equality(object first, object second, Type type) { // Act - var equal = first.Equals(second); + bool equal = first.Equals(second); // Assert Assert.True(equal); @@ -254,7 +254,7 @@ namespace ImageSharp.Tests.Colors public void NotEquality(object first, object second, Type type) { // Act - var equal = first.Equals(second); + bool equal = first.Equals(second); // Assert Assert.False(equal); @@ -266,7 +266,7 @@ namespace ImageSharp.Tests.Colors public void HashCodeEqual(object first, object second, Type type) { // Act - var equal = first.GetHashCode() == second.GetHashCode(); + bool equal = first.GetHashCode() == second.GetHashCode(); // Assert Assert.True(equal); @@ -278,7 +278,7 @@ namespace ImageSharp.Tests.Colors public void HashCodeNotEqual(object first, object second, Type type) { // Act - var equal = first.GetHashCode() == second.GetHashCode(); + bool equal = first.GetHashCode() == second.GetHashCode(); // Assert Assert.False(equal); @@ -297,7 +297,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var equal = firstObject.Equals(secondObject); + dynamic equal = firstObject.Equals(secondObject); // Assert Assert.True(equal); @@ -316,7 +316,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var equal = firstObject.Equals(secondObject); + dynamic equal = firstObject.Equals(secondObject); // Assert Assert.False(equal); @@ -335,7 +335,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var equal = firstObject == secondObject; + dynamic equal = firstObject == secondObject; // Assert Assert.True(equal); @@ -354,7 +354,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var notEqual = firstObject != secondObject; + dynamic notEqual = firstObject != secondObject; // Assert Assert.True(notEqual); @@ -372,7 +372,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var almostEqual = firstObject.AlmostEquals(secondObject, precision); + dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); // Assert Assert.True(almostEqual); @@ -390,7 +390,7 @@ namespace ImageSharp.Tests.Colors dynamic secondObject = Convert.ChangeType(second, type); // Act - var almostEqual = firstObject.AlmostEquals(secondObject, precision); + dynamic almostEqual = firstObject.AlmostEquals(secondObject, precision); // Assert Assert.False(almostEqual); diff --git a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs index ac5f8d6b46..7b743e53ae 100644 --- a/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorPackingTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector4Values = new Vector4[] + Vector4[] vector4Values = new Vector4[] { Vector4.Zero, Vector4.One, @@ -25,9 +25,9 @@ namespace ImageSharp.Tests.Colors Vector4.UnitW, }; - foreach (var vector4 in vector4Values) + foreach (Vector4 vector4 in vector4Values) { - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(), vector4Components }; yield return new object[] { new Bgra4444(), vector4Components }; @@ -47,7 +47,7 @@ namespace ImageSharp.Tests.Colors { get { - var vector4Values = new Vector4[] + Vector4[] vector4Values = new Vector4[] { Vector4.One, new Vector4(0, 0, 0, 1), @@ -56,9 +56,9 @@ namespace ImageSharp.Tests.Colors new Vector4(0, 0, 1, 1), }; - foreach (var vector4 in vector4Values) + foreach (Vector4 vector4 in vector4Values) { - var vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; + float[] vector4Components = new float[] { vector4.X, vector4.Y, vector4.Z, vector4.W }; yield return new object[] { new Argb(), vector4Components }; yield return new object[] { new Bgr565(), vector4Components }; @@ -72,12 +72,12 @@ namespace ImageSharp.Tests.Colors public void FromVector4ToVector4(IPixel packedVector, float[] vector4ComponentsToPack) { // Arrange - var precision = 2; - var vector4ToPack = new Vector4(vector4ComponentsToPack[0], vector4ComponentsToPack[1], vector4ComponentsToPack[2], vector4ComponentsToPack[3]); + int precision = 2; + Vector4 vector4ToPack = new Vector4(vector4ComponentsToPack[0], vector4ComponentsToPack[1], vector4ComponentsToPack[2], vector4ComponentsToPack[3]); packedVector.PackFromVector4(vector4ToPack); // Act - var vector4 = packedVector.ToVector4(); + Vector4 vector4 = packedVector.ToVector4(); // Assert Assert.Equal(vector4ToPack.X, vector4.X, precision); diff --git a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs similarity index 59% rename from tests/ImageSharp.Tests/Common/BufferPointerTests.cs rename to tests/ImageSharp.Tests/Common/BufferSpanTests.cs index c82b63f115..aee032accd 100644 --- a/tests/ImageSharp.Tests/Common/BufferPointerTests.cs +++ b/tests/ImageSharp.Tests/Common/BufferSpanTests.cs @@ -7,131 +7,133 @@ namespace ImageSharp.Tests.Common using Xunit; - public unsafe class BufferPointerTests + using static TestStructs; + + public unsafe class BufferSpanTests { - public struct Foo + [Fact] + public void AsBytes() { - public int A; - - public double B; + Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; - public Foo(int a, double b) + using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) { - this.A = a; - this.B = b; - } + BufferSpan orig = colorBuf.Slice(1); + BufferSpan asBytes = (BufferSpan < byte > )orig; - internal static Foo[] CreateArray(int size) - { - Foo[] result = new Foo[size]; - for (int i = 0; i < size; i++) - { - result[i] = new Foo(i+1, i+1); - } - return result; + Assert.Equal(asBytes.Start, sizeof(Foo)); + Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); } } - /// - /// sizeof(AlignedFoo) == sizeof(long) - /// - public struct AlignedFoo + public class Construct { - public int A; - - public int B; - - static AlignedFoo() + [Fact] + public void Basic() { - Assert.Equal(sizeof(AlignedFoo), sizeof(long)); - } + Foo[] array = Foo.CreateArray(3); + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p); - public AlignedFoo(int a, int b) - { - this.A = a; - this.B = b; + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal((IntPtr)p, span.PointerAtOffset); + Assert.Equal(3, span.Length); + } } - internal static AlignedFoo[] CreateArray(int size) + [Fact] + public void WithStart() { - AlignedFoo[] result = new AlignedFoo[size]; - for (int i = 0; i < size; i++) + Foo[] array = Foo.CreateArray(4); + int start = 2; + fixed (Foo* p = array) { - result[i] = new AlignedFoo(i + 1, i + 1); + // Act: + BufferSpan span = new BufferSpan(array, p, start); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); + Assert.Equal(array.Length - start, span.Length); } - return result; } - } - - [Fact] - public void AsBytes() - { - Foo[] fooz = { new Foo(1, 2), new Foo(3, 4), new Foo(5, 6) }; - using (PinnedBuffer colorBuf = new PinnedBuffer(fooz)) + [Fact] + public void WithStartAndLength() { - BufferPointer orig = colorBuf.Slice(1); - BufferPointer asBytes = (BufferPointer < byte > )orig; - - Assert.Equal(asBytes.Offset, sizeof(Foo)); - Assert.Equal(orig.PointerAtOffset, asBytes.PointerAtOffset); + Foo[] array = Foo.CreateArray(10); + int start = 2; + int length = 3; + fixed (Foo* p = array) + { + // Act: + BufferSpan span = new BufferSpan(array, p, start, length); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal((IntPtr)(p + start), span.PointerAtOffset); + Assert.Equal(length, span.Length); + } } } - - [Fact] - public void ConstructWithoutOffset() + + public class Slice { - Foo[] array = Foo.CreateArray(3); - fixed (Foo* p = array) + [Fact] + public void StartOnly() { - // Act: - BufferPointer ap = new BufferPointer(array, p); + Foo[] array = Foo.CreateArray(5); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal((IntPtr)p, ap.PointerAtOffset); - } - } + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); - [Fact] - public void ConstructWithOffset() - { - Foo[] array = Foo.CreateArray(3); - int offset = 2; - fixed (Foo* p = array) - { - // Act: - BufferPointer ap = new BufferPointer(array, p, offset); + // Act: + span = span.Slice(start1); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(offset, ap.Offset); - Assert.Equal((IntPtr)(p+offset), ap.PointerAtOffset); + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); + Assert.Equal(array.Length - totalOffset, span.Length); + } } - } - [Fact] - public void Slice() - { - Foo[] array = Foo.CreateArray(5); - int offset0 = 2; - int offset1 = 2; - int totalOffset = offset0 + offset1; - fixed (Foo* p = array) + [Fact] + public void StartAndLength() { - BufferPointer ap = new BufferPointer(array, p, offset0); + Foo[] array = Foo.CreateArray(10); + int start0 = 2; + int start1 = 2; + int totalOffset = start0 + start1; + int sliceLength = 3; - // Act: - ap = ap.Slice(offset1); + fixed (Foo* p = array) + { + BufferSpan span = new BufferSpan(array, p, start0); - // Assert: - Assert.Equal(array, ap.Array); - Assert.Equal(totalOffset, ap.Offset); - Assert.Equal((IntPtr)(p + totalOffset), ap.PointerAtOffset); + // Act: + span = span.Slice(start1, sliceLength); + + // Assert: + Assert.Equal(array, span.Array); + Assert.Equal(totalOffset, span.Start); + Assert.Equal((IntPtr)(p + totalOffset), span.PointerAtOffset); + Assert.Equal(sliceLength, span.Length); + } } } + [Theory] [InlineData(4)] [InlineData(1500)] @@ -142,7 +144,7 @@ namespace ImageSharp.Tests.Common int offset = 2; fixed (Foo* p = array) { - BufferPointer ap = new BufferPointer(array, p, offset); + BufferSpan ap = new BufferSpan(array, p, offset); // Act: ap.Clear(count); @@ -155,6 +157,51 @@ namespace ImageSharp.Tests.Common } + public class Indexer + { + public static readonly TheoryData IndexerData = + new TheoryData() + { + { 10, 0, 0 }, + { 10, 2, 0 }, + { 16, 0, 3 }, + { 16, 2, 3 }, + { 10, 0, 9 }, + { 10, 1, 8 } + }; + + [Theory] + [MemberData(nameof(IndexerData))] + public void Read(int length, int start, int index) + { + Foo[] a = Foo.CreateArray(length); + fixed (Foo* p = a) + { + BufferSpan span = new BufferSpan(a, p, start); + + Foo element = span[index]; + + Assert.Equal(a[start + index], element); + } + } + + [Theory] + [MemberData(nameof(IndexerData))] + public void Write(int length, int start, int index) + { + Foo[] a = Foo.CreateArray(length); + fixed (Foo* p = a) + { + BufferSpan span = new BufferSpan(a, p, start); + + span[index] = new Foo(666, 666); + + Assert.Equal(new Foo(666, 666), a[start + index]); + } + } + } + + public class Copy { private static void AssertNotDefault(T[] data, int idx) @@ -194,10 +241,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count-1); + BufferSpan.Copy(apSource, apDest, count-1); } AssertNotDefault(source, 1); @@ -221,10 +268,10 @@ namespace ImageSharp.Tests.Common fixed (AlignedFoo* pSource = source) fixed (AlignedFoo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -248,10 +295,10 @@ namespace ImageSharp.Tests.Common fixed (int* pSource = source) fixed (int* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, 1); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, 1); - BufferPointer.Copy(apSource, apDest, count -1); + BufferSpan.Copy(apSource, apDest, count -1); } AssertNotDefault(source, 1); @@ -276,10 +323,10 @@ namespace ImageSharp.Tests.Common fixed (Foo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(Foo)); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(Foo)); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -303,10 +350,10 @@ namespace ImageSharp.Tests.Common fixed (AlignedFoo* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource, 1); - BufferPointer apDest = new BufferPointer(dest, pDest, sizeof(AlignedFoo)); + BufferSpan apSource = new BufferSpan(source, pSource, 1); + BufferSpan apDest = new BufferSpan(dest, pDest, sizeof(AlignedFoo)); - BufferPointer.Copy(apSource, apDest, count - 1); + BufferSpan.Copy(apSource, apDest, count - 1); } AssertNotDefault(source, 1); @@ -330,10 +377,10 @@ namespace ImageSharp.Tests.Common fixed (int* pSource = source) fixed (byte* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferSpan apSource = new BufferSpan(source, pSource); + BufferSpan apDest = new BufferSpan(dest, pDest); - BufferPointer.Copy(apSource, apDest, count); + BufferSpan.Copy(apSource, apDest, count); } AssertNotDefault(source, 1); @@ -355,10 +402,10 @@ namespace ImageSharp.Tests.Common fixed(byte* pSource = source) fixed (Foo* pDest = dest) { - BufferPointer apSource = new BufferPointer(source, pSource); - BufferPointer apDest = new BufferPointer(dest, pDest); + BufferSpan apSource = new BufferSpan(source, pSource); + BufferSpan apDest = new BufferSpan(dest, pDest); - BufferPointer.Copy(apSource, apDest, count); + BufferSpan.Copy(apSource, apDest, count); } AssertNotDefault(source, sizeof(Foo) + 1); @@ -378,17 +425,18 @@ namespace ImageSharp.Tests.Common using (PinnedBuffer colorBuf = new PinnedBuffer(colors)) using (PinnedBuffer byteBuf = new PinnedBuffer(colors.Length*4)) { - BufferPointer.Copy(colorBuf, byteBuf, colorBuf.Count); + BufferSpan.Copy(colorBuf, byteBuf, colorBuf.Length); byte[] a = byteBuf.Array; - for (int i = 0; i < byteBuf.Count; i++) + for (int i = 0; i < byteBuf.Length; i++) { Assert.Equal((byte)i, a[i]); } } } + internal static bool ElementsAreEqual(Foo[] array, byte[] rawArray, int index) { fixed (Foo* pArray = array) diff --git a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs index 3688763b9a..5e812d5a01 100644 --- a/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs +++ b/tests/ImageSharp.Tests/Common/PinnedBufferTests.cs @@ -3,18 +3,14 @@ using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; + using System.Threading.Tasks; using Xunit; + using static TestStructs; + public unsafe class PinnedBufferTests { - public struct Foo - { - public int A; - - public double B; - } - [Theory] [InlineData(42)] [InlineData(1111)] @@ -24,7 +20,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.NotNull(buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); Assert.True(buffer.Array.Length >= count); VerifyPointer(buffer); @@ -41,7 +37,7 @@ { Assert.False(buffer.IsDisposedOrLostArrayOwnership); Assert.Equal(array, buffer.Array); - Assert.Equal(count, buffer.Count); + Assert.Equal(count, buffer.Length); VerifyPointer(buffer); } @@ -62,6 +58,61 @@ } } + [Fact] + public void CreateClean() + { + for (int i = 0; i < 100; i++) + { + using (PinnedBuffer buffer = PinnedBuffer.CreateClean(42)) + { + for (int j = 0; j < buffer.Length; j++) + { + Assert.Equal(0, buffer.Array[j]); + buffer.Array[j] = 666; + } + } + } + } + + public class Indexer + { + public static readonly TheoryData IndexerData = + new TheoryData() + { + { 10, 0 }, + { 16, 3 }, + { 10, 9 } + }; + + [Theory] + [MemberData(nameof(IndexerData))] + public void Read(int length, int index) + { + Foo[] a = Foo.CreateArray(length); + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + Foo element = buffer[index]; + + Assert.Equal(a[index], element); + } + } + + [Theory] + [MemberData(nameof(IndexerData))] + public void Write(int length, int index) + { + Foo[] a = Foo.CreateArray(length); + + using (PinnedBuffer buffer = new PinnedBuffer(a)) + { + buffer[index] = new Foo(666, 666); + + Assert.Equal(new Foo(666, 666), a[index]); + } + } + } + [Fact] public void Dispose() { @@ -71,21 +122,72 @@ Assert.True(buffer.IsDisposedOrLostArrayOwnership); } + [Theory] + [InlineData(7)] + [InlineData(123)] + public void CastToSpan(int bufferLength) + { + using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + { + BufferSpan span = buffer; + + Assert.Equal(buffer.Array, span.Array); + Assert.Equal(0, span.Start); + Assert.Equal(buffer.Pointer, span.PointerAtOffset); + Assert.Equal(span.Length, bufferLength); + } + } + [Fact] - public void Slice() + public void Span() { - Foo[] a = { new Foo() { A = 1, B = 2 }, new Foo() { A = 3, B = 4 } }; - - using (PinnedBuffer buffer = new PinnedBuffer(a)) + using (PinnedBuffer buffer = new PinnedBuffer(42)) { - var arrayPtr = buffer.Slice(); + BufferSpan span = buffer.Span; - Assert.Equal(a, arrayPtr.Array); - Assert.Equal(0, arrayPtr.Offset); - Assert.Equal(buffer.Pointer, arrayPtr.PointerAtOffset); + Assert.Equal(buffer.Array, span.Array); + Assert.Equal(0, span.Start); + Assert.Equal(buffer.Pointer, span.PointerAtOffset); + Assert.Equal(span.Length, 42); } } + public class Slice + { + + [Theory] + [InlineData(7, 2)] + [InlineData(123, 17)] + public void WithStartOnly(int bufferLength, int start) + { + using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + { + BufferSpan span = buffer.Slice(start); + + Assert.Equal(buffer.Array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset); + Assert.Equal(span.Length, bufferLength - start); + } + } + + [Theory] + [InlineData(7, 2, 5)] + [InlineData(123, 17, 42)] + public void WithStartAndLength(int bufferLength, int start, int spanLength) + { + using (PinnedBuffer buffer = new PinnedBuffer(bufferLength)) + { + BufferSpan span = buffer.Slice(start, spanLength); + + Assert.Equal(buffer.Array, span.Array); + Assert.Equal(start, span.Start); + Assert.Equal(buffer.Pointer + start * Unsafe.SizeOf(), span.PointerAtOffset); + Assert.Equal(span.Length, spanLength); + } + } + } + [Fact] public void UnPinAndTakeArrayOwnership() { diff --git a/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs new file mode 100644 index 0000000000..a23f93a70b --- /dev/null +++ b/tests/ImageSharp.Tests/Common/PinnedImageBufferTests.cs @@ -0,0 +1,106 @@ +// ReSharper disable InconsistentNaming +namespace ImageSharp.Tests.Common +{ + using System.Runtime.CompilerServices; + + using Xunit; + + using static TestStructs; + + public unsafe class PinnedImageBufferTests + { + [Theory] + [InlineData(7, 42)] + [InlineData(1025, 17)] + public void Construct(int width, int height) + { + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + { + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(width * height, buffer.Length); + } + } + + [Theory] + [InlineData(7, 42)] + [InlineData(1025, 17)] + public void Construct_FromExternalArray(int width, int height) + { + Foo[] array = new Foo[width * height + 10]; + using (PinnedImageBuffer buffer = new PinnedImageBuffer(array, width, height)) + { + Assert.Equal(width, buffer.Width); + Assert.Equal(height, buffer.Height); + Assert.Equal(width * height, buffer.Length); + } + } + + + [Fact] + public void CreateClean() + { + for (int i = 0; i < 100; i++) + { + using (PinnedImageBuffer buffer = PinnedImageBuffer.CreateClean(42, 42)) + { + for (int j = 0; j < buffer.Length; j++) + { + Assert.Equal(0, buffer.Array[j]); + buffer.Array[j] = 666; + } + } + } + } + + [Theory] + [InlineData(7, 42, 0)] + [InlineData(7, 42, 10)] + [InlineData(17, 42, 41)] + public void GetRowSpanY(int width, int height, int y) + { + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + { + BufferSpan span = buffer.GetRowSpan(y); + + Assert.Equal(width * y, span.Start); + Assert.Equal(width, span.Length); + Assert.Equal(buffer.Pointer + sizeof(Foo) * width * y, span.PointerAtOffset); + } + } + + [Theory] + [InlineData(7, 42, 0, 0)] + [InlineData(7, 42, 3, 10)] + [InlineData(17, 42, 0, 41)] + public void GetRowSpanXY(int width, int height, int x, int y) + { + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + { + BufferSpan span = buffer.GetRowSpan(x, y); + + Assert.Equal(width * y + x, span.Start); + Assert.Equal(width - x, span.Length); + Assert.Equal(buffer.Pointer + sizeof(Foo) * (width * y + x), span.PointerAtOffset); + } + } + + [Theory] + [InlineData(42, 8, 0, 0)] + [InlineData(400, 1000, 20, 10)] + [InlineData(99, 88, 98, 87)] + public void Indexer(int width, int height, int x, int y) + { + using (PinnedImageBuffer buffer = new PinnedImageBuffer(width, height)) + { + Foo[] array = buffer.Array; + + ref Foo actual = ref buffer[x, y]; + + ref Foo expected = ref array[y * width + x]; + + Assert.True(Unsafe.AreSame(ref expected, ref actual)); + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Common/TestStructs.cs b/tests/ImageSharp.Tests/Common/TestStructs.cs new file mode 100644 index 0000000000..9e762bbd16 --- /dev/null +++ b/tests/ImageSharp.Tests/Common/TestStructs.cs @@ -0,0 +1,62 @@ +namespace ImageSharp.Tests.Common +{ + using Xunit; + + public static class TestStructs + { + public struct Foo + { + public int A; + + public double B; + + public Foo(int a, double b) + { + this.A = a; + this.B = b; + } + + internal static Foo[] CreateArray(int size) + { + Foo[] result = new Foo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new Foo(i + 1, i + 1); + } + return result; + } + } + + + /// + /// sizeof(AlignedFoo) == sizeof(long) + /// + public unsafe struct AlignedFoo + { + public int A; + + public int B; + + static AlignedFoo() + { + Assert.Equal(sizeof(AlignedFoo), sizeof(long)); + } + + public AlignedFoo(int a, int b) + { + this.A = a; + this.B = b; + } + + internal static AlignedFoo[] CreateArray(int size) + { + AlignedFoo[] result = new AlignedFoo[size]; + for (int i = 0; i < size; i++) + { + result[i] = new AlignedFoo(i + 1, i + 1); + } + return result; + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/ConfigurationTests.cs b/tests/ImageSharp.Tests/ConfigurationTests.cs index 883d235673..c749239d71 100644 --- a/tests/ImageSharp.Tests/ConfigurationTests.cs +++ b/tests/ImageSharp.Tests/ConfigurationTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests using System.Linq; using ImageSharp.Formats; - + using ImageSharp.IO; using Xunit; /// @@ -18,10 +18,20 @@ namespace ImageSharp.Tests /// public class ConfigurationTests { + [Fact] + public void DefaultsToLocalFileSystem() + { + Configuration configuration = Configuration.CreateDefaultInstance(); + + ImageSharp.IO.IFileSystem fs = configuration.FileSystem; + + Assert.IsType(fs); + } + [Fact] public void IfAutoloadWellknwonFormatesIsTrueAllFormateAreLoaded() { - var configuration = Configuration.CreateDefaultInstance(); + Configuration configuration = Configuration.CreateDefaultInstance(); Assert.Equal(4, configuration.ImageFormats.Count); } diff --git a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs index d3c1877abd..8162bc5319 100644 --- a/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs +++ b/tests/ImageSharp.Tests/Drawing/FillPatternTests.cs @@ -33,8 +33,9 @@ namespace ImageSharp.Tests.Drawing { // lets pick random spots to start checking Random r = new Random(); - int xStride = expectedPattern.GetLength(1); - int yStride = expectedPattern.GetLength(0); + Fast2DArray expectedPatternFast = new Fast2DArray(expectedPattern); + int xStride = expectedPatternFast.Width; + int yStride = expectedPatternFast.Height; int offsetX = r.Next(image.Width / xStride) * xStride; int offsetY = r.Next(image.Height / yStride) * yStride; for (int x = 0; x < xStride; x++) @@ -43,7 +44,7 @@ namespace ImageSharp.Tests.Drawing { int actualX = x + offsetX; int actualY = y + offsetY; - Color expected = expectedPattern[y, x]; // inverted pattern + Color expected = expectedPatternFast[y, x]; // inverted pattern Color actual = sourcePixels[actualX, actualY]; if (expected != actual) { @@ -187,10 +188,10 @@ namespace ImageSharp.Tests.Drawing { Test("ForwardDiagonal", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink, Color.LimeGreen), new Color[,] { - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}, { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink} + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen} }); } @@ -199,10 +200,10 @@ namespace ImageSharp.Tests.Drawing { Test("ForwardDiagonal_Transparent", Color.Blue, Brushes.ForwardDiagonal(Color.HotPink), new Color[,] { - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue}, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink}, { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink} + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue} }); } @@ -211,10 +212,10 @@ namespace ImageSharp.Tests.Drawing { Test("BackwardDiagonal", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink, Color.LimeGreen), new Color[,] { - { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink}, - { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, - { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, - { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen} + { Color.HotPink, Color.LimeGreen, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.HotPink, Color.LimeGreen, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.HotPink, Color.LimeGreen}, + { Color.LimeGreen, Color.LimeGreen, Color.LimeGreen, Color.HotPink} }); } @@ -223,13 +224,11 @@ namespace ImageSharp.Tests.Drawing { Test("BackwardDiagonal_Transparent", Color.Blue, Brushes.BackwardDiagonal(Color.HotPink), new Color[,] { - { Color.Blue, Color.Blue, Color.Blue, Color.HotPink}, - { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, - { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, - { Color.HotPink, Color.Blue, Color.Blue, Color.Blue} + { Color.HotPink, Color.Blue, Color.Blue, Color.Blue}, + { Color.Blue, Color.HotPink, Color.Blue, Color.Blue}, + { Color.Blue, Color.Blue, Color.HotPink, Color.Blue}, + { Color.Blue, Color.Blue, Color.Blue, Color.HotPink} }); } - - } } diff --git a/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs new file mode 100644 index 0000000000..03994bc94d --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs @@ -0,0 +1,46 @@ + +namespace ImageSharp.Tests.Drawing +{ + using System; + using System.IO; + using ImageSharp; + using ImageSharp.Drawing.Brushes; + using Processing; + using System.Collections.Generic; + using Xunit; + using ImageSharp.Drawing; + using System.Numerics; + using SixLabors.Shapes; + using ImageSharp.Drawing.Processors; + using ImageSharp.Drawing.Pens; + using Moq; + using System.Collections.Immutable; + + public class FillRegionProcessorTests + { + [Theory] + [InlineData(true, 1, 4)] + [InlineData(true, 2, 4)] + [InlineData(true, 5, 5)] + [InlineData(true, 8, 8)] + [InlineData(false, 8, 4)] + [InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off. + public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth) + { + ImageSharp.Rectangle bounds = new ImageSharp.Rectangle(0, 0, 1, 1); + + Mock> brush = new Mock>(); + Mock region = new Mock(); + region.Setup(x => x.Bounds).Returns(bounds); + + 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)); + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs index 6153cb3105..d7a4bde957 100644 --- a/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/LineComplexPolygonTests.cs @@ -21,12 +21,12 @@ namespace ImageSharp.Tests.Drawing { string path = this.CreateOutputDirectory("Drawing", "LineComplexPolygon"); - var simplePath = new Polygon(new LinearLineSegment( + Polygon simplePath = new Polygon(new LinearLineSegment( new Vector2(10, 10), new Vector2(200, 150), new Vector2(50, 300))); - var hole1 = new Polygon(new LinearLineSegment( + Polygon hole1 = new Polygon(new LinearLineSegment( new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137))); diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs index 3bb3b3e777..2d3d2cc2b8 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ProcessorWatchingImage.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests.Drawing.Paths using ImageSharp; using Processing; using System.Collections.Generic; + using ImageSharp.Formats; /// /// Watches but does not actually run the processors against the image. @@ -22,15 +23,11 @@ namespace ImageSharp.Tests.Drawing.Paths public override void ApplyProcessor(IImageProcessor processor, Rectangle rectangle) { - ProcessorApplications.Add(new ProcessorDetails + this.ProcessorApplications.Add(new ProcessorDetails { processor = processor, rectangle = rectangle }); - - // doesn't really apply the processor to the fake images as this is supposed - // to be just used to test which processor was finally applied and to interogate - // its settings } public struct ProcessorDetails diff --git a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs index aa7c0575c7..78c3492332 100644 --- a/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs +++ b/tests/ImageSharp.Tests/Drawing/Paths/ShapeRegionTests.cs @@ -67,25 +67,6 @@ namespace ImageSharp.Tests.Drawing.Paths pathMock.Verify(x => x.MaxIntersections); } - [Fact] - public void ShapeRegionFromPathScanXProxyToShape() - { - int xToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.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 < bounds.Top); - Assert.True(e.Y > bounds.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); - - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - [Fact] public void ShapeRegionFromPathScanYProxyToShape() { @@ -100,27 +81,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.ScanY(yToScan, new float[0], 0, 0); - - pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - - - [Fact] - public void ShapeRegionFromShapeScanXProxyToShape() - { - int xToScan = 10; - ShapeRegion region = new ShapeRegion(pathMock.Object); - - pathMock.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 < bounds.Top); - Assert.True(e.Y > bounds.Bottom); - }).Returns(0); - - int i = region.ScanX(xToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0], 0, 0); pathMock.Verify(x => x.FindIntersections(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } @@ -139,7 +100,7 @@ namespace ImageSharp.Tests.Drawing.Paths Assert.True(e.X > bounds.Right); }).Returns(0); - int i = region.ScanY(yToScan, new float[0], 0, 0); + int i = region.Scan(yToScan, new float[0], 0, 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/SolidBezierTests.cs b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs index 80713468d4..1a7e98a12f 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidBezierTests.cs @@ -36,15 +36,9 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - //top of curve - Assert.Equal(Color.HotPink, sourcePixels[138, 116]); - - //start points - Assert.Equal(Color.HotPink, sourcePixels[10, 400]); - Assert.Equal(Color.HotPink, sourcePixels[300, 400]); + Assert.Equal(Color.HotPink, sourcePixels[150, 300]); //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); Assert.Equal(Color.Blue, sourcePixels[240, 30]); // inside shape should not be empty @@ -83,12 +77,7 @@ namespace ImageSharp.Tests.Drawing //top of curve Assert.Equal(mergedColor, sourcePixels[138, 116]); - //start points - Assert.Equal(mergedColor, sourcePixels[10, 400]); - Assert.Equal(mergedColor, sourcePixels[300, 400]); - //curve points should not be never be set - Assert.Equal(Color.Blue, sourcePixels[30, 10]); Assert.Equal(Color.Blue, sourcePixels[240, 30]); // inside shape should not be empty diff --git a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs index cd3182d6c2..4ff250a934 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidComplexPolygonTests.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Tests.Drawing new Vector2(37, 85), new Vector2(93, 85), new Vector2(65, 137))); - var clipped = simplePath.Clip(hole1); + IPath clipped = simplePath.Clip(hole1); // var clipped = new Rectangle(10, 10, 100, 100).Clip(new Rectangle(20, 0, 20, 20)); using (Image image = new Image(500, 500)) { @@ -42,20 +42,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - - Assert.Equal(Color.HotPink, sourcePixels[70, 137]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } @@ -87,18 +77,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); - - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.HotPink, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(Color.HotPink, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } @@ -133,18 +115,10 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[11, 11]); - - Assert.Equal(mergedColor, sourcePixels[200, 150]); - - Assert.Equal(mergedColor, sourcePixels[50, 50]); - - Assert.Equal(mergedColor, sourcePixels[35, 100]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); + Assert.Equal(mergedColor, sourcePixels[20, 35]); //inside hole - Assert.Equal(Color.Blue, sourcePixels[57, 99]); + Assert.Equal(Color.Blue, sourcePixels[60, 100]); } } } diff --git a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs index 9c6c6d2342..79363480fc 100644 --- a/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs +++ b/tests/ImageSharp.Tests/Drawing/SolidPolygonTests.cs @@ -38,13 +38,33 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[11, 11]); + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); + } + } + } - Assert.Equal(Color.HotPink, sourcePixels[200, 150]); + [Fact] + public void ImageShouldBeOverlayedByFilledPolygonWithPattern() + { + string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); + Vector2[] simplePath = new[] { + new Vector2(10, 10), + new Vector2(200, 150), + new Vector2(50, 300) + }; - Assert.Equal(Color.HotPink, sourcePixels[50, 50]); + using (Image image = new Image(500, 500)) + { + using (FileStream output = File.OpenWrite($"{path}/Pattern.png")) + { + image + .FillPolygon(Brushes.Horizontal(Color.HotPink), simplePath, new GraphicsOptions(true)) + .Save(output); + } - Assert.NotEqual(Color.HotPink, sourcePixels[2, 2]); + using (PixelAccessor sourcePixels = image.Lock()) + { + Assert.Equal(Color.HotPink, sourcePixels[81, 145]); } } } @@ -129,12 +149,6 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(mergedColor, sourcePixels[11, 11]); - - Assert.Equal(mergedColor, sourcePixels[200, 150]); - - Assert.Equal(mergedColor, sourcePixels[50, 50]); - Assert.Equal(Color.Blue, sourcePixels[2, 2]); } } @@ -187,19 +201,9 @@ namespace ImageSharp.Tests.Drawing using (PixelAccessor sourcePixels = image.Lock()) { - Assert.Equal(Color.HotPink, sourcePixels[25, 35]); - - Assert.Equal(Color.HotPink, sourcePixels[50, 79]); - - Assert.Equal(Color.HotPink, sourcePixels[75, 35]); + Assert.Equal(Color.Blue, sourcePixels[30, 65]); Assert.Equal(Color.HotPink, sourcePixels[50, 50]); - - Assert.Equal(Color.Blue, sourcePixels[2, 2]); - - Assert.Equal(Color.Blue, sourcePixels[28, 60]); - - Assert.Equal(Color.Blue, sourcePixels[67, 67]); } } } @@ -209,7 +213,7 @@ namespace ImageSharp.Tests.Drawing { string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - var config = Configuration.CreateDefaultInstance(); + Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(100, 100, config)) { @@ -228,7 +232,7 @@ namespace ImageSharp.Tests.Drawing { string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - var config = Configuration.CreateDefaultInstance(); + Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(100, 100, config)) { @@ -248,7 +252,7 @@ namespace ImageSharp.Tests.Drawing { string path = this.CreateOutputDirectory("Drawing", "FilledPolygons"); - var config = Configuration.CreateDefaultInstance(); + Configuration config = Configuration.CreateDefaultInstance(); config.ParallelOptions.MaxDegreeOfParallelism = 1; using (Image image = new Image(200, 200, config)) { diff --git a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs index 2a2cb8a075..52b7fcbb65 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/DrawText.cs @@ -1,96 +1,107 @@ - +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + namespace ImageSharp.Tests.Drawing.Text { using System; - using System.IO; - using ImageSharp; - using ImageSharp.Drawing.Brushes; - using Processing; - using System.Collections.Generic; - using Xunit; - using ImageSharp.Drawing; using System.Numerics; - using SixLabors.Shapes; - using ImageSharp.Drawing.Processors; + + using ImageSharp.Drawing; + using ImageSharp.Drawing.Brushes; using ImageSharp.Drawing.Pens; + using ImageSharp.Drawing.Processors; + using ImageSharp.Tests.Drawing.Paths; + using SixLabors.Fonts; - using Paths; + using SixLabors.Shapes; + + using Xunit; public class DrawText : IDisposable { Color color = Color.HotPink; + SolidBrush brush = Brushes.Solid(Color.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), - })); + + 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() { this.FontCollection = new FontCollection(); - this.Font = FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); + this.Font = this.FontCollection.Install(TestFontUtilities.GetPath("SixLaborsSampleAB.woff")); this.img = new ProcessorWatchingImage(10, 10); } public void Dispose() { - img.Dispose(); + this.img.Dispose(); } [Fact] public void FillsForEachACharachterWhenBrushSetAndNotPen() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Color.Red), + null, + Vector2.Zero, + new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); + this.img.DrawText("123", this.Font, Brushes.Solid(Color.Red), null, Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); + this.img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Color.Red, Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText("123", this.Font, Color.Red, Vector2.Zero, new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + 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(Color.Red, brush.Color); @@ -99,12 +110,13 @@ namespace ImageSharp.Tests.Drawing.Text [Fact] public void FillsForEachACharachterWhenColorSetDefaultOptions() { - img.DrawText("123", this.Font, Color.Red, Vector2.Zero); + this.img.DrawText("123", this.Font, Color.Red, Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); - Assert.IsType>(img.ProcessorApplications[0].processor); - FillRegionProcessor processor = Assert.IsType>(img.ProcessorApplications[0].processor); + 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(Color.Red, brush.Color); @@ -113,82 +125,126 @@ namespace ImageSharp.Tests.Drawing.Text [Fact] public void DrawForEachACharachterWhenPenSetAndNotBrush() { - img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText( + "123", + this.Font, + null, + Pens.Dash(Color.Red, 1), + Vector2.Zero, + new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); + this.img.DrawText("123", this.Font, null, Pens.Dash(Color.Red, 1), Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); + this.img.DrawText("123", this.Font, Pens.Dash(Color.Red, 1), Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(3, img.ProcessorApplications.Count); // 3 fills where applied - Assert.IsType>(img.ProcessorApplications[0].processor); + 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() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText( + "123", + this.Font, + Brushes.Solid(Color.Red), + Pens.Dash(Color.Red, 1), + Vector2.Zero, + new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(6, img.ProcessorApplications.Count); + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); } [Fact] public void DrawForEachACharachterWhenPenSetAndFillFroEachWhenBrushSetDefaultOptions() { - img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + this.img.DrawText("123", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(6, img.ProcessorApplications.Count); + Assert.NotEmpty(this.img.ProcessorApplications); + Assert.Equal(6, this.img.ProcessorApplications.Count); } [Fact] public void BrushAppliesBeforPen() { - img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero, new TextGraphicsOptions(true)); + this.img.DrawText( + "1", + this.Font, + Brushes.Solid(Color.Red), + Pens.Dash(Color.Red, 1), + Vector2.Zero, + new TextGraphicsOptions(true)); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(2, img.ProcessorApplications.Count); - Assert.IsType>(img.ProcessorApplications[0].processor); - Assert.IsType>(img.ProcessorApplications[1].processor); + 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() { - img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); + this.img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Pens.Dash(Color.Red, 1), Vector2.Zero); - Assert.NotEmpty(img.ProcessorApplications); - Assert.Equal(2, img.ProcessorApplications.Count); - Assert.IsType>(img.ProcessorApplications[0].processor); - Assert.IsType>(img.ProcessorApplications[1].processor); + 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(Color.Red), Vector2.Zero, new TextGraphicsOptions(true) { + UseImageResolution = false + }); + + this.img.DrawText("1", this.Font, Brushes.Solid(Color.Red), Vector2.Zero, 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/GlyphBuilder.cs b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs index 1faa5edd37..c7121695e2 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/GlyphBuilder.cs @@ -16,7 +16,7 @@ namespace ImageSharp.Tests.Drawing.Text public void OriginUsed() { // Y axis is inverted as it expects to be drawing for bottom left - var fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); + GlyphBuilder fullBuilder = new GlyphBuilder(new System.Numerics.Vector2(10, 99)); IGlyphRenderer builder = fullBuilder; builder.BeginGlyph(); @@ -36,7 +36,7 @@ namespace ImageSharp.Tests.Drawing.Text builder.EndFigure(); builder.EndGlyph(); - var points = fullBuilder.Paths.Single().Flatten().Single().Points; + 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); @@ -50,7 +50,7 @@ namespace ImageSharp.Tests.Drawing.Text // Y axis is inverted as it expects to be drawing for bottom left GlyphBuilder fullBuilder = new GlyphBuilder(); IGlyphRenderer builder = fullBuilder; - for (var i = 0; i < 10; i++) + for (int i = 0; i < 10; i++) { builder.BeginGlyph(); builder.BeginFigure(); diff --git a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs index ae007727a5..0bb3afccd7 100644 --- a/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs +++ b/tests/ImageSharp.Tests/Drawing/Text/OutputText.cs @@ -30,7 +30,7 @@ namespace ImageSharp.Tests.Drawing.Text public void DrawAB() { //draws 2 overlapping triangle glyphs twice 1 set on each line - using (var img = new Image(100, 200)) + using (Image img = new Image(100, 200)) { img.Fill(Color.DarkBlue) .DrawText("AB\nAB", new Font(this.Font, 50), Color.Red, new Vector2(0, 0)); diff --git a/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs new file mode 100644 index 0000000000..a291a7d18b --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/Text/TextGraphicsOptionsTests.cs @@ -0,0 +1,43 @@ + +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 TextGraphicsOptionsTests + { + [Fact] + public void ExplicitCastOfGraphicsOptions() + { + GraphicsOptions opt = new GraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + TextGraphicsOptions textOptions = opt; + + Assert.Equal(false, textOptions.Antialias); + Assert.Equal(99, textOptions.AntialiasSubpixelDepth); + } + + [Fact] + public void ImplicitCastToGraphicsOptions() + { + TextGraphicsOptions textOptions = new TextGraphicsOptions(false) + { + AntialiasSubpixelDepth = 99 + }; + + GraphicsOptions opt = (GraphicsOptions)textOptions; + + Assert.Equal(false, opt.Antialias); + Assert.Equal(99, opt.AntialiasSubpixelDepth); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index ae795c2ecc..1ecd04690a 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -150,7 +150,7 @@ namespace ImageSharp.Tests serialized = memoryStream.ToArray(); } - using (Image image2 = new Image(serialized)) + using (Image image2 = Image.Load(serialized)) using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { image2.Save(output); diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs index b874a1585f..5dac59d696 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_CommentsAreRead() { - var options = new DecoderOptions() + DecoderOptions options = new DecoderOptions() { IgnoreMetadata = false }; @@ -33,7 +33,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() { - var options = new DecoderOptions() + DecoderOptions options = new DecoderOptions() { IgnoreMetadata = true }; @@ -49,7 +49,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - var options = new GifDecoderOptions() + GifDecoderOptions options = new GifDecoderOptions() { TextEncoding = Encoding.Unicode }; diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs index da1323627f..897778bc3a 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Gif/GifEncoderTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_CommentsAreWritten() { - var options = new EncoderOptions() + EncoderOptions options = new EncoderOptions() { IgnoreMetadata = false }; @@ -29,7 +29,7 @@ namespace ImageSharp.Tests input.Save(memStream, new GifFormat(), options); memStream.Position = 0; - using (Image output = new Image(memStream)) + using (Image output = Image.Load(memStream)) { Assert.Equal(1, output.MetaData.Properties.Count); Assert.Equal("Comments", output.MetaData.Properties[0].Name); @@ -42,7 +42,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_CommentsAreNotWritten() { - var options = new GifEncoderOptions() + GifEncoderOptions options = new GifEncoderOptions() { IgnoreMetadata = true }; @@ -56,7 +56,7 @@ namespace ImageSharp.Tests input.SaveAsGif(memStream, options); memStream.Position = 0; - using (Image output = new Image(memStream)) + using (Image output = Image.Load(memStream)) { Assert.Equal(0, output.MetaData.Properties.Count); } @@ -77,7 +77,7 @@ namespace ImageSharp.Tests input.Save(memStream, new GifFormat()); memStream.Position = 0; - using (Image output = new Image(memStream)) + using (Image output = Image.Load(memStream)) { Assert.Equal(1, output.MetaData.Properties.Count); Assert.Equal("Comments", output.MetaData.Properties[0].Name); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs index cd863ebf98..63ddbc884c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs @@ -278,7 +278,7 @@ namespace ImageSharp.Tests [InlineData(3)] public void TransformIDCT(int seed) { - var sourceArray = Create8x8RandomFloatData(-200, 200, seed); + MutableSpan sourceArray = Create8x8RandomFloatData(-200, 200, seed); float[] expectedDestArray = new float[64]; float[] tempArray = new float[64]; @@ -306,7 +306,7 @@ namespace ImageSharp.Tests [Fact] public unsafe void CopyColorsTo() { - var data = Create8x8FloatData(); + float[] data = Create8x8FloatData(); Block8x8F block = new Block8x8F(); block.LoadFrom(data); block.MultiplyAllInplace(new Vector4(5, 5, 5, 5)); @@ -348,7 +348,7 @@ namespace ImageSharp.Tests public void TransformByteConvetibleColorValuesInto() { Block8x8F block = new Block8x8F(); - var input = Create8x8ColorCropTestData(); + float[] input = Create8x8ColorCropTestData(); block.LoadFrom(input); this.Output.WriteLine("Input:"); this.PrintLinearData(input); @@ -371,18 +371,18 @@ namespace ImageSharp.Tests [InlineData(2)] public void FDCT8x4_LeftPart(int seed) { - var src = Create8x8RandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + Block8x8F destBlock = new Block8x8F(); - var expectedDest = new MutableSpan(64); + MutableSpan expectedDest = new MutableSpan(64); ReferenceImplementations.fDCT2D8x4_32f(src, expectedDest); DCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); - var actualDest = new MutableSpan(64); + MutableSpan actualDest = new MutableSpan(64); destBlock.CopyTo(actualDest); Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); @@ -393,18 +393,18 @@ namespace ImageSharp.Tests [InlineData(2)] public void FDCT8x4_RightPart(int seed) { - var src = Create8x8RandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + Block8x8F destBlock = new Block8x8F(); - var expectedDest = new MutableSpan(64); + MutableSpan expectedDest = new MutableSpan(64); ReferenceImplementations.fDCT2D8x4_32f(src.Slice(4), expectedDest.Slice(4)); DCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); - var actualDest = new MutableSpan(64); + MutableSpan actualDest = new MutableSpan(64); destBlock.CopyTo(actualDest); Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); @@ -415,20 +415,20 @@ namespace ImageSharp.Tests [InlineData(2)] public void TransformFDCT(int seed) { - var src = Create8x8RandomFloatData(-200, 200, seed); - var srcBlock = new Block8x8F(); + MutableSpan src = Create8x8RandomFloatData(-200, 200, seed); + Block8x8F srcBlock = new Block8x8F(); srcBlock.LoadFrom(src); - var destBlock = new Block8x8F(); + Block8x8F destBlock = new Block8x8F(); - var expectedDest = new MutableSpan(64); - var temp1 = new MutableSpan(64); - var temp2 = new Block8x8F(); + MutableSpan expectedDest = new MutableSpan(64); + MutableSpan temp1 = new MutableSpan(64); + Block8x8F temp2 = new Block8x8F(); ReferenceImplementations.fDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); DCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); - var actualDest = new MutableSpan(64); + MutableSpan actualDest = new MutableSpan(64); destBlock.CopyTo(actualDest); Assert.Equal(actualDest.Data, expectedDest.Data, new ApproximateFloatComparer(1f)); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 7cb9a7cf23..cdd892dcee 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -89,11 +89,10 @@ namespace ImageSharp.Tests { image.Save(ms, new JpegEncoder()); ms.Seek(0, SeekOrigin.Begin); - - Image mirror = provider.Factory.CreateImage(1, 1); - using (JpegDecoderCore decoder = new JpegDecoderCore(null)) + + using (JpegDecoderCore decoder = new JpegDecoderCore(null, null)) { - decoder.Decode(mirror, ms, true); + Image mirror = decoder.Decode(ms); Assert.Equal(decoder.ImageWidth, image.Width); Assert.Equal(decoder.ImageHeight, image.Height); @@ -125,7 +124,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_ExifProfileIsRead() { - var options = new DecoderOptions() + DecoderOptions options = new DecoderOptions() { IgnoreMetadata = false }; @@ -141,7 +140,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_ExifProfileIgnored() { - var options = new DecoderOptions() + DecoderOptions options = new DecoderOptions() { IgnoreMetadata = true }; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 741e785c0c..0833cb8680 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -74,7 +74,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsFalse_ExifProfileIsWritten() { - var options = new EncoderOptions() + EncoderOptions options = new EncoderOptions() { IgnoreMetadata = false }; @@ -88,7 +88,7 @@ namespace ImageSharp.Tests input.Save(memStream, new JpegFormat(), options); memStream.Position = 0; - using (Image output = new Image(memStream)) + using (Image output = Image.Load(memStream)) { Assert.NotNull(output.MetaData.ExifProfile); } @@ -99,7 +99,7 @@ namespace ImageSharp.Tests [Fact] public void Encode_IgnoreMetadataIsTrue_ExifProfileIgnored() { - var options = new JpegEncoderOptions() + JpegEncoderOptions options = new JpegEncoderOptions() { IgnoreMetadata = true }; @@ -113,7 +113,7 @@ namespace ImageSharp.Tests input.SaveAsJpeg(memStream, options); memStream.Position = 0; - using (Image output = new Image(memStream)) + using (Image output = Image.Load(memStream)) { Assert.Null(output.MetaData.ExifProfile); } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs index 50e678bf08..28a64a765c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegProfilingBenchmarks.cs @@ -50,7 +50,7 @@ namespace ImageSharp.Tests ExecutionCount, () => { - Image img = new Image(bytes); + Image img = Image.Load(bytes); }, // ReSharper disable once ExplicitCallerInfoArgument $"Decode {fileName}"); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs index 589317a361..50b94bc24c 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/ReferenceImplementationsTests.cs @@ -52,7 +52,7 @@ namespace ImageSharp.Tests.Formats.Jpg { MutableSpan original = Create8x8RandomIntData(-200, 200, seed); - var block = original.AddScalarToAllValues(128); + MutableSpan block = original.AddScalarToAllValues(128); ReferenceImplementations.IntegerReferenceDCT.TransformFDCTInplace(block); @@ -79,7 +79,7 @@ namespace ImageSharp.Tests.Formats.Jpg [InlineData(2, 0)] public void FloatingPointDCT_ReferenceImplementation_ForwardThenInverse(int seed, int startAt) { - var data = Create8x8RandomIntData(-200, 200, seed); + int[] data = Create8x8RandomIntData(-200, 200, seed); MutableSpan src = new MutableSpan(data).ConvertToFloat32MutableSpan(); MutableSpan dest = new MutableSpan(64); MutableSpan temp = new MutableSpan(64); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs index 8e228141df..ee38f500b0 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/YCbCrImageTests.cs @@ -55,7 +55,7 @@ namespace ImageSharp.Tests this.Output.WriteLine($"RATIO: {ratio}"); - var img = new YCbCrImage(400, 400, ratio); + YCbCrImage img = new YCbCrImage(400, 400, ratio); //this.PrintChannel("Y", img.YChannel); //this.PrintChannel("Cb", img.CbChannel); diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 921530806c..e03d42c9af 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() { - var options = new PngDecoderOptions() + PngDecoderOptions options = new PngDecoderOptions() { IgnoreMetadata = false }; @@ -33,7 +33,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - var options = new PngDecoderOptions() + PngDecoderOptions options = new PngDecoderOptions() { IgnoreMetadata = true }; @@ -49,7 +49,7 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - var options = new PngDecoderOptions() + PngDecoderOptions options = new PngDecoderOptions() { TextEncoding = Encoding.Unicode }; diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs index 49be751391..51cb0cdc00 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs @@ -8,47 +8,37 @@ using ImageSharp.Formats; namespace ImageSharp.Tests { using System.IO; + using System.Linq; using System.Threading.Tasks; - + using ImageSharp.IO; using Xunit; public class PngEncoderTests : FileTestBase { - [Fact] - public void ImageCanSaveIndexedPng() + [Theory] + [WithBlankImages(1, 1, PixelTypes.All)] + public void WritesFileMarker(TestImageProvider provider) + where TColor : struct, IPixel { - string path = CreateOutputDirectory("Png", "Indexed"); - - foreach (TestFile file in Files) + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.MetaData.Quality = 256; - image.Save(output, new PngFormat()); - } - } - } - } + image.Save(ms, new PngEncoder()); + + byte[] data = ms.ToArray().Take(8).ToArray(); + byte[] expected = { + 0x89, // Set the high bit. + 0x50, // P + 0x4E, // N + 0x47, // G + 0x0D, // Line ending CRLF + 0x0A, // Line ending CRLF + 0x1A, // EOF + 0x0A // LF + }; - [Fact] - public void ImageCanSavePngInParallel() - { - string path = this.CreateOutputDirectory("Png"); - - Parallel.ForEach( - Files, - file => - { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png")) - { - image.SaveAsPng(output); - } - } - }); + Assert.Equal(expected, data); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs new file mode 100644 index 0000000000..882f903d68 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Formats.Png +{ + using System; + using System.Collections.Generic; + using System.Text; + using System.IO; + using Xunit; + using ImageSharp.Formats; + using System.Linq; + using ImageSharp.IO; + + public class PngSmokeTests + { + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.All)] + public void GeneralTest(TestImageProvider provider) + where TColor : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.CheckSimilarity(image, img2); + } + } + } + + [Theory] + [WithTestPatternImages(100, 100, PixelTypes.All)] + public void CanSaveIndexedPng(TestImageProvider provider) + where TColor : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + image.MetaData.Quality = 256; + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + ImageComparer.CheckSimilarity(image, img2); + } + } + } + + [Theory] + [WithTestPatternImages(300, 300, PixelTypes.All)] + public void Resize(TestImageProvider provider) + where TColor : struct, IPixel + { + // does saving a file then repoening mean both files are identical??? + using (Image image = provider.GetImage()) + using (MemoryStream ms = new MemoryStream()) + { + // image.Save(provider.Utility.GetTestOutputFileName("png")); + image.Resize(100, 100); + // image.Save(provider.Utility.GetTestOutputFileName("png", "resize")); + + image.Save(ms, new PngEncoder()); + ms.Position = 0; + using (Image img2 = Image.Load(ms, new PngDecoder())) + { + ImageComparer.CheckSimilarity(image, img2); + } + } + } + } +} diff --git a/tests/ImageSharp.Tests/Helpers/MathFTests.cs b/tests/ImageSharp.Tests/Helpers/MathFTests.cs new file mode 100644 index 0000000000..7f3fb77d0a --- /dev/null +++ b/tests/ImageSharp.Tests/Helpers/MathFTests.cs @@ -0,0 +1,69 @@ +namespace ImageSharp.Tests.Helpers +{ + using System; + + using Xunit; + + public class MathFTests + { + [Fact] + public void MathF_PI_Is_Equal() + { + Assert.Equal(MathF.PI, (float)Math.PI); + } + + [Fact] + public void MathF_Ceililng_Is_Equal() + { + Assert.Equal(MathF.Ceiling(0.3333F), (float)Math.Ceiling(0.3333F)); + } + + [Fact] + public void MathF_Abs_Is_Equal() + { + Assert.Equal(MathF.Abs(-0.3333F), (float)Math.Abs(-0.3333F)); + } + + [Fact] + public void MathF_Exp_Is_Equal() + { + Assert.Equal(MathF.Exp(1.2345F), (float)Math.Exp(1.2345F)); + } + + [Fact] + public void MathF_Floor_Is_Equal() + { + Assert.Equal(MathF.Floor(1.2345F), (float)Math.Floor(1.2345F)); + } + + [Fact] + public void MathF_Min_Is_Equal() + { + Assert.Equal(MathF.Min(1.2345F, 5.4321F), (float)Math.Min(1.2345F, 5.4321F)); + } + + [Fact] + public void MathF_Max_Is_Equal() + { + Assert.Equal(MathF.Max(1.2345F, 5.4321F), (float)Math.Max(1.2345F, 5.4321F)); + } + + [Fact] + public void MathF_Pow_Is_Equal() + { + Assert.Equal(MathF.Pow(1.2345F, 5.4321F), (float)Math.Pow(1.2345F, 5.4321F)); + } + + [Fact] + public void MathF_Sin_Is_Equal() + { + Assert.Equal(MathF.Sin(1.2345F), (float)Math.Sin(1.2345F)); + } + + [Fact] + public void MathF_Sqrt_Is_Equal() + { + Assert.Equal(MathF.Sqrt(2F), (float)Math.Sqrt(2F)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/BigEndianBitConverter.CopyBytesTests.cs b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.CopyBytesTests.cs new file mode 100644 index 0000000000..4cdf9122a9 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.CopyBytesTests.cs @@ -0,0 +1,230 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using ImageSharp.IO; + using Xunit; + + /// + /// The tests. + /// + public class BigEndianBitConverterCopyBytesTests + { + [Fact] + public void CopyToWithNullBufferThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(false, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((short)42, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ushort)42, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42u, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42L, null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ulong)42L, null, 0)); + } + + [Fact] + public void CopyToWithIndexTooBigThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(false, new byte[1], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((short)42, new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ushort)42, new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42, new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42u, new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42L, new byte[8], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ulong)42L, new byte[8], 1)); + } + + [Fact] + public void CopyToWithBufferTooSmallThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(false, new byte[0], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((short)42, new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ushort)42, new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42, new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42u, new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes(42L, new byte[7], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.CopyBytes((ulong)42L, new byte[7], 0)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesBoolean() + { + byte[] buffer = new byte[1]; + + EndianBitConverter.BigEndianConverter.CopyBytes(false, buffer, 0); + this.CheckBytes(new byte[] { 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(true, buffer, 0); + this.CheckBytes(new byte[] { 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesShort() + { + byte[] buffer = new byte[2]; + + EndianBitConverter.BigEndianConverter.CopyBytes((short)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((short)1, buffer, 0); + this.CheckBytes(new byte[] { 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((short)256, buffer, 0); + this.CheckBytes(new byte[] { 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((short)-1, buffer, 0); + this.CheckBytes(new byte[] { 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((short)257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesUShort() + { + byte[] buffer = new byte[2]; + + EndianBitConverter.BigEndianConverter.CopyBytes((ushort)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((ushort)1, buffer, 0); + this.CheckBytes(new byte[] { 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((ushort)256, buffer, 0); + this.CheckBytes(new byte[] { 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(ushort.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((ushort)257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesInt() + { + byte[] buffer = new byte[4]; + + EndianBitConverter.BigEndianConverter.CopyBytes(0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(65536, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(16777216, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(-1, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(257, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesUInt() + { + byte[] buffer = new byte[4]; + + EndianBitConverter.BigEndianConverter.CopyBytes((uint)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((uint)1, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((uint)256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((uint)65536, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((uint)16777216, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(uint.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes((uint)257, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesLong() + { + byte[] buffer = new byte[8]; + + EndianBitConverter.BigEndianConverter.CopyBytes(0L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(256L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(65536L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(16777216L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(4294967296L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776L * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776L * 256 * 256, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(-1L, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(257L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesULong() + { + byte[] buffer = new byte[8]; + + EndianBitConverter.BigEndianConverter.CopyBytes(0UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(256UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(65536UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(16777216UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(4294967296UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776UL * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(1099511627776UL * 256 * 256, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(ulong.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, buffer); + EndianBitConverter.BigEndianConverter.CopyBytes(257UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, buffer); + } + + /// + /// Tests the two byte arrays for equality. + /// + /// The expected bytes. + /// The actual bytes. + private void CheckBytes(byte[] expected, byte[] actual) + { + Assert.Equal(expected.Length, actual.Length); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/BigEndianBitConverterTests.cs b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.GetBytesTests.cs similarity index 54% rename from tests/ImageSharp.Tests/IO/BigEndianBitConverterTests.cs rename to tests/ImageSharp.Tests/IO/BigEndianBitConverter.GetBytesTests.cs index 2030c3dcad..06962e0106 100644 --- a/tests/ImageSharp.Tests/IO/BigEndianBitConverterTests.cs +++ b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.GetBytesTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,25 +6,47 @@ namespace ImageSharp.Tests.IO { using ImageSharp.IO; - using Xunit; /// /// The tests. /// - public class BigEndianBitConverterTests + public class BigEndianBitConverterGetBytesTests { + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void GetBytesBoolean() + { + this.CheckBytes(new byte[] { 0 }, EndianBitConverter.BigEndianConverter.GetBytes(false)); + this.CheckBytes(new byte[] { 1 }, EndianBitConverter.BigEndianConverter.GetBytes(true)); + } + /// /// Tests that passing a returns the correct bytes. /// [Fact] public void GetBytesShort() { - this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((short)0)); - this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((short)1)); - this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((short)256)); - this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((short)-1)); - this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((short)257)); + this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((short)0)); + this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((short)1)); + this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((short)256)); + this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes((short)-1)); + this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((short)257)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void GetBytesUShort() + { + this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((ushort)0)); + this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((ushort)1)); + this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((ushort)256)); + this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes(ushort.MaxValue)); + this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((ushort)257)); } /// @@ -33,13 +55,13 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesInt() { - this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)0)); - this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)1)); - this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)256)); - this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)65536)); - this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)16777216)); - this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)-1)); - this.CheckBytes(new byte[] { 0, 0, 1, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((int)257)); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(0)); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(1)); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(256)); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(65536)); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(16777216)); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes(-1)); + this.CheckBytes(new byte[] { 0, 0, 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(257)); } /// @@ -48,13 +70,13 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesUInt() { - this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)0)); - this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)1)); - this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)256)); - this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)65536)); - this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)16777216)); - this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)uint.MaxValue)); - this.CheckBytes(new byte[] { 0, 0, 1, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes((uint)257)); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)0)); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)1)); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)256)); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)65536)); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)16777216)); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes(uint.MaxValue)); + this.CheckBytes(new byte[] { 0, 0, 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes((uint)257)); } /// @@ -63,17 +85,17 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesLong() { - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(0L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(256L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(65536L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(16777216L)); - this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(4294967296L)); - this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776L)); - this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776L * 256)); - this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776L * 256 * 256)); - this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(-1L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(257L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(0L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(1L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(256L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(65536L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(16777216L)); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(4294967296L)); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776L)); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776L * 256)); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776L * 256 * 256)); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes(-1L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(257L)); } /// @@ -82,17 +104,17 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesULong() { - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(0UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(256UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(65536UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(16777216UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(4294967296UL)); - this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776UL)); - this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776UL * 256)); - this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(1099511627776UL * 256 * 256)); - this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(ulong.MaxValue)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, EndianBitConverter.GetConverter(Endianness.BigEndian).GetBytes(257UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(0UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(1UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(256UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(65536UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(16777216UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(4294967296UL)); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776UL)); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776UL * 256)); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.BigEndianConverter.GetBytes(1099511627776UL * 256 * 256)); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.BigEndianConverter.GetBytes(ulong.MaxValue)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, EndianBitConverter.BigEndianConverter.GetBytes(257UL)); } /// diff --git a/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs new file mode 100644 index 0000000000..50a86d3cc0 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/BigEndianBitConverter.ToTypeTests.cs @@ -0,0 +1,214 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using ImageSharp.IO; + using Xunit; + + /// + /// The tests. + /// + public class BigEndianBitConverterTests + { + [Fact] + public void CopyToWithNullBufferThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToBoolean(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt16(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt16(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt32(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt32(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt64(null, 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt64(null, 0)); + } + + [Fact] + public void CopyToWithIndexTooBigThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToBoolean(new byte[1], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt16(new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt16(new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt32(new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt32(new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt64(new byte[8], 1)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt64(new byte[8], 1)); + } + + [Fact] + public void CopyToWithBufferTooSmallThrowsException() + { + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToBoolean(new byte[0], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt16(new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt16(new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt32(new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt32(new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToInt64(new byte[7], 0)); + Assert.Throws(() => EndianBitConverter.BigEndianConverter.ToUInt64(new byte[7], 0)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToBoolean() + { + Assert.Equal(false, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 0 }, 0)); + Assert.Equal(true, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 1 }, 0)); + Assert.Equal(true, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 42 }, 0)); + + Assert.Equal(false, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 1, 0 }, 1)); + Assert.Equal(true, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 0, 1 }, 1)); + Assert.Equal(true, EndianBitConverter.BigEndianConverter.ToBoolean(new byte[] { 0, 42 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt16() + { + Assert.Equal((short)0, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 0, 0 }, 0)); + Assert.Equal((short)1, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 0, 1 }, 0)); + Assert.Equal((short)256, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 1, 0 }, 0)); + Assert.Equal((short)-1, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 255, 255 }, 0)); + Assert.Equal((short)257, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 1, 1 }, 0)); + + Assert.Equal((short)0, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 1, 0, 0 }, 1)); + Assert.Equal((short)1, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 1, 0, 1 }, 1)); + Assert.Equal((short)256, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 0, 1, 0 }, 1)); + Assert.Equal((short)-1, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 0, 255, 255 }, 1)); + Assert.Equal((short)257, EndianBitConverter.BigEndianConverter.ToInt16(new byte[] { 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt16() + { + Assert.Equal((ushort)0, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 0, 0 }, 0)); + Assert.Equal((ushort)1, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 0, 1 }, 0)); + Assert.Equal((ushort)256, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 1, 0 }, 0)); + Assert.Equal(ushort.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 255, 255 }, 0)); + Assert.Equal((ushort)257, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 1, 1 }, 0)); + + Assert.Equal((ushort)0, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 1, 0, 0 }, 1)); + Assert.Equal((ushort)1, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 1, 0, 1 }, 1)); + Assert.Equal((ushort)256, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 0, 1, 0 }, 1)); + Assert.Equal(ushort.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 0, 255, 255 }, 1)); + Assert.Equal((ushort)257, EndianBitConverter.BigEndianConverter.ToUInt16(new byte[] { 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt32() + { + Assert.Equal(0, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 0, 0, 0 }, 0)); + Assert.Equal(1, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 0, 0, 1 }, 0)); + Assert.Equal(256, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 0, 1, 0 }, 0)); + Assert.Equal(65536, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 1, 0, 0 }, 0)); + Assert.Equal(16777216, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0 }, 0)); + Assert.Equal(-1, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 255, 255, 255, 255 }, 0)); + Assert.Equal(257, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 0, 1, 1 }, 0)); + + Assert.Equal(0, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(1, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0, 1 }, 1)); + Assert.Equal(256, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 0, 1, 0 }, 1)); + Assert.Equal(65536, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 1, 0, 0 }, 1)); + Assert.Equal(16777216, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(-1, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 0, 255, 255, 255, 255 }, 1)); + Assert.Equal(257, EndianBitConverter.BigEndianConverter.ToInt32(new byte[] { 1, 0, 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt32() + { + Assert.Equal((uint)0, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 0, 0, 0 }, 0)); + Assert.Equal((uint)1, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 0, 0, 1 }, 0)); + Assert.Equal((uint)256, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 0, 1, 0 }, 0)); + Assert.Equal((uint)65536, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 1, 0, 0 }, 0)); + Assert.Equal((uint)16777216, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0 }, 0)); + Assert.Equal(uint.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 255, 255, 255, 255 }, 0)); + Assert.Equal((uint)257, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 0, 1, 1 }, 0)); + + Assert.Equal((uint)0, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0, 0 }, 1)); + Assert.Equal((uint)1, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0, 1 }, 1)); + Assert.Equal((uint)256, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 1, 0 }, 1)); + Assert.Equal((uint)65536, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 1, 0, 0 }, 1)); + Assert.Equal((uint)16777216, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(uint.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 0, 255, 255, 255, 255 }, 1)); + Assert.Equal((uint)257, EndianBitConverter.BigEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt64() + { + Assert.Equal(0L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, 0)); + Assert.Equal(256L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, 0)); + Assert.Equal(65536L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, 0)); + Assert.Equal(16777216L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, 0)); + Assert.Equal(4294967296L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776L * 256, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776L * 256 * 256, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(-1L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, 0)); + Assert.Equal(257L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, 0)); + + Assert.Equal(0L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 1 }, 1)); + Assert.Equal(256L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 0 }, 1)); + Assert.Equal(65536L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 }, 1)); + Assert.Equal(16777216L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(4294967296L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 1, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776L * 256, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 1, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776L * 256 * 256, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(-1L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 0, 255, 255, 255, 255, 255, 255, 255, 255 }, 1)); + Assert.Equal(257L, EndianBitConverter.BigEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt64() + { + Assert.Equal(0UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, 0)); + Assert.Equal(256UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, 0)); + Assert.Equal(65536UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, 0)); + Assert.Equal(16777216UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, 0)); + Assert.Equal(4294967296UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776UL * 256, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776UL * 256 * 256, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(ulong.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, 0)); + Assert.Equal(257UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 1 }, 0)); + + Assert.Equal(0UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 1 }, 1)); + Assert.Equal(256UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 0 }, 1)); + Assert.Equal(65536UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 }, 1)); + Assert.Equal(16777216UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(4294967296UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 1, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776UL * 256, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 1, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776UL * 256 * 256, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(ulong.MaxValue, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 0, 255, 255, 255, 255, 255, 255, 255, 255 }, 1)); + Assert.Equal(257UL, EndianBitConverter.BigEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 1 }, 1)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.CopyBytesTests.cs b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.CopyBytesTests.cs new file mode 100644 index 0000000000..5ff47409b0 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.CopyBytesTests.cs @@ -0,0 +1,230 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using ImageSharp.IO; + using Xunit; + + /// + /// The tests. + /// + public class LittleEndianBitConverterCopyBytesTests + { + [Fact] + public void CopyToWithNullBufferThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(false, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((short)42, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)42, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42u, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42L, null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ulong)42L, null, 0)); + } + + [Fact] + public void CopyToWithIndexTooBigThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(false, new byte[1], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((short)42, new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)42, new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42, new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42u, new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42L, new byte[8], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ulong)42L, new byte[8], 1)); + } + + [Fact] + public void CopyToWithBufferTooSmallThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(false, new byte[0], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((short)42, new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)42, new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42, new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42u, new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes(42L, new byte[7], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.CopyBytes((ulong)42L, new byte[7], 0)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesBoolean() + { + byte[] buffer = new byte[1]; + + EndianBitConverter.LittleEndianConverter.CopyBytes(false, buffer, 0); + this.CheckBytes(new byte[] { 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(true, buffer, 0); + this.CheckBytes(new byte[] { 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesShort() + { + byte[] buffer = new byte[2]; + + EndianBitConverter.LittleEndianConverter.CopyBytes((short)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((short)1, buffer, 0); + this.CheckBytes(new byte[] { 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((short)256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((short)-1, buffer, 0); + this.CheckBytes(new byte[] { 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((short)257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesUShort() + { + byte[] buffer = new byte[2]; + + EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)1, buffer, 0); + this.CheckBytes(new byte[] { 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(ushort.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((ushort)257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesInt() + { + byte[] buffer = new byte[4]; + + EndianBitConverter.LittleEndianConverter.CopyBytes(0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(65536, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(16777216, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(-1, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1, 0, 0 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesUInt() + { + byte[] buffer = new byte[4]; + + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)0, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)1, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)256, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)65536, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)16777216, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(uint.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes((uint)257, buffer, 0); + this.CheckBytes(new byte[] { 1, 1, 0, 0 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesLong() + { + byte[] buffer = new byte[8]; + + EndianBitConverter.LittleEndianConverter.CopyBytes(0L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1L, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(256L, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(65536L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(16777216L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(4294967296L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776L, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776L * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776L * 256 * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(-1L, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(257L, buffer, 0); + this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, buffer); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void CopyBytesULong() + { + byte[] buffer = new byte[8]; + + EndianBitConverter.LittleEndianConverter.CopyBytes(0UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1UL, buffer, 0); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(256UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(65536UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(16777216UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(4294967296UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776UL, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776UL * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(1099511627776UL * 256 * 256, buffer, 0); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(ulong.MaxValue, buffer, 0); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, buffer); + EndianBitConverter.LittleEndianConverter.CopyBytes(257UL, buffer, 0); + this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, buffer); + } + + /// + /// Tests the two byte arrays for equality. + /// + /// The expected bytes. + /// The actual bytes. + private void CheckBytes(byte[] expected, byte[] actual) + { + Assert.Equal(expected.Length, actual.Length); + Assert.Equal(expected, actual); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/LittleEndianBitConverterTests.cs b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.GetBytesTests.cs similarity index 53% rename from tests/ImageSharp.Tests/IO/LittleEndianBitConverterTests.cs rename to tests/ImageSharp.Tests/IO/LittleEndianBitConverter.GetBytesTests.cs index fe76623063..7fd7a97d43 100644 --- a/tests/ImageSharp.Tests/IO/LittleEndianBitConverterTests.cs +++ b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.GetBytesTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -6,25 +6,47 @@ namespace ImageSharp.Tests.IO { using ImageSharp.IO; - using Xunit; /// /// The tests. /// - public class LittleEndianBitConverterTests + public class LittleEndianBitConverterGetBytesTests { + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void GetBytesBoolean() + { + this.CheckBytes(new byte[] { 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(false)); + this.CheckBytes(new byte[] { 1 }, EndianBitConverter.LittleEndianConverter.GetBytes(true)); + } + /// /// Tests that passing a returns the correct bytes. /// [Fact] public void GetBytesShort() { - this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((short)0)); - this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((short)1)); - this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((short)256)); - this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((short)-1)); - this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((short)257)); + this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((short)0)); + this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((short)1)); + this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes((short)256)); + this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes((short)-1)); + this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes((short)257)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void GetBytesUShort() + { + this.CheckBytes(new byte[] { 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((ushort)0)); + this.CheckBytes(new byte[] { 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((ushort)1)); + this.CheckBytes(new byte[] { 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes((ushort)256)); + this.CheckBytes(new byte[] { 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes(ushort.MaxValue)); + this.CheckBytes(new byte[] { 1, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes((ushort)257)); } /// @@ -33,13 +55,13 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesInt() { - this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)0)); - this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)1)); - this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)256)); - this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)65536)); - this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)16777216)); - this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)-1)); - this.CheckBytes(new byte[] { 1, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((int)257)); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(0)); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1)); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(256)); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(65536)); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes(16777216)); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes(-1)); + this.CheckBytes(new byte[] { 1, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(257)); } /// @@ -48,13 +70,13 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesUInt() { - this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)0)); - this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)1)); - this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)256)); - this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)65536)); - this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)16777216)); - this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)uint.MaxValue)); - this.CheckBytes(new byte[] { 1, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes((uint)257)); + this.CheckBytes(new byte[] { 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)0)); + this.CheckBytes(new byte[] { 1, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)1)); + this.CheckBytes(new byte[] { 0, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)256)); + this.CheckBytes(new byte[] { 0, 0, 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)65536)); + this.CheckBytes(new byte[] { 0, 0, 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)16777216)); + this.CheckBytes(new byte[] { 255, 255, 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes(uint.MaxValue)); + this.CheckBytes(new byte[] { 1, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes((uint)257)); } /// @@ -63,17 +85,17 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesLong() { - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(0L)); - this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1L)); - this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(256L)); - this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(65536L)); - this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(16777216L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(4294967296L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776L)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776L * 256)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776L * 256 * 256)); - this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(-1L)); - this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(257L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(0L)); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1L)); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(256L)); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(65536L)); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(16777216L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(4294967296L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776L)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776L * 256)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776L * 256 * 256)); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes(-1L)); + this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(257L)); } /// @@ -82,17 +104,17 @@ namespace ImageSharp.Tests.IO [Fact] public void GetBytesULong() { - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(0UL)); - this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1UL)); - this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(256UL)); - this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(65536UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(16777216UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(4294967296UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776UL)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776UL * 256)); - this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(1099511627776UL * 256 * 256)); - this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(ulong.MaxValue)); - this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.GetConverter(Endianness.LittleEndian).GetBytes(257UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(0UL)); + this.CheckBytes(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1UL)); + this.CheckBytes(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(256UL)); + this.CheckBytes(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(65536UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(16777216UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(4294967296UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776UL)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776UL * 256)); + this.CheckBytes(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, EndianBitConverter.LittleEndianConverter.GetBytes(1099511627776UL * 256 * 256)); + this.CheckBytes(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, EndianBitConverter.LittleEndianConverter.GetBytes(ulong.MaxValue)); + this.CheckBytes(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, EndianBitConverter.LittleEndianConverter.GetBytes(257UL)); } /// diff --git a/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs new file mode 100644 index 0000000000..fa8b2a1a25 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LittleEndianBitConverter.ToTypeTests.cs @@ -0,0 +1,212 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using ImageSharp.IO; + using Xunit; + + /// + /// The tests. + /// + public class LittleEndianBitConverterToTypeTests + { + [Fact] + public void CopyToWithNullBufferThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToBoolean(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt16(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt16(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt32(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt32(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt64(null, 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt64(null, 0)); + } + + [Fact] + public void CopyToWithIndexTooBigThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[1], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt16(new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[2], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt32(new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[4], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt64(new byte[8], 1)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[8], 1)); + } + + [Fact] + public void CopyToWithBufferTooSmallThrowsException() + { + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[0], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt16(new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[1], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt32(new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[3], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToInt64(new byte[7], 0)); + Assert.Throws(() => EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[7], 0)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToBoolean() + { + Assert.Equal(false, EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[] { 0 }, 0)); + Assert.Equal(true, EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[] { 1 }, 0)); + + Assert.Equal(false, EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[] { 1, 0 }, 1)); + Assert.Equal(true, EndianBitConverter.LittleEndianConverter.ToBoolean(new byte[] { 0, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt16() + { + Assert.Equal((short)0, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 0, 0 }, 0)); + Assert.Equal((short)1, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 1, 0 }, 0)); + Assert.Equal((short)256, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 0, 1 }, 0)); + Assert.Equal((short)-1, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 255, 255 }, 0)); + Assert.Equal((short)257, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 1, 1 }, 0)); + + Assert.Equal((short)0, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 1, 0, 0 }, 1)); + Assert.Equal((short)1, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 0, 1, 0 }, 1)); + Assert.Equal((short)256, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 1, 0, 1 }, 1)); + Assert.Equal((short)-1, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 0, 255, 255 }, 1)); + Assert.Equal((short)257, EndianBitConverter.LittleEndianConverter.ToInt16(new byte[] { 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt16() + { + Assert.Equal((ushort)0, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 0, 0 }, 0)); + Assert.Equal((ushort)1, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 1, 0 }, 0)); + Assert.Equal((ushort)256, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 0, 1 }, 0)); + Assert.Equal(ushort.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 255, 255 }, 0)); + Assert.Equal((ushort)257, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 1, 1 }, 0)); + + Assert.Equal((ushort)0, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 1, 0, 0 }, 1)); + Assert.Equal((ushort)1, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 0, 1, 0 }, 1)); + Assert.Equal((ushort)256, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 1, 0, 1 }, 1)); + Assert.Equal(ushort.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 0, 255, 255 }, 1)); + Assert.Equal((ushort)257, EndianBitConverter.LittleEndianConverter.ToUInt16(new byte[] { 0, 1, 1 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt32() + { + Assert.Equal(0, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 0, 0, 0 }, 0)); + Assert.Equal(1, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0 }, 0)); + Assert.Equal(256, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 1, 0, 0 }, 0)); + Assert.Equal(65536, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 0, 1, 0 }, 0)); + Assert.Equal(16777216, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 0, 0, 1 }, 0)); + Assert.Equal(-1, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 255, 255, 255, 255 }, 0)); + Assert.Equal(257, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 1, 0, 0 }, 0)); + + Assert.Equal(0, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(1, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(256, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 0, 1, 0, 0 }, 1)); + Assert.Equal(65536, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 0, 0, 1, 0 }, 1)); + Assert.Equal(16777216, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 1, 0, 0, 0, 1 }, 1)); + Assert.Equal(-1, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 255, 255, 255, 255 }, 1)); + Assert.Equal(257, EndianBitConverter.LittleEndianConverter.ToInt32(new byte[] { 0, 1, 1, 0, 0 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt32() + { + Assert.Equal((uint)0, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 0, 0, 0 }, 0)); + Assert.Equal((uint)1, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0 }, 0)); + Assert.Equal((uint)256, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 1, 0, 0 }, 0)); + Assert.Equal((uint)65536, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 0, 1, 0 }, 0)); + Assert.Equal((uint)16777216, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 0, 0, 1 }, 0)); + Assert.Equal(uint.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 255, 255, 255, 255 }, 0)); + Assert.Equal((uint)257, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 1, 0, 0 }, 0)); + + Assert.Equal((uint)0, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0, 0 }, 1)); + Assert.Equal((uint)1, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 1, 0, 0, 0 }, 1)); + Assert.Equal((uint)256, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 0, 1, 0, 0 }, 1)); + Assert.Equal((uint)65536, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 1, 0 }, 1)); + Assert.Equal((uint)16777216, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 1, 0, 0, 0, 1 }, 1)); + Assert.Equal(uint.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 255, 255, 255, 255 }, 1)); + Assert.Equal((uint)257, EndianBitConverter.LittleEndianConverter.ToUInt32(new byte[] { 0, 1, 1, 0, 0 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToInt64() + { + Assert.Equal(0L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(256L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(65536L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(16777216L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, 0)); + Assert.Equal(4294967296L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, 0)); + Assert.Equal(1099511627776L * 256, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, 0)); + Assert.Equal(1099511627776L * 256 * 256, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, 0)); + Assert.Equal(-1L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, 0)); + Assert.Equal(257L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, 0)); + + Assert.Equal(0L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(256L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 1, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(65536L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 1, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(16777216L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(4294967296L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 }, 1)); + Assert.Equal(1099511627776L * 256, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 0 }, 1)); + Assert.Equal(1099511627776L * 256 * 256, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 1 }, 1)); + Assert.Equal(-1L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 255, 255, 255, 255, 255, 255, 255, 255 }, 1)); + Assert.Equal(257L, EndianBitConverter.LittleEndianConverter.ToInt64(new byte[] { 0, 1, 1, 0, 0, 0, 0, 0, 0 }, 1)); + } + + /// + /// Tests that passing a returns the correct bytes. + /// + [Fact] + public void ToUInt64() + { + Assert.Equal(0UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(1UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(256UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(65536UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 1, 0, 0, 0, 0, 0 }, 0)); + Assert.Equal(16777216UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 1, 0, 0, 0, 0 }, 0)); + Assert.Equal(4294967296UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 1, 0, 0, 0 }, 0)); + Assert.Equal(1099511627776UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 1, 0, 0 }, 0)); + Assert.Equal(1099511627776UL * 256, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 1, 0 }, 0)); + Assert.Equal(1099511627776UL * 256 * 256, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 }, 0)); + Assert.Equal(ulong.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 255, 255, 255, 255, 255, 255, 255, 255 }, 0)); + Assert.Equal(257UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 1, 0, 0, 0, 0, 0, 0 }, 0)); + + Assert.Equal(0UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(1UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 1, 0, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(256UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 1, 0, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(65536UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 1, 0, 0, 0, 0, 0 }, 1)); + Assert.Equal(16777216UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 1, 0, 0, 0, 0 }, 1)); + Assert.Equal(4294967296UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 1, 0, 0, 0 }, 1)); + Assert.Equal(1099511627776UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 1, 0, 0 }, 1)); + Assert.Equal(1099511627776UL * 256, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 1, 0 }, 1)); + Assert.Equal(1099511627776UL * 256 * 256, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 1 }, 1)); + Assert.Equal(ulong.MaxValue, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 255, 255, 255, 255, 255, 255, 255, 255 }, 1)); + Assert.Equal(257UL, EndianBitConverter.LittleEndianConverter.ToUInt64(new byte[] { 0, 1, 1, 0, 0, 0, 0, 0, 0 }, 1)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/IO/LocalFileSystem.cs b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs new file mode 100644 index 0000000000..472d643cd3 --- /dev/null +++ b/tests/ImageSharp.Tests/IO/LocalFileSystem.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.IO +{ + using System; + using System.IO; + using ImageSharp.IO; + + using Xunit; + + public class LocalFileSystemTests + { + [Fact] + public void OpenRead() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + File.WriteAllText(path, testData); + + LocalFileSystem fs = new LocalFileSystem(); + + using (StreamReader r = new StreamReader(fs.OpenRead(path))) + { + string data = r.ReadToEnd(); + + Assert.Equal(testData, data); + } + + File.Delete(path); + } + + [Fact] + public void Create() + { + string path = Path.GetTempFileName(); + string testData = Guid.NewGuid().ToString(); + LocalFileSystem fs = new LocalFileSystem(); + + using (StreamWriter r = new StreamWriter(fs.Create(path))) + { + r.Write(testData); + } + + string data = File.ReadAllText(path); + Assert.Equal(testData, data); + + File.Delete(path); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs new file mode 100644 index 0000000000..ddb9414cc4 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -0,0 +1,520 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using System.Linq; + using ImageSharp.Formats; + using ImageSharp.IO; + using Moq; + using Xunit; + + /// + /// Tests the class. + /// + public class ImageLoadTests : IDisposable + { + private readonly Mock fileSystem; + private readonly IDecoderOptions decoderOptions; + private Image returnImage; + private Mock localDecoder; + private Mock localFormat; + private readonly string FilePath; + + public Configuration LocalConfiguration { get; private set; } + public byte[] Marker { get; private set; } + public MemoryStream DataStream { get; private set; } + public byte[] DecodedData { get; private set; } + + public ImageLoadTests() + { + this.returnImage = new Image(1, 1); + + this.localDecoder = new Mock(); + this.localFormat = new Mock(); + this.localFormat.Setup(x => x.Decoder).Returns(this.localDecoder.Object); + this.localFormat.Setup(x => x.Encoder).Returns(new Mock().Object); + this.localFormat.Setup(x => x.MimeType).Returns("img/test"); + this.localFormat.Setup(x => x.Extension).Returns("png"); + this.localFormat.Setup(x => x.HeaderSize).Returns(1); + this.localFormat.Setup(x => x.IsSupportedFileFormat(It.IsAny())).Returns(true); + this.localFormat.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + + this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny(), It.IsAny())) + + .Callback((c, s, o) => { + using (var ms = new MemoryStream()) + { + s.CopyTo(ms); + this.DecodedData = ms.ToArray(); + } + }) + .Returns(this.returnImage); + + this.fileSystem = new Mock(); + + this.LocalConfiguration = new Configuration(this.localFormat.Object) + { + FileSystem = this.fileSystem.Object + }; + TestFormat.RegisterGloablTestFormat(); + this.Marker = Guid.NewGuid().ToByteArray(); + this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); + this.decoderOptions = new Mock().Object; + + this.FilePath = Guid.NewGuid().ToString(); + this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); + + TestFileSystem.RegisterGloablTestFormat(); + TestFileSystem.Global.AddFile(this.FilePath, this.DataStream); + } + + [Fact] + public void LoadFromStream() + { + Image img = Image.Load(this.DataStream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromStreamWithType() + { + Image img = Image.Load(this.DataStream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromStreamWithOptions() + { + Image img = Image.Load(this.DataStream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromStreamWithTypeAndOptions() + { + Image img = Image.Load(this.DataStream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromStreamWithConfig() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(this.LocalConfiguration, stream); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); + + } + + [Fact] + public void LoadFromStreamWithTypeAndConfig() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(this.LocalConfiguration, stream); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, null)); + + } + + [Fact] + public void LoadFromStreamWithConfigAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); + + } + + [Fact] + public void LoadFromStreamWithTypeAndConfigAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(this.LocalConfiguration, stream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream, this.decoderOptions)); + + } + + + + [Fact] + public void LoadFromStreamWithDecoder() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); + } + + [Fact] + public void LoadFromStreamWithTypeAndDecoder() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, null)); + } + + [Fact] + public void LoadFromStreamWithDecoderAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, this.decoderOptions)); + } + + [Fact] + public void LoadFromStreamWithTypeAndDecoderAndOptions() + { + Stream stream = new MemoryStream(); + Image img = Image.Load(stream, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream, this.decoderOptions)); + } + + [Fact] + public void LoadFromBytes() + { + Image img = Image.Load(this.DataStream.ToArray()); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromBytesWithType() + { + Image img = Image.Load(this.DataStream.ToArray()); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromBytesWithOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromBytesWithTypeAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromBytesWithConfig() + { + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); + + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndConfig() + { + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), null)); + + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithConfigAndOptions() + { + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), this.decoderOptions)); + + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndConfigAndOptions() + { + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray(), this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, It.IsAny(), this.decoderOptions)); + + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + + [Fact] + public void LoadFromBytesWithDecoder() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndDecoder() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), null)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithDecoderAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromBytesWithTypeAndDecoderAndOptions() + { + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny(), this.decoderOptions)); + Assert.Equal(this.DataStream.ToArray(), this.DecodedData); + } + + [Fact] + public void LoadFromFile() + { + Image img = Image.Load(this.DataStream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromFileWithType() + { + Image img = Image.Load(this.DataStream); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, null, Configuration.Default); + + } + + [Fact] + public void LoadFromFileWithOptions() + { + Image img = Image.Load(this.DataStream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromFileWithTypeAndOptions() + { + Image img = Image.Load(this.DataStream, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); + Assert.Equal(TestFormat.GlobalTestFormat, img.CurrentImageFormat); + + TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, this.decoderOptions, Configuration.Default); + + } + + [Fact] + public void LoadFromFileWithConfig() + { + Image img = Image.Load(this.LocalConfiguration, this.FilePath); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); + + } + + [Fact] + public void LoadFromFileWithTypeAndConfig() + { + Image img = Image.Load(this.LocalConfiguration, this.FilePath); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, null)); + + } + + [Fact] + public void LoadFromFileWithConfigAndOptions() + { + Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); + + } + + [Fact] + public void LoadFromFileWithTypeAndConfigAndOptions() + { + Image img = Image.Load(this.LocalConfiguration, this.FilePath, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + Assert.Equal(this.localFormat.Object, img.CurrentImageFormat); + + this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream, this.decoderOptions)); + + } + + + [Fact] + public void LoadFromFileWithDecoder() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithTypeAndDecoder() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, null)); + } + + [Fact] + public void LoadFromFileWithDecoderAndOptions() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); + } + + [Fact] + public void LoadFromFileWithTypeAndDecoderAndOptions() + { + Image img = Image.Load(this.FilePath, this.localDecoder.Object, this.decoderOptions); + + Assert.NotNull(img); + Assert.Equal(this.returnImage, img); + this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream, this.decoderOptions)); + } + + public void Dispose() + { + // clean up the global object; + this.returnImage?.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs new file mode 100644 index 0000000000..0d1c3e09b5 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -0,0 +1,186 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + using System.Linq; + using ImageSharp.Formats; + using ImageSharp.IO; + using Moq; + using Xunit; + + /// + /// Tests the class. + /// + public class ImageSaveTests : IDisposable + { + private readonly Image Image; + private readonly Mock fileSystem; + private readonly Mock format; + private readonly Mock formatNotRegistered; + private readonly Mock encoder; + private readonly Mock encoderNotInFormat; + private readonly IEncoderOptions encoderOptions; + + public ImageSaveTests() + { + this.encoder = new Mock(); + this.format = new Mock(); + this.format.Setup(x => x.Encoder).Returns(this.encoder.Object); + this.format.Setup(x => x.Decoder).Returns(new Mock().Object); + this.format.Setup(x => x.MimeType).Returns("img/test"); + this.format.Setup(x => x.Extension).Returns("png"); + this.format.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + + + this.encoderNotInFormat = new Mock(); + this.formatNotRegistered = new Mock(); + this.formatNotRegistered.Setup(x => x.Encoder).Returns(this.encoderNotInFormat.Object); + this.formatNotRegistered.Setup(x => x.Decoder).Returns(new Mock().Object); + this.formatNotRegistered.Setup(x => x.MimeType).Returns("img/test"); + this.formatNotRegistered.Setup(x => x.Extension).Returns("png"); + this.formatNotRegistered.Setup(x => x.SupportedExtensions).Returns(new string[] { "png", "jpg" }); + + this.fileSystem = new Mock(); + this.encoderOptions = new Mock().Object; + this.Image = new Image(1, 1, new Configuration(this.format.Object) { + FileSystem = this.fileSystem.Object + }); + } + + [Fact] + public void SavePath() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.png")).Returns(stream); + this.Image.Save("path.png"); + + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderOptions); + + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SavePathWithEncoder() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + + + [Fact] + public void SavePathWithFormat() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SavePathWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + this.fileSystem.Setup(x => x.Create("path.jpg")).Returns(stream); + + this.Image.Save("path.jpg", this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStream() + { + Stream stream = new MemoryStream(); + this.Image.Save(stream); + + this.encoder.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderOptions); + + this.encoder.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStreamWithEncoder() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderNotInFormat.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithEncoderAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.encoderNotInFormat.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + [Fact] + public void SaveStreamWithFormat() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.formatNotRegistered.Object); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, null)); + } + + [Fact] + public void SaveStreamWithFormatAndOptions() + { + Stream stream = new MemoryStream(); + + this.Image.Save(stream, this.formatNotRegistered.Object, this.encoderOptions); + + this.encoderNotInFormat.Verify(x => x.Encode(this.Image, stream, this.encoderOptions)); + } + + public void Dispose() + { + this.Image.Dispose(); + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 0ed724fadc..02b0e5ad9b 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -21,11 +21,11 @@ namespace ImageSharp.Tests { Assert.Throws(() => { - new Image((byte[])null); + Image.Load((byte[])null); }); TestFile file = TestFile.Create(TestImages.Bmp.Car); - using (Image image = new Image(file.Bytes)) + using (Image image = Image.Load(file.Bytes)) { Assert.Equal(600, image.Width); Assert.Equal(450, image.Height); @@ -36,7 +36,7 @@ namespace ImageSharp.Tests public void ConstructorFileSystem() { TestFile file = TestFile.Create(TestImages.Bmp.Car); - using (Image image = new Image(file.FilePath)) + using (Image image = Image.Load(file.FilePath)) { Assert.Equal(600, image.Width); Assert.Equal(450, image.Height); @@ -49,7 +49,7 @@ namespace ImageSharp.Tests System.IO.FileNotFoundException ex = Assert.Throws( () => { - new Image(Guid.NewGuid().ToString()); + Image.Load(Guid.NewGuid().ToString()); }); } @@ -59,7 +59,7 @@ namespace ImageSharp.Tests ArgumentNullException ex = Assert.Throws( () => { - new Image((string) null); + Image.Load((string) null); }); } @@ -67,14 +67,14 @@ namespace ImageSharp.Tests public void Save_DetecedEncoding() { string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.png"); - var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); using (Image image = new Image(10, 10)) { image.Save(file); } - var c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); - using (var img = c.CreateImage()) + TestFile c = TestFile.Create("../../TestOutput/Save_DetecedEncoding.png"); + using (Image img = c.CreateImage()) { Assert.IsType(img.CurrentImageFormat); } @@ -84,7 +84,7 @@ namespace ImageSharp.Tests public void Save_UnknownExtensionsEncoding() { string file = TestFile.GetPath("../../TestOutput/Save_DetecedEncoding.tmp"); - var ex = Assert.Throws( + InvalidOperationException ex = Assert.Throws( () => { using (Image image = new Image(10, 10)) @@ -98,14 +98,14 @@ namespace ImageSharp.Tests public void Save_SetFormat() { string file = TestFile.GetPath("../../TestOutput/Save_SetFormat.dat"); - var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); using (Image image = new Image(10, 10)) { image.Save(file, new PngFormat()); } - var c = TestFile.Create("../../TestOutput/Save_SetFormat.dat"); - using (var img = c.CreateImage()) + TestFile c = TestFile.Create("../../TestOutput/Save_SetFormat.dat"); + using (Image img = c.CreateImage()) { Assert.IsType(img.CurrentImageFormat); } @@ -115,14 +115,14 @@ namespace ImageSharp.Tests public void Save_SetEncoding() { string file = TestFile.GetPath("../../TestOutput/Save_SetEncoding.dat"); - var dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); + System.IO.DirectoryInfo dir = System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(file)); using (Image image = new Image(10, 10)) { image.Save(file, new PngEncoder()); } - var c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat"); - using (var img = c.CreateImage()) + TestFile c = TestFile.Create("../../TestOutput/Save_SetEncoding.dat"); + using (Image img = c.CreateImage()) { Assert.IsType(img.CurrentImageFormat); } diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs new file mode 100644 index 0000000000..41b884dd43 --- /dev/null +++ b/tests/ImageSharp.Tests/ImageComparer.cs @@ -0,0 +1,119 @@ +namespace ImageSharp.Tests +{ + using System; + using ImageSharp; + using Xunit; + + /// + /// Class to perform simple image comparisons. + /// + 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 + + /// + /// Does a visual comparison between 2 images and then asserts the difference 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 threshold for the percentage difference where the images are asumed to be the same. + /// The default/undefined value is + /// + /// + /// The threashold 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 CheckSimilarity(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) + where TColorA : struct, IPixel + where TColorB : struct, IPixel + { + float percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor); + + Assert.InRange(percentage, 0, imageTheshold); + } + + /// + /// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2 + /// + /// The color of the source image + /// The color type for the target image + /// The source image + /// The target image + /// + /// The threashold 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. + public static float PercentageDifference(this Image source, Image target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor) + where TColorA : struct, IPixel + where TColorB : struct, IPixel + { + // code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET + Fast2DArray differences = GetDifferences(source, target, scalingFactor); + + int diffPixels = 0; + + foreach (byte b in differences.Data) + { + if (b > segmentThreshold) { diffPixels++; } + } + + return diffPixels / (scalingFactor * scalingFactor); + } + + private static Fast2DArray GetDifferences(Image source, Image target, int scalingFactor) + where TColorA : struct, IPixel + where TColorB : struct, IPixel + { + Fast2DArray differences = new Fast2DArray(scalingFactor, scalingFactor); + Fast2DArray firstGray = source.GetGrayScaleValues(scalingFactor); + Fast2DArray secondGray = target.GetGrayScaleValues(scalingFactor); + + for (int y = 0; y < scalingFactor; y++) + { + for (int x = 0; x < scalingFactor; x++) + { + differences[x, y] = (byte)Math.Abs(firstGray[x, y] - secondGray[x, y]); + } + } + + return differences; + } + + private static Fast2DArray GetGrayScaleValues(this Image source, int scalingFactor) + where TColorA : struct, IPixel + { + byte[] buffer = new byte[4]; + using (Image img = new Image(source).Resize(scalingFactor, scalingFactor).Grayscale()) + { + using (PixelAccessor pixels = img.Lock()) + { + Fast2DArray 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]; + } + } + + return grayScale; + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs index 8ec57057f2..1bc31286da 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifProfileTests.cs @@ -73,7 +73,7 @@ namespace ImageSharp.Tests image.SaveAsJpeg(memStream); memStream.Position = 0; - image = new Image(memStream); + image = Image.Load(memStream); profile = image.MetaData.ExifProfile; Assert.NotNull(profile); @@ -91,7 +91,7 @@ namespace ImageSharp.Tests image.SaveAsJpeg(memStream); memStream.Position = 0; - image = new Image(memStream); + image = Image.Load(memStream); profile = image.MetaData.ExifProfile; Assert.NotNull(profile); @@ -243,7 +243,7 @@ namespace ImageSharp.Tests TestProfile(profile); - var thumbnail = profile.CreateThumbnail(); + Image thumbnail = profile.CreateThumbnail(); Assert.NotNull(thumbnail); Assert.Equal(256, thumbnail.Width); Assert.Equal(170, thumbnail.Height); @@ -286,7 +286,7 @@ namespace ImageSharp.Tests image.Dispose(); memStream.Position = 0; - return new Image(memStream); + return Image.Load(memStream); } } diff --git a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs index daad49b2c7..1d36de5ef3 100644 --- a/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs +++ b/tests/ImageSharp.Tests/MetaData/Profiles/Exif/ExifTagDescriptionAttributeTests.cs @@ -12,7 +12,7 @@ namespace ImageSharp.Tests [Fact] public void TestExifTag() { - var exifProfile = new ExifProfile(); + ExifProfile exifProfile = new ExifProfile(); exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)1); ExifValue value = exifProfile.GetValue(ExifTag.ResolutionUnit); diff --git a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs index 91f383dd2c..97947a7874 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs @@ -9,30 +9,32 @@ namespace ImageSharp.Tests using Xunit; using ImageSharp.Processing; + using ImageSharp.Tests; + using System.Numerics; public class GrayscaleTest : FileTestBase { - public static readonly TheoryData GrayscaleValues - = new TheoryData - { - GrayscaleMode.Bt709 , - GrayscaleMode.Bt601 , - }; - + /// + /// Use test patterns over loaded images to save decode time. + /// [Theory] - [MemberData(nameof(GrayscaleValues))] - public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value) + [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt709)] + [WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt601)] + public void ImageShouldApplyGrayscaleFilterAll(TestImageProvider provider, GrayscaleMode value) + where TColor : struct, IPixel { - string path = this.CreateOutputDirectory("Grayscale"); - - foreach (TestFile file in Files) + using (Image image = provider.GetImage()) { - string filename = file.GetFileName(value); - using (Image image = file.CreateImage()) - using (FileStream output = File.OpenWrite($"{path}/{filename}")) + image.Grayscale(value); + byte[] data = new byte[3]; + foreach (TColor p in image.Pixels) { - image.Grayscale(value).Save(output); + p.ToXyzBytes(data, 0); + Assert.Equal(data[0], data[1]); + Assert.Equal(data[1], data[2]); } + + image.DebugSave(provider); } } } diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs new file mode 100644 index 0000000000..da09aa85e7 --- /dev/null +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeProfilingBenchmarks.cs @@ -0,0 +1,61 @@ +namespace ImageSharp.Tests +{ + using System.IO; + using System.Text; + + using ImageSharp.Processing; + using ImageSharp.Processing.Processors; + + using Xunit; + using Xunit.Abstractions; + + public class ResizeProfilingBenchmarks : MeasureFixture + { + public ResizeProfilingBenchmarks(ITestOutputHelper output) + : base(output) + { + } + + public int ExecutionCount { get; set; } = 50; + + // [Theory] // Benchmark, enable manually! + [InlineData(100, 100)] + [InlineData(2000, 2000)] + public void ResizeBicubic(int width, int height) + { + this.Measure(this.ExecutionCount, + () => + { + using (Image image = new Image(width, height)) + { + image.Resize(width / 4, height / 4); + } + }); + } + + // [Fact] + public void PrintWeightsData() + { + ResizeProcessor proc = new ResizeProcessor(new BicubicResampler(), 200, 200); + + ResamplingWeightedProcessor.WeightsBuffer weights = proc.PrecomputeWeights(200, 500); + + StringBuilder bld = new StringBuilder(); + + foreach (ResamplingWeightedProcessor.WeightsWindow window in weights.Weights) + { + for (int i = 0; i < window.Length; i++) + { + float value = window.Span[i]; + bld.Append(value); + bld.Append("| "); + } + bld.AppendLine(); + } + + File.WriteAllText("BicubicWeights.MD", bld.ToString()); + + //this.Output.WriteLine(bld.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs index 06ab245c90..643033f4c4 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/ResizeTests.cs @@ -64,8 +64,8 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - 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); + 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); } } diff --git a/tests/ImageSharp.Tests/TestFile.cs b/tests/ImageSharp.Tests/TestFile.cs index 42340dc44c..eedc0d306a 100644 --- a/tests/ImageSharp.Tests/TestFile.cs +++ b/tests/ImageSharp.Tests/TestFile.cs @@ -46,7 +46,7 @@ namespace ImageSharp.Tests this.file = file; this.Bytes = File.ReadAllBytes(file); - this.image = new Image(this.Bytes); + this.image = Image.Load(this.Bytes); } /// @@ -139,7 +139,7 @@ namespace ImageSharp.Tests /// public Image CreateImage(IDecoderOptions options) { - return new Image(this.Bytes, options); + return Image.Load(this.Bytes, options); } /// @@ -153,7 +153,7 @@ namespace ImageSharp.Tests List directories = new List< string > { "TestImages/Formats/", // Here for code coverage tests. "tests/ImageSharp.Tests/TestImages/Formats/", // from travis/build script - "../../../ImageSharp.Tests/TestImages/Formats/", // from Sandbox46 + "../../../../../ImageSharp.Tests/TestImages/Formats/", // from Sandbox46 "../../../../TestImages/Formats/", "../../../TestImages/Formats/" }; @@ -165,7 +165,7 @@ namespace ImageSharp.Tests AddFormatsDirectoryFromTestAssebmlyPath(directories); - var directory = directories.FirstOrDefault(x => Directory.Exists(x)); + string directory = directories.FirstOrDefault(x => Directory.Exists(x)); if(directory != null) { diff --git a/tests/ImageSharp.Tests/TestFileSystem.cs b/tests/ImageSharp.Tests/TestFileSystem.cs new file mode 100644 index 0000000000..d43b989f10 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFileSystem.cs @@ -0,0 +1,71 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using ImageSharp.Formats; + using Xunit; + + /// + /// A test image file. + /// + public class TestFileSystem : ImageSharp.IO.IFileSystem + { + + public static TestFileSystem Global { get; } = new TestFileSystem(); + + public static void RegisterGloablTestFormat() + { + Configuration.Default.FileSystem = Global; + } + + Dictionary fileSystem = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public void AddFile(string path, Stream data) + { + fileSystem.Add(path, data); + } + + public Stream Create(string path) + { + // if we have injected a fake file use it instead + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } + } + + return File.Create(path); + } + + + public Stream OpenRead(string path) + { + // if we have injected a fake file use it instead + lock (fileSystem) + { + if (fileSystem.ContainsKey(path)) + { + Stream stream = fileSystem[path]; + stream.Position = 0; + return stream; + } + } + + return File.OpenRead(path); + } + } +} + diff --git a/tests/ImageSharp.Tests/TestFont.cs b/tests/ImageSharp.Tests/TestFont.cs index 3a5bb2b2c1..7e5b6c370d 100644 --- a/tests/ImageSharp.Tests/TestFont.cs +++ b/tests/ImageSharp.Tests/TestFont.cs @@ -47,7 +47,7 @@ namespace ImageSharp.Tests List directories = new List< string > { "TestFonts/", // Here for code coverage tests. "tests/ImageSharp.Tests/TestFonts/", // from travis/build script - "../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 + "../../../../../ImageSharp.Tests/TestFonts/", // from Sandbox46 "../../../../TestFonts/" }; @@ -58,7 +58,7 @@ namespace ImageSharp.Tests AddFormatsDirectoryFromTestAssebmlyPath(directories); - var directory = directories.FirstOrDefault(x => Directory.Exists(x)); + string directory = directories.FirstOrDefault(x => Directory.Exists(x)); if(directory != null) { diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs new file mode 100644 index 0000000000..084ad59938 --- /dev/null +++ b/tests/ImageSharp.Tests/TestFormat.cs @@ -0,0 +1,185 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using ImageSharp.Formats; + using Xunit; + + /// + /// A test image file. + /// + public class TestFormat : ImageSharp.Formats.IImageFormat + { + public static TestFormat GlobalTestFormat { get; } = new TestFormat(); + + public static void RegisterGloablTestFormat() + { + Configuration.Default.AddImageFormat(GlobalTestFormat); + } + + public TestFormat() + { + this.Encoder = new TestEncoder(this); ; + this.Decoder = new TestDecoder(this); ; + } + + public List DecodeCalls { get; } = new List(); + + public IImageEncoder Encoder { get; } + + public IImageDecoder Decoder { get; } + + private byte[] header = Guid.NewGuid().ToByteArray(); + + public MemoryStream CreateStream(byte[] marker = null) + { + MemoryStream ms = new MemoryStream(); + byte[] data = this.header; + ms.Write(data, 0, data.Length); + if (marker != null) + { + ms.Write(marker, 0, marker.Length); + } + ms.Position = 0; + return ms; + } + + Dictionary _sampleImages = new Dictionary(); + + + public void VerifyDecodeCall(byte[] marker, IDecoderOptions options, Configuration config) + { + DecodeOperation[] discovered = this.DecodeCalls.Where(x => x.IsMatch(marker, options, config)).ToArray(); + + + Assert.True(discovered.Any(), "No calls to decode on this formate with the proveded options happend"); + + foreach (DecodeOperation d in discovered) { + this.DecodeCalls.Remove(d); + } + } + + public Image Sample() + where TColor : struct, IPixel + { + lock (this._sampleImages) + { + if (!this._sampleImages.ContainsKey(typeof(TColor))) + { + this._sampleImages.Add(typeof(TColor), new Image(1, 1)); + } + + return (Image)this._sampleImages[typeof(TColor)]; + } + } + + public string MimeType => "img/test"; + + public string Extension => "test_ext"; + + public IEnumerable SupportedExtensions => new[] { "test_ext" }; + + public int HeaderSize => this.header.Length; + + public bool IsSupportedFileFormat(byte[] header) + { + if (header.Length < this.header.Length) + { + return false; + } + for (int i = 0; i < this.header.Length; i++) + { + if (header[i] != this.header[i]) + { + return false; + } + } + return true; + } + public struct DecodeOperation + { + public byte[] marker; + public IDecoderOptions options; + internal Configuration config; + + public bool IsMatch(byte[] testMarker, IDecoderOptions testOptions, Configuration config) + { + if (this.options != testOptions) + { + return false; + } + + if (this.config != config) + { + return false; + } + + if (testMarker.Length != this.marker.Length) + { + return false; + } + + for (int i = 0; i < this.marker.Length; i++) + { + if (testMarker[i] != this.marker[i]) + { + return false; + } + } + return true; + } + } + + public class TestDecoder : ImageSharp.Formats.IImageDecoder + { + private TestFormat testFormat; + + public TestDecoder(TestFormat testFormat) + { + this.testFormat = testFormat; + } + + + public Image Decode(Configuration config, Stream stream, IDecoderOptions options) where TColor : struct, IPixel + + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + var marker = ms.ToArray().Skip(this.testFormat.header.Length).ToArray(); + this.testFormat.DecodeCalls.Add(new DecodeOperation + { + marker = marker, + options = options, + config = config + }); + + // TODO record this happend so we an verify it. + return this.testFormat.Sample(); + } + } + + public class TestEncoder : ImageSharp.Formats.IImageEncoder + { + private TestFormat testFormat; + + public TestEncoder(TestFormat testFormat) + { + this.testFormat = testFormat; + } + + public void Encode(Image image, Stream stream, IEncoderOptions options) where TColor : struct, IPixel + { + // TODO record this happend so we an verify it. + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs index 4d18da0253..206393e274 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/ImageDataAttributeBase.cs @@ -29,25 +29,25 @@ namespace ImageSharp.Tests public override IEnumerable GetData(MethodInfo testMethod) { - var type = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); + TypeInfo type = testMethod.GetParameters().First().ParameterType.GetTypeInfo(); if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(TestImageProvider<>)) { yield return this.AdditionalParameters; } else { - foreach (var kv in this.PixelTypes.ExpandAllTypes()) + foreach (KeyValuePair kv in this.PixelTypes.ExpandAllTypes()) { - var factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); + Type factoryType = typeof(TestImageProvider<>).MakeGenericType(kv.Value); foreach (object[] originalFacoryMethodArgs in this.GetAllFactoryMethodArgs(testMethod, factoryType)) { - var actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2]; + object[] actualFactoryMethodArgs = new object[originalFacoryMethodArgs.Length + 2]; Array.Copy(originalFacoryMethodArgs, actualFactoryMethodArgs, originalFacoryMethodArgs.Length); actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 2] = testMethod; actualFactoryMethodArgs[actualFactoryMethodArgs.Length - 1] = kv.Key; - var factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) + object factory = factoryType.GetMethod(this.GetFactoryMethodName(testMethod)) .Invoke(null, actualFactoryMethodArgs); object[] result = new object[this.AdditionalParameters.Length + 1]; @@ -61,7 +61,7 @@ namespace ImageSharp.Tests protected virtual IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - var args = this.GetFactoryMethodArgs(testMethod, factoryType); + object[] args = this.GetFactoryMethodArgs(testMethod, factoryType); return Enumerable.Repeat(args, 1); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs index c75cf71bf8..be0fa7b3f9 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithFileCollectionAttribute.cs @@ -36,7 +36,7 @@ namespace ImageSharp.Tests protected override IEnumerable GetAllFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - var accessor = this.GetPropertyAccessor(testMethod.DeclaringType); + Func accessor = this.GetPropertyAccessor(testMethod.DeclaringType); accessor = accessor ?? this.GetFieldAccessor(testMethod.DeclaringType); @@ -52,7 +52,7 @@ namespace ImageSharp.Tests private Func GetFieldAccessor(Type type) { FieldInfo fieldInfo = null; - for (var reflectionType = type; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { @@ -71,7 +71,7 @@ namespace ImageSharp.Tests private Func GetPropertyAccessor(Type type) { PropertyInfo propInfo = null; - for (var reflectionType = type; + for (Type reflectionType = type; reflectionType != null; reflectionType = reflectionType.GetTypeInfo().BaseType) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs index e658e817dc..fa5e57dd09 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithMemberFactoryAttribute.cs @@ -33,19 +33,19 @@ namespace ImageSharp.Tests protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) { - var m = testMethod.DeclaringType.GetMethod(this.memberMethodName); + MethodInfo m = testMethod.DeclaringType.GetMethod(this.memberMethodName); - var args = factoryType.GetGenericArguments(); - var colorType = args.Single(); + Type[] args = factoryType.GetGenericArguments(); + Type colorType = args.Single(); - var imgType = typeof(Image<>).MakeGenericType(colorType); - var genericFactoryType = (typeof(GenericFactory<>)).MakeGenericType(colorType); + Type imgType = typeof(Image<>).MakeGenericType(colorType); + Type genericFactoryType = (typeof(GenericFactory<>)).MakeGenericType(colorType); - var funcType = typeof(Func<,>).MakeGenericType(genericFactoryType, imgType); + Type funcType = typeof(Func<,>).MakeGenericType(genericFactoryType, imgType); - var genericMethod = m.MakeGenericMethod(args); + MethodInfo genericMethod = m.MakeGenericMethod(args); - var d = genericMethod.CreateDelegate(funcType); + Delegate d = genericMethod.CreateDelegate(funcType); return new object[] { d }; } diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs new file mode 100644 index 0000000000..98bc45f5b2 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.Reflection; + + /// + /// Triggers passing instances which produce a blank image of size width * height. + /// One instance will be passed for each the pixel format defined by the pixelTypes parameter + /// + public class WithTestPatternImagesAttribute : ImageDataAttributeBase + { + /// + /// Triggers passing an that produces a test pattern image of size width * height + /// + /// The required width + /// The required height + /// The requested parameter + /// Additional theory parameter values + public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters) + : base(pixelTypes, additionalParameters) + { + this.Width = width; + this.Height = height; + } + + public int Width { get; } + public int Height { get; } + + protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern"; + + protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height }; + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs index 87b7ace6ae..c2fe0dc5c2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/GenericFactory.cs @@ -21,7 +21,7 @@ namespace ImageSharp.Tests public virtual Image CreateImage(byte[] bytes) { - return new Image(bytes); + return Image.Load(bytes); } public virtual Image CreateImage(Image other) diff --git a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs index a8d398c1e8..2361bc01ce 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Factories/ImageFactory.cs @@ -7,7 +7,7 @@ namespace ImageSharp.Tests { public class ImageFactory : GenericFactory { - public override Image CreateImage(byte[] bytes) => new Image(bytes); + public override Image CreateImage(byte[] bytes) => Image.Load(bytes); public override Image CreateImage(int width, int height) => new Image(width, height); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs index ad4d2cc986..6dc0d89c52 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs @@ -6,25 +6,46 @@ namespace ImageSharp.Tests { using System; + using Xunit.Abstractions; public abstract partial class TestImageProvider where TColor : struct, IPixel { - private class BlankProvider : TestImageProvider + private class BlankProvider : TestImageProvider, IXunitSerializable { public BlankProvider(int width, int height) { this.Width = width; this.Height = height; } + public BlankProvider() + { + this.Width = 100; + this.Height = 100; + } public override string SourceFileOrDescription => $"Blank{this.Width}x{this.Height}"; - protected int Height { get; } + protected int Height { get; private set; } - protected int Width { get; } + protected int Width { get; private set; } public override Image GetImage() => this.Factory.CreateImage(this.Width, this.Height); + + + public override void Deserialize(IXunitSerializationInfo info) + { + this.Width = info.GetValue("width"); + this.Height = info.GetValue("height"); + base.Deserialize(info); + } + + public override void Serialize(IXunitSerializationInfo info) + { + info.AddValue("width", this.Width); + info.AddValue("height", this.Height); + base.Serialize(info); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs index cd403caed3..bc18209f32 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs @@ -7,11 +7,12 @@ namespace ImageSharp.Tests { using System; using System.Collections.Concurrent; + using Xunit.Abstractions; public abstract partial class TestImageProvider where TColor : struct, IPixel { - private class FileProvider : TestImageProvider + private class FileProvider : TestImageProvider, IXunitSerializable { // Need PixelTypes in the dictionary key, because result images of TestImageProvider.FileProvider // are shared between PixelTypes.Color & PixelTypes.StandardImageClass @@ -33,22 +34,39 @@ namespace ImageSharp.Tests this.filePath = filePath; } + public FileProvider() + { + } + public override string SourceFileOrDescription => this.filePath; public override Image GetImage() { - var key = new Key(this.PixelType, this.filePath); + Key key = new Key(this.PixelType, this.filePath); - var cachedImage = cache.GetOrAdd( + Image cachedImage = cache.GetOrAdd( key, fn => { - var testFile = TestFile.Create(this.filePath); + TestFile testFile = TestFile.Create(this.filePath); return this.Factory.CreateImage(testFile.Bytes); }); return this.Factory.CreateImage(cachedImage); } + + public override void Deserialize(IXunitSerializationInfo info) + { + this.filePath = info.GetValue("path"); + + base.Deserialize(info); // must be called last + } + + public override void Serialize(IXunitSerializationInfo info) + { + base.Serialize(info); + info.AddValue("path", this.filePath); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs index 855374f552..9a67508721 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests { using System; + using Xunit.Abstractions; /// /// Provides instances for parametric unit tests. @@ -14,15 +15,15 @@ namespace ImageSharp.Tests public abstract partial class TestImageProvider where TColor : struct, IPixel { - private class SolidProvider : BlankProvider + private class SolidProvider : BlankProvider { - private readonly byte a; + private byte a; - private readonly byte b; + private byte b; - private readonly byte g; + private byte g; - private readonly byte r; + private byte r; public SolidProvider(int width, int height, byte r, byte g, byte b, byte a) : base(width, height) @@ -33,17 +34,44 @@ namespace ImageSharp.Tests this.a = a; } + public SolidProvider() + : base() + { + this.r = 0; + this.g = 0; + this.b = 0; + this.a = 0; + } + public override string SourceFileOrDescription => $"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})"; public override Image GetImage() { - var image = base.GetImage(); + Image image = base.GetImage(); TColor color = default(TColor); color.PackFromBytes(this.r, this.g, this.b, this.a); return image.Fill(color); } + + public override void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.r); + info.AddValue("green", this.g); + info.AddValue("blue", this.b); + info.AddValue("alpha", this.a); + base.Serialize(info); + } + + public override void Deserialize(IXunitSerializationInfo info) + { + this.r = info.GetValue("red"); + this.g = info.GetValue("green"); + this.b = info.GetValue("blue"); + this.a = info.GetValue("alpha"); + base.Deserialize(info); + } } } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs index cdb31ab69a..26192ba1e1 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs @@ -7,12 +7,13 @@ namespace ImageSharp.Tests { using System; using System.Reflection; + using Xunit.Abstractions; /// /// Provides instances for parametric unit tests. /// /// The pixel format of the image - public abstract partial class TestImageProvider + public abstract partial class TestImageProvider where TColor : struct, IPixel { public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType(); @@ -25,13 +26,22 @@ namespace ImageSharp.Tests public ImagingTestCaseUtility Utility { get; private set; } public GenericFactory Factory { get; private set; } = new GenericFactory(); + public string TypeName { get; private set; } + public string MethodName { get; private set; } - public static TestImageProvider Blank( + public static TestImageProvider TestPattern( int width, int height, MethodInfo testMethod = null, PixelTypes pixelTypeOverride = PixelTypes.Undefined) - => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); + => new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride); + + public static TestImageProvider Blank( + int width, + int height, + MethodInfo testMethod = null, + PixelTypes pixelTypeOverride = PixelTypes.Undefined) + => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride); public static TestImageProvider File( string filePath, @@ -65,12 +75,30 @@ namespace ImageSharp.Tests /// public abstract Image GetImage(); - protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) + public virtual void Deserialize(IXunitSerializationInfo info) + { + PixelTypes pixelType = info.GetValue("PixelType"); + string typeName = info.GetValue("TypeName"); + string methodName = info.GetValue("MethodName"); + + this.Init(typeName, methodName, pixelType); + } + + public virtual void Serialize(IXunitSerializationInfo info) + { + info.AddValue("PixelType", this.PixelType); + info.AddValue("TypeName", this.TypeName); + info.AddValue("MethodName", this.MethodName); + } + + protected TestImageProvider Init(string typeName, string methodName, PixelTypes pixelTypeOverride) { if (pixelTypeOverride != PixelTypes.Undefined) { this.PixelType = pixelTypeOverride; } + this.TypeName = typeName; + this.MethodName = methodName; if (pixelTypeOverride == PixelTypes.StandardImageClass) { @@ -78,19 +106,24 @@ namespace ImageSharp.Tests } this.Utility = new ImagingTestCaseUtility() - { - SourceFileOrDescription = this.SourceFileOrDescription, - PixelTypeName = this.PixelType.ToString() - }; + { + SourceFileOrDescription = this.SourceFileOrDescription, + PixelTypeName = this.PixelType.ToString() + }; - if (testMethod != null) + if (methodName != null) { - this.Utility.Init(testMethod); + this.Utility.Init(typeName, methodName); } return this; } + protected TestImageProvider Init(MethodInfo testMethod, PixelTypes pixelTypeOverride) + { + return Init(testMethod?.DeclaringType.Name, testMethod?.Name, pixelTypeOverride); + } + public override string ToString() { string provName = this.GetType().Name.Replace("Provider", ""); diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs new file mode 100644 index 0000000000..39ce614956 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs @@ -0,0 +1,210 @@ +// +// 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.Numerics; + using Xunit.Abstractions; + + public abstract partial class TestImageProvider + where TColor : struct, IPixel + { + + /// + /// A test image provider that produces test patterns. + /// + /// + private class TestPatternProvider : BlankProvider + { + static Dictionary> testImages = new Dictionary>(); + + public TestPatternProvider(int width, int height) + : base(width, height) + { + } + + public TestPatternProvider() + : base() + { + } + + public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}"; + + public override Image GetImage() + { + lock (testImages) + { + if (!testImages.ContainsKey(this.SourceFileOrDescription)) + { + Image image = new Image(this.Width, this.Height); + DrawTestPattern(image); + testImages.Add(this.SourceFileOrDescription, image); + } + } + + return new Image(testImages[this.SourceFileOrDescription]); + } + + /// + /// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image. + /// + /// + private static void DrawTestPattern(Image image) + { + // first lets split the image into 4 quadrants + using (PixelAccessor pixels = image.Lock()) + { + BlackWhiteChecker(pixels); // top left + VirticalBars(pixels); // top right + TransparentGradients(pixels); // bottom left + Rainbow(pixels); // bottom right + } + } + /// + /// Fills the top right quadrant with alternating solid vertical bars. + /// + /// + private static void VirticalBars(PixelAccessor pixels) + { + // topLeft + int left = pixels.Width / 2; + int right = pixels.Width; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 12; + TColor[] c = { + NamedColors.HotPink, + NamedColors.Blue + }; + int p = 0; + for (int y = top; y < bottom; y++) + { + for (int x = left; x < right; x++) + { + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; + } + } + } + + /// + /// fills the top left quadrant with a black and white checker board. + /// + /// + private static void BlackWhiteChecker(PixelAccessor pixels) + { + // topLeft + int left = 0; + int right = pixels.Width / 2; + int top = 0; + int bottom = pixels.Height / 2; + int stride = pixels.Width / 6; + TColor[] c = { + NamedColors.Black, + NamedColors.White + }; + + int p = 0; + for (int y = top; y < bottom; y++) + { + if (y % stride == 0) + { + p++; + p = p % c.Length; + } + int pstart = p; + for (int x = left; x < right; x++) + { + if (x % stride == 0) + { + p++; + p = p % c.Length; + } + pixels[x, y] = c[p]; + } + p = pstart; + } + } + + /// + /// Fills the bottom left quadrent with 3 horizental bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid). + /// + /// + private static void TransparentGradients(PixelAccessor pixels) + { + // topLeft + int left = 0; + int right = pixels.Width / 2; + int top = pixels.Height / 2; + int bottom = pixels.Height; + int height = (int)Math.Ceiling(pixels.Height / 6f); + + Vector4 red = Color.Red.ToVector4(); // use real color so we can see har it translates in the test pattern + Vector4 green = Color.Green.ToVector4(); // use real color so we can see har it translates in the test pattern + Vector4 blue = Color.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern + + TColor c = default(TColor); + + for (int x = left; x < right; x++) + { + blue.W = red.W = green.W = (float)x / (float)right; + + c.PackFromVector4(red); + int topBand = top; + for (int y = topBand; y < top + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.PackFromVector4(green); + for (int y = topBand; y < topBand + height; y++) + { + pixels[x, y] = c; + } + topBand = topBand + height; + c.PackFromVector4(blue); + for (int y = topBand; y < bottom; y++) + { + pixels[x, y] = c; + } + } + } + + /// + /// Fills the bottom right quadrant with all the colors producable by converting itterating over a uint and unpacking it. + /// A better algorithm could be used but it works + /// + /// + private static void Rainbow(PixelAccessor pixels) + { + int left = pixels.Width / 2; + int right = pixels.Width; + int top = pixels.Height / 2; + int bottom = pixels.Height; + + int pixelCount = left * top; + uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount); + TColor c = default(TColor); + Color t = new Color(0); + + for (int x = left; x < right; x++) + for (int y = top; y < bottom; y++) + { + t.PackedValue += stepsPerPixel; + Vector4 v = t.ToVector4(); + //v.W = (x - left) / (float)left; + c.PackFromVector4(v); + pixels[x, y] = c; + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs index 38429b2786..9fd33d90b6 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs @@ -44,13 +44,26 @@ namespace ImageSharp.Tests /// /// /// The required extension - public string GetTestOutputFileName(string extension = null) + public string GetTestOutputFileName(string extension = null, string tag = null) { string fn = string.Empty; + if (string.IsNullOrWhiteSpace(extension)) + { + extension = null; + } + fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription); - extension = extension ?? Path.GetExtension(this.SourceFileOrDescription); - extension = extension ?? ".bmp"; + + if (string.IsNullOrWhiteSpace(extension)) + { + extension = Path.GetExtension(this.SourceFileOrDescription); + } + + if (string.IsNullOrWhiteSpace(extension)) + { + extension = ".bmp"; + } if (extension[0] != '.') { @@ -65,7 +78,14 @@ namespace ImageSharp.Tests pixName = '_' + pixName; } - return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{extension}"; + tag = tag ?? string.Empty; + if (tag != string.Empty) + { + tag = '_' + tag; + } + + + return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{tag}{extension}"; } /// @@ -80,27 +100,32 @@ namespace ImageSharp.Tests where TColor : struct, IPixel { string path = this.GetTestOutputFileName(extension); - - var format = GetImageFormatByExtension(extension); + extension = Path.GetExtension(path); + IImageFormat format = GetImageFormatByExtension(extension); encoder = encoder ?? format.Encoder; - using (var stream = File.OpenWrite(path)) + using (FileStream stream = File.OpenWrite(path)) { image.Save(stream, encoder, options); } } + internal void Init(string typeName, string methodName) + { + this.TestGroupName = typeName; + this.TestName = methodName; + } + internal void Init(MethodInfo method) { - this.TestGroupName = method.DeclaringType.Name; - this.TestName = method.Name; + this.Init(method.DeclaringType.Name, method.Name); } private static IImageFormat GetImageFormatByExtension(string extension) { - extension = extension.ToLower(); - return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension)); + extension = extension?.TrimStart('.'); + return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)); } private string GetTestOutputDir() diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs new file mode 100644 index 0000000000..e2bc2bd2d7 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -0,0 +1,20 @@ + +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Text; + + public static class TestImageExtensions + { + public static void DebugSave(this Image img, TestImageProvider provider, string extension = "png") + where TColor : struct, IPixel + { + 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); + } + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs index ca33402380..260a677d3d 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestUtilityExtensions.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests foreach (PixelTypes pt in AllConcretePixelTypes.Where(pt => pt != PixelTypes.StandardImageClass)) { string typeName = $"{nameSpace}.{pt.ToString()}"; - var t = ImageSharpAssembly.GetType(typeName); + Type t = ImageSharpAssembly.GetType(typeName); if (t == null) { throw new InvalidOperationException($"Could not find: {typeName}"); @@ -58,16 +58,16 @@ namespace ImageSharp.Tests byte[] bytesA = new byte[3]; byte[] bytesB = new byte[3]; - using (var pixA = a.Lock()) + using (PixelAccessor pixA = a.Lock()) { - using (var pixB = b.Lock()) + using (PixelAccessor pixB = b.Lock()) { for (int y = 0; y < a.Height; y++) { for (int x = 0; x < a.Width; x++) { - var ca = pixA[x, y]; - var cb = pixB[x, y]; + TColor ca = pixA[x, y]; + TColor cb = pixB[x, y]; if (compareAlpha) { diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 1acb9e1492..6760735d1f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Tests public void Use_WithEmptyImageAttribute(TestImageProvider provider, string message) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.Equal(42, img.Width); Assert.Equal(666, img.Height); @@ -38,7 +38,7 @@ namespace ImageSharp.Tests string message) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.Equal(42, img.Width); Assert.Equal(666, img.Height); @@ -62,7 +62,7 @@ namespace ImageSharp.Tests TestImageProvider provider) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.IsType(img); } @@ -74,7 +74,7 @@ namespace ImageSharp.Tests where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.True(img.Width * img.Height > 0); Assert.Equal(88, yo); @@ -91,7 +91,7 @@ namespace ImageSharp.Tests where TColor : struct, IPixel { Assert.NotNull(provider.Utility.SourceFileOrDescription); - var image = provider.GetImage(); + Image image = provider.GetImage(); provider.Utility.SaveTestOutputFile(image, "png"); } @@ -100,13 +100,13 @@ namespace ImageSharp.Tests public void Use_WithSolidFilledImagesAttribute(TestImageProvider provider) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.Equal(img.Width, 10); Assert.Equal(img.Height, 20); byte[] colors = new byte[4]; - using (var pixels = img.Lock()) + using (PixelAccessor pixels = img.Lock()) { for (int y = 0; y < pixels.Height; y++) { @@ -140,7 +140,7 @@ namespace ImageSharp.Tests public void Use_WithMemberFactoryAttribute(TestImageProvider provider) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.Equal(img.Width, 3); if (provider.PixelType == PixelTypes.StandardImageClass) { @@ -162,7 +162,7 @@ namespace ImageSharp.Tests public void Blank_MemberData(TestImageProvider provider) where TColor : struct, IPixel { - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.True(img.Width * img.Height > 0); } @@ -183,7 +183,7 @@ namespace ImageSharp.Tests this.Output.WriteLine("SRC: " + provider.Utility.SourceFileOrDescription); this.Output.WriteLine("OUT: " + provider.Utility.GetTestOutputFileName()); - var img = provider.GetImage(); + Image img = provider.GetImage(); Assert.True(img.Width * img.Height > 0); } diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs index 1db209dcd3..63c24a157c 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestUtilityExtensionsTests.cs @@ -28,7 +28,7 @@ namespace ImageSharp.Tests { Image image = factory.CreateImage(10, 10); - using (var pixels = image.Lock()) + using (PixelAccessor pixels = image.Lock()) { for (int i = 0; i < 10; i++) { @@ -51,10 +51,10 @@ namespace ImageSharp.Tests [Fact] public void Baz() { - var type = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.Color"); + Type type = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.Color"); this.Output.WriteLine(type.ToString()); - var fake = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.dsaada_DASqewrr"); + Type fake = typeof(Color).GetTypeInfo().Assembly.GetType("ImageSharp.dsaada_DASqewrr"); Assert.Null(fake); } @@ -64,8 +64,8 @@ namespace ImageSharp.Tests public void IsEquivalentTo_WhenFalse(TestImageProvider provider, bool compareAlpha) where TColor : struct, IPixel { - var a = provider.GetImage(); - var b = provider.GetImage(); + Image a = provider.GetImage(); + Image b = provider.GetImage(); b = b.OilPaint(3, 2); Assert.False(a.IsEquivalentTo(b, compareAlpha)); @@ -77,8 +77,8 @@ namespace ImageSharp.Tests public void IsEquivalentTo_WhenTrue(TestImageProvider provider, bool compareAlpha) where TColor : struct, IPixel { - var a = provider.GetImage(); - var b = provider.GetImage(); + Image a = provider.GetImage(); + Image b = provider.GetImage(); Assert.True(a.IsEquivalentTo(b, compareAlpha)); } @@ -114,7 +114,7 @@ namespace ImageSharp.Tests { PixelTypes pixelTypes = PixelTypes.Alpha8 | PixelTypes.Bgr565 | PixelTypes.Color | PixelTypes.HalfVector2 | PixelTypes.StandardImageClass; - var expanded = pixelTypes.ExpandAllTypes(); + IEnumerable> expanded = pixelTypes.ExpandAllTypes(); Assert.Equal(expanded.Count(), 5); @@ -128,7 +128,7 @@ namespace ImageSharp.Tests [Fact] public void ToTypes_All() { - var expanded = PixelTypes.All.ExpandAllTypes().ToArray(); + KeyValuePair[] expanded = PixelTypes.All.ExpandAllTypes().ToArray(); Assert.True(expanded.Length >= TestUtilityExtensions.GetAllPixelTypes().Length - 2); AssertContainsPixelType(PixelTypes.Color, expanded);